Skip to content

Questionnaire & QML Tools — MCP Tool Reference

Questionnaire tools manage the QML lifecycle: saving new versions, validating with Z3 formal analysis, publishing a version to a campaign, moving between projects, and unpublishing. The lifecycle enforces constraints — for example, unpublishing is blocked if active campaigns reference the questionnaire.

How QML is stored

A questionnaire's QML lives in Askalot's database as an append-only version chain. Every save adds a new version and moves the questionnaire's head to it; earlier versions are never overwritten or deleted.

qml_name is the questionnaire's stable address, of the form projects/{project_id}/questionnaires/{questionnaire_id}.qml. It looks like a path and reads like one, but it identifies a questionnaire — there is no directory to browse and no file to stage. list_qml_files returns the addresses that exist.

Two rules follow from this, and they explain most of the behaviour below:

  • Saving never refuses. A sketch, a pasted block, or half-finished logic must be storable, so save_qml_file records the formal-validation verdict on the version it just wrote and returns any problems as warnings.
  • Publishing does refuse. publish_qml_file pins a version to a campaign, which is the moment the instrument reaches a respondent — so it is the one door that rejects an unsound design. The pinned version is then immutable for that campaign's whole life.

list_questionnaires

List all questionnaires, optionally filtered by name.

Parameter Type Required Default Description
name string No Case-insensitive substring filter
limit integer No 100 Maximum results to return

Returns: { items: [...], count, limit }


get_questionnaire

Get a questionnaire by ID.

Parameter Type Required Default Description
questionnaire_id string Yes Questionnaire UUID

Returns: Questionnaire object or { error }.


create_questionnaire

Create a new questionnaire from QML content. The questionnaire and version 1 of its QML are created together, so a questionnaire never exists without content.

Parameter Type Required Default Description
name string Yes Display name
content string Yes The QML YAML, stored as version 1
project_id string Yes Parent project ID
description string No Free-text description
language string No "en" Language code
estimated_duration integer No Expected completion time in minutes
allow_adhoc boolean No true Whether ad-hoc (campaign-less) surveys may use it

Returns: Created questionnaire object.

Creation is not gated on formal validation — content may be structurally broken. The verdict is recorded, not enforced; publishing to a campaign is what requires soundness.


delete_questionnaire

Soft-delete a questionnaire.

Parameter Type Required Default Description
questionnaire_id string Yes Questionnaire UUID

Returns: { success: true, questionnaire_id } or error.

Soft-delete inserts a backup row; restore_questionnaire (below) can bring the questionnaire back for a short window.


rename_questionnaire

Rename a questionnaire. Pure metadata update — the questionnaire's address and QML version chain are unchanged, so campaigns referencing it keep working.

Parameter Type Required Default Description
questionnaire_id string Yes Questionnaire UUID
name string Yes New display name (max 255 bytes)

Returns: Updated questionnaire object.


restore_questionnaire

Restore a recently soft-deleted questionnaire and re-link its references. Its QML version chain was never touched by the delete, so the restored questionnaire comes back at exactly the version it had.

Parameter Type Required Default Description
questionnaire_id string Yes Questionnaire UUID

Returns: Restored questionnaire object, or an error if the restore window has lapsed.


list_qml_files

List the organization's QML questionnaires. No parameters required. Each entry's qml_name can be handed to any other QML tool.

Returns:

{
  "items": [
    {
      "name": "3f9c2b10-....qml",
      "qml_name": "projects/abc123/questionnaires/3f9c2b10-....qml",
      "tier": "project",
      "project_id": "abc123"
    }
  ],
  "count": 1
}

inspect_qml_file

Get a quick summary of a questionnaire's current version — item count and metadata without the full content.

Parameter Type Required Default Description
qml_name string Yes Questionnaire address from list_qml_files

Returns: { success, qml_name, items_count, metadata }


get_qml_content

Retrieve the raw YAML of a questionnaire's current version.

Parameter Type Required Default Description
qml_name string Yes Questionnaire address

