QUORUMRELAY / REFERENCE

MCP reference, candidate 0.1.0.dev8

The implementation uses the official Python SDK mcp 2.3.0, not hand-written JSON-RPC framing. Both transports call the same engine operations as Python, CLI and REST. Tool arguments are {"request": <shared engine request>}. The full result envelope is returned as structuredContent and JSON text. Failed/unsupported operations set MCP isError; partial is not failed or complete. Fetched source text is untrusted data, never an instruction to the service.

Implemented and tested revisions

Transport Revision Executed behavior
stdio subprocess 2026-07-28 Official modern client, version metadata, full read/import/export and mocked plan/commit/readback/receipt, progress/cancellation
stdio subprocess 2025-11-25 Official legacy client, initialize/initialized negotiation, identical workflow, cancellation notification
Streamable HTTP socket 2026-07-28 Official modern client, per-request version/capabilities, mirrored-header rejection, no session, disconnect cancellation
Streamable HTTP socket 2025-11-25 Official legacy client, initialize/initialized, SDK sessions, identical workflow and explicit cancellation

These are the revisions actually tested for this candidate. The SDK may advertise other backwards-compatible revisions; no test claim is made for those. Unsupported HTTP revisions and header/body mismatch are rejected. No deprecated HTTP+SSE endpoint or experimental Tasks API is exposed.

Modern MCP does not use initialize, session IDs, standalone GET streams or DELETE termination. Version and capabilities are per-request _meta fields, mirrored into HTTP headers as required. A modern client may use server/discover to learn supported versions. Closing a response SSE stream cancels the request.

Legacy MCP uses initialize/initialized negotiation. Subsequent HTTP requests carry the negotiated MCP-Protocol-Version and SDK-issued Mcp-Session-Id. The SDK binds sessions to authenticated principals and owns GET/DELETE semantics, expiry and teardown. Losing a legacy response connection is not itself cancellation; clients send notifications/cancelled. Sessions do not authorize access to another caller's records or plans. Keep one worker or use sticky routing for legacy sessions.

stdio

quorumrelay mcp --database C:\app\connector.sqlite --config C:\app\trusted-principal.json

The host launches and terminates the process. Stdout is MCP only. Process-owner configuration supplies trusted principal, client_id, consumer tenant, allowed operations/source namespaces, and private upstream credentials. stdio does not run HTTP OAuth. Default process policy is read-only. Never copy HTTP provider credentials into model tool arguments.

Streamable HTTP and authentication

Set service.mcp=true in trusted service configuration. REST and /mcp share the configured engine and state. The service binds loopback by default and requires explicit authenticated configuration for non-loopback use. Host and Origin allowlists apply before dispatch.

When bearer policies are configured, MCP requires public_url (service root) and issuer_url (operator-owned external OAuth authority). The official SDK serves protected-resource metadata for public_url + "/mcp" and challenges unauthenticated requests with its metadata URL. Resource/audience validation is enabled. The built-in verifier accepts only explicitly provisioned opaque service tokens in the trusted map; it is not a JWT signature verifier. For existing OAuth infrastructure, Python deployments may supply both mcp_token_verifier and mcp_principal_resolver to create_app. The official TokenVerifier must return validated resource-bound AccessTokens; the trusted resolver maps those claims to a Principal. Never trust an unverified tenant header. This project does not issue OAuth tokens, host a login page, or provide a general authorization server. Actual external login flows are not acceptance claims of the synthetic tests.

Upstream provider credentials remain distinct private context. A service token is never forwarded to provider URLs. Scoped actor/client/tenant state and optional source-namespace policy apply in the engine, not only tool annotations.

Capabilities, cancellation and writes

Available tools mirror all shared operations, including status/cancel and the exact plan/commit/receipt contract. Advertising a tool is not a grant: trusted caller operation policy and engine capability checks still reject disallowed calls. The server does not request sampling, roots, elicitation or model access.

The server keeps SSE responses and legacy sessions enabled: json_response=False, stateless_http=False. The SDK documents that changing these can disable cancellation on the modern or legacy leg respectively. SDK cancellation sets the synchronous engine context's cancellation event. Workers stop cooperatively between bounded units. An already in-flight network request cannot be forcibly reversed; completed or uncertain writes remain in durable receipts for reconciliation. Cancellation never deletes provider data.

write:plan/write:commit are trusted saved-policy grants. Commit contains only the exact immutable scoped plan ID/hash. The service never manufactures a grant from that hash. Examples refuse to commit unless the planned adapter is mock. Mock receipts retain live_acceptance: "NOT_RUN".

Large exported files

For input files, use upload_begin(filename, content_type, byte_length), append base64 chunks of at most 256 KiB with upload_chunk(upload_id, offset, content_base64), then call upload_finish(upload_id). Each returns the shared structured envelope. The final result contains an immutable scoped file_handle, length and SHA256. Pass that handle to import_preview or import_bundle with the neutral format (json, jsonl, or zip). This does not grant a provider write. Wrong-scope, expired, altered and oversized input is rejected. upload_abort discards an unfinished upload. Complete native provider capture normalization continues to use its documented parser input contract.

Export with stream: true or delivery: "storage" returns an opaque artifact_id and manifest instead of large base64 data. The MCP tool also returns a resource link, civic-artifact://ARTIFACT_ID/0. Read that resource with resources/read: each JSON resource contains at most 256 KiB of decoded bytes, an offset, total length, SHA256, and next_offset. Follow offsets until null, decode each base64 chunk, and check the final hash. Every read rechecks scope, expiry, current source authorization and attachment grant. This is bounded chunk delivery, not an unframed binary stdout stream. HTTP clients may instead use the authenticated /v1/files/ARTIFACT_ID stream. Use these paths for large files; inline result/event sizes also depend on the MCP client's configured SSE event limit.

Executable synthetic examples

examples/mock_workflow.py starts an engine with the real write adapters and an injected synthetic upstream transport that never opens a provider socket. examples/mcp-client.py runs the shared workflow through an actual stdio subprocess or configured HTTP service, in modern or --mode legacy mode. See examples/README.md. No live provider acceptance is implied.

Official references checked on 2026-11-11: