Wire Schemas

Purpose and Scope

Wire schemas are the public validation layer for code that handles raw Model Context Protocol JSON rather than higher-level SDK objects. The SDK documentation defines them as the exact Zod constants used to validate protocol and OAuth payloads, exported from the core package for gateway, proxy, test harness, and worker-fleet code. That distinction matters because most application code should not need this layer. If you build a server with the high-level server API or a client with the high-level client API, the SDK has already converted incoming messages into validated values before your handlers or call results see them. Sources: docs/advanced/wire-schemas.md

Use this page when you are designing code that sits between two MCP peers, records or replays traffic, validates upstream responses before relaying them, or accepts authentication and discovery metadata as plain JSON. The wire-schema workflow is intentionally lower level than server tool registration or client calling helpers. You are responsible for choosing the right schema for the message currently in hand, deciding whether parse failures should stop forwarding, and keeping any proxy behavior consistent with the protocol envelope you received. The benefit is precise validation without having to instantiate a full SDK client or server around every payload. Sources: docs/advanced/wire-schemas.md

Relevant Source Files

  • docs/advanced/wire-schemas.md - The first-party how-to for validating raw payloads, choosing schema constants, and routing JSON-RPC messages in proxy-style code.
  • docs/.vitepress/nav.ts - Places the Wire schemas guide under the Advanced documentation group, next to custom transports, schema libraries, and gateway patterns.
  • docs/.vitepress/llms.ts - Generates markdown renditions and LLM indexes from the VitePress sidebar, preserving guide order and stripping snippet wiring attributes from examples.
  • docs/_meta/CONVENTIONS.md - Defines documentation conventions used by the authored guide, including code-first task flow, concise imperative steps, and recap expectations.
  • docs/.vitepress/theme/index.ts - Shows the v2 documentation site theme extension and shared banner slot used when the guide is rendered.
  • docs/v1/.vitepress/theme/index.ts - Shows the v1 documentation theme using the shared custom CSS, useful context for migration notes that compare v1 and v2 docs.

Core Primitives

The central primitive is a named schema constant that mirrors a named specification type. The guide describes the naming convention as one schema constant for every named type in the spec, using the type name followed by Schema. That gives proxy code a direct mapping from the payload it holds to the validator it should apply. A result body from a forwarded tool call is checked with the tool-call result schema. A full JSON-RPC frame whose method is still unknown is checked with the generic JSON-RPC message schema before the proxy branches on the decoded envelope. Sources: docs/advanced/wire-schemas.md

The guide also distinguishes schema families, which helps keep validation local to the current routing decision. Request schemas validate complete request messages, result schemas validate complete response payloads, notification schemas validate notifications, and parameter schemas validate just a params object. That separation is useful when middleware receives only part of a message, for example after a transport layer has already decoded the envelope but before an upstream-specific router has validated method-specific fields. The docs emphasize that the schema constants are Zod objects, so callers can use either a throwing parse flow or a non-throwing safe-parse flow depending on failure handling needs. Sources: docs/advanced/wire-schemas.md

import { CallToolResultSchema, JSONRPCMessageSchema } from '@modelcontextprotocol/core';

Install the core package separately when you need this validation layer. The guide states that the server and client packages keep a Zod-free public surface and do not depend on the core schema package, while the core package is runtime-neutral and depends on Zod. That packaging choice gives high-level SDK users a smaller public API and lets raw JSON handlers opt into validation only where it is needed. In migration work, the same section is the replacement for v1 code that imported schema constants from the older SDK types entry point; the codemod rewrites those imports to the split package path. Sources: docs/advanced/wire-schemas.md

Validation Workflow

Start by identifying whether your code holds an entire JSON-RPC message, a method-specific request, a response result, a notification, or only a parameter object. If the answer is not known yet, validate the undecoded envelope first. The documented envelope schema narrows a parsed frame into one of the four JSON-RPC shapes: request, notification, result response, or error response. That first step gives routing code a safe structure to inspect without assuming that arbitrary JSON has a method, id, params, result, or error field with the right shape. Sources: docs/advanced/wire-schemas.md

After the envelope is trusted, switch to the schema that matches the next concrete operation. The guide’s first example validates an upstream tool-call result before relaying it. The body is parsed as unknown JSON, passed through a non-throwing schema check, and only then used as typed data. This is the right pattern for gateways because it lets the gateway return or log a clear upstream-contract error without crashing on malformed traffic. The malformed example demonstrates that an invalid content field returns structured issues describing the expected array, the received string, and the offending field path. Sources: docs/advanced/wire-schemas.md

const parsed = CallToolResultSchema.safeParse(body);
if (!parsed.success) {
  throw new Error(`upstream returned an invalid tools/call result: ${parsed.error.message}`);
}

Use throwing parsing when invalid input should abort the current operation immediately, such as a test fixture that must fail fast or a proxy branch that cannot proceed without a valid method-specific request. Use safe parsing when the surrounding code needs to decide how to report the problem, attach upstream context, or continue processing other messages in a batch or stream. Both styles are grounded in the same exported schema constants, so the choice is about error policy rather than schema accuracy. The important implementation rule is to validate before reading nested fields that came from the network. Sources: docs/advanced/wire-schemas.md

Proxy Routing and Schema Selection

A proxy should parse the JSON-RPC envelope once, branch on the method, and then apply the per-method request schema before forwarding. The guide shows this pattern for a tool-call route: check that the decoded message has a method, switch on the method value, parse the matching request schema, and only then read the tool name from params. Unknown methods can be forwarded unchanged after the generic envelope check, which is useful for extension methods and gateway patterns that do not terminate every operation locally. Sources: docs/advanced/wire-schemas.md