Returns: { success, qml_name, content, size_bytes } for content up to 32 KB.

Above 32 KB the content isn't inlined — instead the response is { success, qml_name, download_url, size_bytes, note }, where download_url is a short-lived link (reusable until it expires) you fetch directly over HTTPS (no further authentication needed). Call get_qml_content again if the link has expired.


save_qml_file

Save QML content as an existing questionnaire's new version, and move its head to that version. Saving never blocks on broken YAML or schema violations — an in-progress, not-yet-valid draft still saves, with any issues returned as a warnings list rather than rejecting the write.

Parameter Type Required Default Description
qml_name string Yes Address of an existing questionnaire
project_id string No When given, must match the questionnaire's project; a mismatch is refused rather than ignored
content string Yes QML YAML content, up to 32 KB

Returns: { success, qml_name, questionnaire_id, version_id, version_number, bytes_written, logic_validated, warnings? }warnings is absent on a clean save, or a list of parse/schema issues on a saved-but-imperfect draft.

Formal validation is re-run against the new content on every save and its verdict is recorded on the version that lands, so a version's soundness stays answerable later even after further edits. A regressing save flips the questionnaire to not-validated without blocking the write; publishing it to a campaign is refused until it is fixed.

An address that backs no questionnaire is refused — call create_questionnaire first.

Content over 32 KB is refused with {"error": "content_too_large_for_inline_save"} and nothing is written. Use request_upload_url / finalize_upload (below) instead of trimming content to fit.


request_upload_url / finalize_upload

A two-step handle for saving QML content too large for save_qml_file's 32 KB inline limit. The content itself never appears in a tool call — you request a handle, upload the bytes directly over HTTPS, then finalize.

  1. request_upload_url(qml_name, project_id?) — mints a short-lived, single-use, size-capped upload handle.

    Returns: { upload_url, artifact_id, max_size_bytes, expires_in }

  2. Upload the raw QML bytes to upload_url directly (an HTTP PUT, outside the MCP tool call).

  3. finalize_upload(artifact_id) — saves the uploaded content as the questionnaire's new version, with exactly the same validation and never-blocks behaviour as save_qml_file. Safe to retry with the same artifact_id.

    Returns: the same envelope as save_qml_file

Requires an HTTP-capable client

Uploading needs the ability to make an HTTPS request outside the MCP conversation. An MCP client with no such capability cannot complete an above-threshold save — save_qml_file's content_too_large_for_inline_save error names this limitation explicitly.


validate_qml_file

Run Z3 SMT formal validation on a questionnaire's current version. Analyzes reachability, consistency, and postcondition satisfiability for every item.

Parameter Type Required Default Description
qml_name string Yes Questionnaire address

Returns:

{
  "is_valid": true,
  "item_count": 12,
  "block_count": 3,
  "item_classifications": {
    "q_age": {
      "precondition": "REACHABLE",
      "postcondition": "CONTINGENT"
    }
  },
  "statistics": {
    "preconditions": {"REACHABLE": 11, "NEVER": 1},
    "postconditions": {"CONTINGENT": 8, "TAUTOLOGICAL": 3, "INFEASIBLE": 1}
  },
  "issues": [
    {
      "item_id": "q_unreachable",
      "severity": "error",
      "type": "unreachable_item",
      "message": "Item is unreachable",
      "suggestion": "Check preconditions"
    }
  ],
  "validation_report": "..."
}

Classification Reference

Precondition statuses (can the item be reached?):

Status Meaning
REACHABLE Item can be reached in at least one flow path
NEVER Item is unreachable — dead code
UNKNOWN Analysis could not determine reachability

