Documentation menu

Offline MCP elicitation wire matrix

The pinned MCP 2025-11-25 request params and result are complete, unchanged JSON bodies in ahead-of-time §4 uploads. Normalized AHP metadata contains server, mode, request / result references and result action. Bodies are never renamed to schema, flattened, or replaced by arrays of content descriptors.

Run

From the workspace root (the parent of the five repositories):

(cd typescript-sdk && pnpm --filter @agenthooksprotocol/sdk build)
(cd rust-sdk && cargo build --bin elicitation)
python3 agent-hooks-protocol/interop/elicitation_matrix.py
(cd agent-hooks-protocol/interop && python3 -m unittest test_elicitation_matrix)

Python adapter uses python-sdk/.venv/bin/python and the installed jsonschema package. All adapters read canonical schemas from the checkout. No network MCP server, model, credentials, or LLM inference is needed.

Evidence and limits

Each of the 16 directed language pairs starts a separate authenticated HTTP receiver, executes the sender SDK’s actual HTTP stack, uploads exact UTF-8 octets, and transmits real hooks/intercept envelopes. The receiver itself validates the canonical envelopes, complete MCP JSON bodies, metadata equality, ownership, size, SHA-256, immutable references, and request/result pairing. Exact receiver receipts include the received envelope, selected body bytes and decoded result. The oracle holds expected outcomes; /receipts reads receiver evidence. Uploads are scoped to the isolated authenticated credential scope. Wrong or unauthorized credentials are rejected; subscription labels remain local test configuration. Receivers return 201 JSON content-reference objects with assigned refs; callers send neither ref nor subscription headers.

The matrix covers explicit/default form mode, form accept/decline/cancel, URL accept/decline/cancel, URL ID/address preservation, missing mode-specific fields, invalid actions and content, submitted form data that fails required/type/enum/number constraints, forbidden URL/decline/cancel content, metadata mismatches, missing uploads, bad digests and immutable references. Metadata/omit cases for both modes prove zero per-scenario uploads and no raw prompt, schema, URL, result text or body bytes in events/receipts. Selected gaps fail closed. Upstream default annotations are preserved without being applied or tested as submitted data (including a fractional integer-property default and an enum default outside the listed options). URL accept never reports external completion. The authenticated principal comes from the receiver’s out-of-band bearer configuration, not asserted event/source/server fields. Result binding additionally requires an explicit parentEventId equal to the request event ID, the same event source, and matching session-ID scope, checked before resolving bodies or staging result effects. Pending requests are indexed by (source, request event ID), never session alone. Duplicate live identities are rejected without replacement. Failed result correlation does not consume a pending request. The wire grid includes all scope mismatches for body/metadata/omit and two requests outstanding in the same session, settled in reverse order with distinct form schemas. Unmodified results are labelled mcp, not verified human.

The separate offline check entry point tests all four SDK helpers for canonical return/deny/modify effects, per-request grants, modify target/operation grants, and trusted hook provenance. The atomic application helper stages before-request return / deny and result content replace/merge on snapshots. Only a fully validated final MCP/form result is returned for publication. Invalid later effects, ungranted operations, conflicting terminal effects, or invalid submitted form data reject the whole list with no partial result. Inputs and uploaded bytes remain unchanged; callers upload the complete returned JSON before emitting a body-selected result event. Metadata/omit cannot authorize execution. The HTTP matrix also executes the sender SDK’s atomic helper, independently checks its output, then uploads and delivers that actual staged output to every receiver SDK. Before-return, before-deny, result replace/merge and URL return/deny are published through the wire matrix. Failed atomic lists publish no result.

Form/url registration is optional and independent. Per-request mode validation requires the corresponding advertised mode. An absent capability supports neither; AHP {} also supports neither. Only explicit MCP-origin legacy {} normalizes to {form:{}}; it never grants URL support or leaks into AHP registration.

The host-provided canonical validator callback also handles form-answer values {schema, value} with a standards-compliant JSON Schema validator. This validates submitted values against the original requestedSchema without altering schema annotations/defaults or coercing submitted values. Body-selected form accepts require this validation. Metadata/omit receipts explicitly mark body validation as not-selected; they do not claim to validate unavailable bytes.

This matrix covers HTTP bearer. Coverage excludes OAuth/workload/mTLS, stdio, browser/OAuth completion, host UI, cancellation races, and production server hardening. MCP structural and submitted form-schema validation are enforced on selected bodies. The shared upload and lifecycle matrices cover other transport/authentication and scheduling behaviors independently.

Upstream provenance

upstream/mcp/2025-11-25/pin.json records version-tagged official TypeScript and JSON Schema URLs and full-file SHA-256 digests. tools/mcp_elicitation.py verifies deterministic extraction offline; explicit --refresh downloads that pinned version for reviewed updates, not automatic tracking of future MCP revisions. Extraction preserves primitive and enum variants and default types, restoring TypeScript numeric types where the upstream JSON generator narrowed numbers. The published requested-schema vocabulary is intentionally closed, unlike upstream extension-open objects; MCP params/results remain open. See the interaction payload specification and public boundary APIs.

Public-client stream handoff

The controller starts a fresh sender for each operation. It retains original upload bytes, but never imports an opaque reference into an SDK registry. Each client plan now includes contentSources, an array of { "descriptor": { "ref", "size", "sha256" }, "bytes": "<base64>" } entries. These are only previously successful, integrity-checked upload confirmations. Existing reference-oriented adapters may ignore this additive field.

A stream-oriented adapter must match a selected body descriptor exactly to a source (including size and digest), create a new raw byte stream for the public client, and let that client upload it. Missing sources and incorrect metadata must not be repaired. Negative interception operations carry step.bypass: true to preserve intentionally invalid wire data rather than normalize it.

If a public client allocates a fresh receiver reference, its result entry must also contain contentUploads, an array of { "sourceRef": "<input descriptor ref>", "descriptor": { "ref", "size", "sha256" } }. These descriptors must come from the actual successful upload HTTP response, not aliases or synthesized confirmations. A trusted fetch wrapper can record upload response clones without changing the public client’s request/response behavior; match response integrity to original bytes. Do not send extra uploads merely to construct this report.

The controller verifies integrity, immutable reference identity, source membership and selection before substituting only reference strings in the planned message. It then compares the receiver-created receipts exactly, including that message, raw bytes, semantic summary, correlation and provenance. Thus sender claims cannot substitute for receiver evidence. No protocol schema or scenario intent changes are needed.

For an isolated four-SDK workspace, run:

python3 interop/elicitation_matrix.py --root /path/to/workspace \
  --output /tmp/elicitation-allpairs.json

Omit sender/receiver filters for the full 16-pair matrix.