This routing shape prevents two common classes of gateway bugs. First, it avoids method-specific assumptions before the message has been identified as a request or notification. Second, it prevents a proxy from relaying a method that looks familiar but has invalid method-specific fields. The distinction is especially important when a gateway fans out to multiple upstream servers or records advertisements for later routing. The wire schema validates the boundary between generic transport framing and the operation contract, while the proxy still owns policy decisions such as which upstream receives a valid call and how errors are surfaced. Sources: docs/advanced/wire-schemas.md

const message = JSONRPCMessageSchema.parse(JSON.parse(frame));
if ('method' in message) {
  switch (message.method) {
    case 'tools/call':
      // validate with the method-specific request schema before forwarding
      break;
  }
}

Treat parameter schemas as a narrower tool, not as a replacement for full-message validation. They are useful when a framework has already validated the envelope, when a test harness constructs only params, or when a transport callback hands your code a params object separately from JSON-RPC metadata. If your code is responsible for receiving a raw frame, begin with the message schema so the id, method, result, and error shape is validated consistently. Then move inward to the operation schema that matches the branch you selected. Sources: docs/advanced/wire-schemas.md

OAuth, Discovery, and Migration Notes

The same public schema package covers protocol and OAuth payloads, so it is the right place to validate raw authentication or discovery JSON when your code is not using a higher-level helper that already performs that work. That includes resource-server metadata probes, authorization-server discovery responses, or gateway code that receives metadata before deciding which backend or client context should handle a request. The documentation’s rule still applies: if a high-level SDK component is already managing the interaction, prefer the typed SDK surface; if your code is holding untrusted JSON directly, validate it with the matching exported schema before trusting fields. Sources: docs/advanced/wire-schemas.md

Migration from v1 mainly changes where schema constants come from, not the validation idea. The guide calls out that the v1 constants previously exported from the SDK types entry point now come from the core package, and that the codemod rewrites the import path. That note is important for libraries that were written as protocol adapters, mocks, or traffic inspectors, because those projects often imported schema constants directly. During a v2 migration, separate the mechanical import rewrite from any protocol-revision behavior changes, then rerun tests that feed malformed payloads through the proxy or harness. Sources: docs/advanced/wire-schemas.md

Protocol revision work can introduce wire-only fields or era-specific behavior that high-level public types intentionally hide. Official migration guidance for the newer revision warns that code already on v2 should treat revision adoption as explicit and should not rely on hand-constructed clients or servers changing wire behavior by default. For raw validators, the practical takeaway is to keep schema selection close to the revision and route being validated, and to avoid treating SDK object types as a complete description of every byte that may appear on the wire. Sources: docs/advanced/wire-schemas.md

Documentation and Site Integration

The docs navigation places Wire schemas in the Advanced group, after custom transports and before gateway material. That ordering reflects the intended reader path: learn high-level client and server APIs first, then use schema-library guidance for tool definitions, custom transports for framing, wire schemas for raw validation, and gateway guidance for larger proxy topologies. Readers who arrive from server or client pages should confirm that they truly need raw JSON validation before adding this package, because the authored guide explicitly says high-level handlers and calls already provide validated values. Sources: docs/.vitepress/nav.ts, docs/advanced/wire-schemas.md

The documentation build also creates LLM-facing markdown renditions for guide pages in sidebar order. The generator strips frontmatter, removes source attributes from fenced snippets, absolutizes links, and keeps generated API reference material out of the concatenated guide text so the prose remains focused. That matters for this guide because the examples use snippet wiring in the source markdown, but rendered and LLM-facing versions should read as ordinary TypeScript examples. The page therefore serves both human readers in VitePress and automated readers that consume plain markdown from the generated index. Sources: docs/.vitepress/llms.ts, docs/.vitepress/nav.ts

The repository’s documentation conventions explain the concise, task-oriented shape of these guides: lead with code quickly, define identifiers inline, keep main-flow paragraphs short, and move caveats into notes when they would interrupt the task. The v2 and v1 theme files show that both sites extend the default VitePress theme with a banner, with the v1 theme sharing the v2 custom CSS rather than duplicating it. For Wire schemas, that surrounding infrastructure reinforces two reader signals: this is an advanced v2 guide, and v1 migration notes should point readers toward the upgrade path without mixing old and new imports. Sources: docs/_meta/CONVENTIONS.md, docs/.vitepress/theme/index.ts, docs/v1/.vitepress/theme/index.ts

Practical Checklist

Before you introduce wire schemas, write down the boundary that receives raw JSON and the payload family it expects. If the boundary receives a frame, validate the JSON-RPC message first. If it receives a decoded operation, validate the matching request, result, notification, or params schema before reading nested fields. If it receives OAuth or discovery metadata, choose the matching public schema from the same core package and keep the validation result attached to the routing or authorization decision that depends on it. Sources: docs/advanced/wire-schemas.md

Then decide how failures should behave. A local test harness can throw immediately because a malformed fixture is a test failure. A gateway should usually convert schema failures into a clear diagnostic that names the upstream, method, and schema issue without leaking unrelated payload data. A proxy that forwards unknown extension methods should still validate the generic envelope before forwarding, because extension tolerance is not the same as accepting malformed JSON-RPC. Finally, keep imports limited to the core schema package for this layer and use the high-level client or server packages everywhere you do not directly own raw payload validation. Sources: docs/advanced/wire-schemas.md