Postcondition statuses (are the item's conditions satisfiable?):

Status Meaning
TAUTOLOGICAL Condition is always true (redundant)
CONTINGENT Condition depends on responses (normal)
INFEASIBLE Condition can never be satisfied
UNKNOWN Analysis could not determine

Issue Types

Type Severity Description
unreachable_item error Precondition is NEVER — dead item
infeasible_postcondition error Postcondition can never be satisfied
globally_false_postcondition error Postcondition is always false
tautological_postcondition warning Postcondition is always true (redundant)
vacuous_postcondition warning Postcondition is vacuously true (unreachable item)
file_not_found error The address backs no questionnaire
validation_error error Exception during parsing or analysis

qml_quality_report

Compute a design-quality scorecard (dimensions D2–D8) for a questionnaire's current version. Use it after validate_qml_file reports zero errors — validation proves logical soundness; this grades design quality (instrument economy, quality-gate density, verification coverage, structural complexity, order coherence, path diversity, and burden balance). Deterministic and advisory: grades never affect is_valid or publishing.

Parameter Type Required Default Description
qml_name string Yes Questionnaire address

Returns:

{
  "dimensions": {
    "instrument_economy": {
      "id": "D2",
      "grade": "strong",
      "metrics": { },
      "offenders": [{"item_id": "...", "detail": "..."}],
      "truncated": false
    }
  },
  "item_count": 12,
  "notes": []
}

Each dimension's grade is one of strong, adequate, weak, or not_applicable. An unknown address or an analysis failure returns { error }.


publish_qml_file

Publish a questionnaire to a campaign — the fielding door, and the only tool in this reference that refuses an unsound design.

Parameter Type Required Default Description
questionnaire_id string Yes Questionnaire UUID to field
campaign_id string Yes Campaign UUID to pin it to

The questionnaire's current version is formally validated, the verdict is recorded on that version, and only a clean verdict pins it to the campaign.

Returns: { success, questionnaire_id, campaign_id, qml_version_id, logic_validated }

On refusal: { error, issues }issues lists the error-severity problems to fix. Nothing is pinned, so the campaign keeps fielding whatever it had.

What the pin guarantees

Every survey the campaign produces is served that exact version for its whole life. Editing the questionnaire afterwards cannot change the instrument under a respondent mid-interview. Call publish_qml_file again after an edit to re-validate and re-pin.

The campaign must already reference this questionnaire; if it fields a different one, rebind it with update_campaign_questionnaire first.


move_qml_to_project

Move a questionnaire to another project. The questionnaire ID stays stable and its version chain is untouched, so campaigns referencing it — including ones pinned to an earlier version — continue to work. A move is not an edit.

Parameter Type Required Default Description
questionnaire_id string Yes Questionnaire UUID
target_project_id string Yes Destination project UUID

Returns: { success, questionnaire_id, qml_name }qml_name is the new address.


unpublish_qml_file

Retract a questionnaire from its project by soft-deleting it. Its QML version chain is retained — a completed campaign may still be pinned to one of those versions, so unpublishing removes the questionnaire from your working set without erasing what was fielded. Blocked if the questionnaire has active campaigns.

Parameter Type Required Default Description
questionnaire_id string Yes Questionnaire UUID

Returns: { success, questionnaire_id } or error.


Error Handling

QML Lifecycle Constraints

Scenario Response
Unpublish with active campaigns {"error": "Cannot unpublish: questionnaire has active campaign {campaign_id}"}
Publish an unsound version {"error": "...", "issues": [...]} — nothing is pinned
Publish to a campaign fielding a different questionnaire {"error": "Campaign {id} fields questionnaire {other}, not {id}. Use update_campaign_questionnaire to rebind it first."}
Already in target project {"error": "Questionnaire is already in this project"}

Scoping

Every tool resolves questionnaires within your own organization only. An address belonging to another organization is indistinguishable from one that does not exist, and a project_id that disagrees with the questionnaire's own project is refused rather than silently ignored.

Common Errors

Scenario Response
Questionnaire not found {"error": "Questionnaire {id} not found"}
Organization context not set {"error": "Organization context not set"}
Address backs no questionnaire {"error": "QML document '{name}' not found for this organization"}
QML parse error {"error": "Failed to parse QML: {details}"}