Survey Review MCP#

survey-review-mcp is a Model Context Protocol server that exposes a review-queue session for inspection and decision-making through an MCP client.

The installed CLI uses the official MCP TypeScript server runtime over stdio. It does not make network requests. All review state lives in one local session JSON file.

Protocol compatibility#

One server definition serves both MCP eras:

  • MCP 2026-07-28 clients open with server/discover.
  • Existing clients continue to use the legacy initialize flow.

There is no protocol setting to manage. The opening request selects the compatible era automatically, while the same queue, decision validation, and atomic persistence code runs behind both. Modern cacheable discovery, list, and resource responses are private with a zero TTL because the session is mutable.

Start the server#

npx survey-review-mcp --session path/to/session.json

Options:

Flag Description
--session <path> Path to the session JSON file (read and updated in-place). Defaults to the bundled example-data/mcp-review-session.json fixture.
--no-ui Suppress the embedded UI resource from survey_review_queue and survey_review_item results. Text content is always returned.

Tool table#

Tool Args Returns
survey_review_queue (none) Text summary + JSON of all items with status, active item, queue progress, and session totals
survey_review_item itemName: string Full item detail: current vs proposed values, confidence, source refs, excerpts, and current decision
survey_review_decide itemName: string, decision: "accept" | "hold" | "reject" | "could-not-confirm" | "select", note?: string, reason?: string, attemptEvidenceIds?: string[], candidateId?: string Applies the decision through the real session APIs and persists atomically. reason is required and non-empty for could-not-confirm. candidateId is required for select.

Decision mapping:

MCP value ReviewWorkbenchDecision Effect
accept accept-proposed Proposed value becomes the verified outcome
hold keep-current Current value remains the verified outcome
reject reject-proposed Proposed value is rejected; current value is unmodified
could-not-confirm could-not-confirm Review round ends without changing, rejecting, or escalating the claim; a reason is required
select select-proposed On a conflict (several proposed values), the value named by candidateId becomes the verified outcome; the other proposed values are recorded as seen and not chosen

On a conflict item, survey_review_item lists each proposed value with its candidateId, and the card offers "Use this value" per value. select is refused for an id that is not one of the item's proposed candidates, and for an item with a single proposed value (use accept). accept stays refused on a conflict. After a choice the item text, data (selectedCandidateId, and chosen on each proposed candidate) and card mark every value chosen or not chosen.

Rejected calls#

Domain errors such as an unknown item or an item that is already decided return { isError: true, content: [{ type: "text", text: "..." }] }. Schema-invalid arguments, including an unknown decision value, are rejected by the official runtime before the handler runs. In every negotiated protocol path the server rejects these calls before applying or persisting a decision.

UI card behavior#

survey_review_queue and survey_review_item include an embedded UI resource:

{
  "type": "resource",
  "resource": {
    "uri": "ui://survey/review-card/<instance>",
    "mimeType": "text/html;profile=mcp-app",
    "text": "<!doctype html>...",
    "_meta": {
      "ui": {
        "csp": {
          "connectDomains": [],
          "resourceDomains": []
        }
      },
      "mcpui.dev/ui-preferred-frame-size": ["420px", "560px"]
    }
  }
}

The HTML card is compact and fully self-contained (no external network requests). It shows:

  • Current vs proposed values side by side
  • Confidence percentage and source reference for each candidate
  • Excerpt from the source locator
  • Queue progress (resolved / total)
  • Current decision badge (if any)
  • A reviewer note textarea
  • Accept / Hold / Reject / Could not confirm buttons

Buttons post back to the host using the MCP guest-to-host postMessage protocol:

window.parent.postMessage({
  jsonrpc: "2.0",
  id: <n>,
  method: "tools/call",
  params: { name: "survey_review_decide", arguments: { itemName, decision, note } }
}, "*");

The card uses --k-* design tokens matching the Console Kit dark theme by default, with prefers-color-scheme: light support. No fonts or assets are fetched from the network.

Pass --no-ui to suppress the UI resource; the text content items are always returned.

The queue tool also declares its app using canonical nested _meta.ui.resourceUri. The flat _meta["ui/resourceUri"] key remains transitional compatibility output for older Apps hosts. New integrations should read the nested shape.

Session file contract#

The session file is a JSON object with three keys, and an optional fourth:

{
  "session": { ... },    // ReviewSession resource (metadata only)
  "snapshot": { ... },  // ReviewQueueSessionState at session creation
  "events": [ ... ],    // ReviewSessionEvent array (appended by decide)
  "extractionImport": { ... } // optional: the ExtractionEnvelopeImport record the snapshot's items came from
}

When extractionImport is present, every tool checks the snapshot's items against it (attestReviewQueueExtraction). A queue that does not match it (for example an excluded rival value deleted from a stored item) is refused: every tool returns isError and nothing is written. When it is absent and the items came from an extraction import, the queue is served with an "Unverified queue" notice at the top of the queue and item text and on the card, and the data carries queueAttestation: "unverified". Store the import record with the session when you create it.

The snapshot field is the baseline state. All decisions are recorded as append-only ReviewSessionEvent records in events. When a tool reads state it replays the events over the snapshot using replayReviewSessionEvents. When survey_review_decide applies a new decision it:

  1. Replays events to derive the current live state.
  2. Builds the updated session with the new decision.
  3. Generates a fresh full event stream via buildReviewSessionEvents.
  4. Validates the new events using deriveServerReviewSessionApplyResult (freshness + replay checks).
  5. Writes the updated file atomically (temp file + rename).

The file is written atomically so a failed write leaves the previous state intact.

No-network statement#

The MCP server makes no outbound network requests. It reads and writes one local file. The embedded HTML card contains no external <script>, <link>, <img>, or @import references, and its Apps CSP declares no connect or resource domains. Standard output is reserved for JSON-RPC; sanitized SDK and transport diagnostics are written only to standard error.

Source: docs/review-mcp.md