Skip to content

feat: MCP carrier with _meta format and reserved key guard#417

Merged
jithinraj merged 8 commits intomainfrom
feat/mcp-carrier-adoption
Feb 23, 2026
Merged

feat: MCP carrier with _meta format and reserved key guard#417
jithinraj merged 8 commits intomainfrom
feat/mcp-carrier-adoption

Conversation

@jithinraj
Copy link
Member

Summary

  • Adds Evidence Carrier Contract support to @peac/mappings-mcp
  • New _meta carrier format: org.peacprotocol/receipt_ref + org.peacprotocol/receipt_jws
  • MCP reserved key guard: assertNotMcpReservedKey() checks second label per MCP 2025-11-25 spec
  • Legacy peac_receipt and org.peacprotocol/receipt keys remain readable (DD-125 phase 1)
  • 31 new tests (20 meta + 11 guard); all existing tests pass unchanged

Key Design

DD-125: 3-Phase Legacy Deprecation

  • v0.11.1 (this PR): extractReceipt() reads BOTH legacy and _meta. attachReceiptToMeta() defaults to _meta; opts.legacyFormat: true for legacy.
  • v0.12.x: Console warning on legacy read (future)
  • v0.13.0: Remove legacy read (future)

MCP Reserved Key Guard (Correction Item 3)

Per MCP spec 2025-11-25, the reserved rule checks the second label (dot-separated):

  • dev.mcp/anything -> RESERVED (2nd label = mcp)
  • io.modelcontextprotocol/x -> RESERVED (2nd label = modelcontextprotocol)
  • com.example.mcp/data -> NOT reserved (2nd label = example)
  • org.peacprotocol/receipt_ref -> NOT reserved (2nd label = peacprotocol)

Polish B: Legacy _meta Key Backward Compat

extractReceiptFromMetaAsync() reads org.peacprotocol/receipt (v0.10.13 format), computes receipt_ref from the JWS, and returns a proper PeacEvidenceCarrier.

Test plan

  • All 30 existing budget tests pass unchanged
  • All 12 existing MCP tests pass unchanged (+ 3 new _meta tests)
  • Guard: 11 tests covering reserved/non-reserved key edge cases
  • Meta: 20 tests covering attach, extract (sync + async), legacy compat
  • pnpm build (77/77), pnpm test (4280), lint, typecheck, guards pass

Stacked on: PR #414 (feat/evidence-carrier-types)

Ship PeacEvidenceCarrier types (Layer 0, zero runtime) and Zod schemas
with shared computeReceiptRef() helper (Layer 1). Includes carrier
conformance fixtures (5 valid, 3 invalid) and verifyReceiptRefConsistency()
for DD-129 async extraction enforcement.

- kernel: PeacEvidenceCarrier, CarrierAdapter<T,U>, CarrierMeta types
- kernel: PEAC_RECEIPT_HEADER canonical constant (DD-127)
- schema: ReceiptRefSchema, CompactJwsSchema, PeacEvidenceCarrierSchema
- schema: computeReceiptRef() with WebCrypto runtime guard
- schema: validateCarrierConstraints() transport-aware validation
- schema: verifyReceiptRefConsistency() for tamper detection
- schema: CARRIER_TRANSPORT_LIMITS per-transport size constants
- conformance: 8 carrier fixtures in specs/conformance/fixtures/carrier/
- conformance: carrier category registered in manifest and tracking
…DD-125)

Add Evidence Carrier Contract support to @peac/mappings-mcp. New _meta
carrier format uses receipt_ref + receipt_jws keys under org.peacprotocol/
prefix. Legacy peac_receipt and org.peacprotocol/receipt keys remain
readable for backward compatibility (DD-125 phase 1).

- meta: attachReceiptToMeta/extractReceiptFromMeta (sync + async)
- meta: McpCarrierAdapter implementing CarrierAdapter interface
- meta: legacy org.peacprotocol/receipt auto-computes receipt_ref (Polish B)
- guard: assertNotMcpReservedKey checks second label per MCP 2025-11-25 spec
- legacy: extractReceipt() reads _meta keys, legacy _meta key, and peac_receipt
- 31 new tests (20 meta + 11 guard), all existing tests unchanged
The MCP spec reserves prefixes where "mcp" or "modelcontextprotocol"
appears as a non-last label (followed by another label). The previous
implementation incorrectly checked only the second label (index 1).
Now checks all labels except the last, matching the spec exactly.

Reserved: mcp.dev/x, tools.mcp.com/x, api.modelcontextprotocol.org/x
Not reserved: dev.mcp/x, io.modelcontextprotocol/x, com.example.mcp/x
The MCP spec states: "Any prefix where the second label is
`modelcontextprotocol` or `mcp` is reserved."

Previous implementation used a non-last-label rule which incorrectly
treated `mcp.dev/...` as reserved and `dev.mcp/...` as not reserved.

Now correctly checks only labels[1] (0-indexed):
  Reserved:     dev.mcp/..., io.modelcontextprotocol/..., com.mcp.tools/...
  Not reserved: mcp.dev/..., modelcontextprotocol.io/..., com.example.mcp/...
@jithinraj jithinraj changed the base branch from feat/evidence-carrier-types to main February 23, 2026 18:29
@jithinraj jithinraj changed the title feat: MCP carrier adoption with _meta format and reserved key guard (DD-125) feat: MCP carrier with _meta format and reserved key guard Feb 23, 2026
@jithinraj jithinraj merged commit 89c92d5 into main Feb 23, 2026
7 checks passed
@jithinraj jithinraj deleted the feat/mcp-carrier-adoption branch February 24, 2026 21:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant