Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/lazy-era-wire-schemas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@modelcontextprotocol/client': patch
'@modelcontextprotocol/server': patch
'@modelcontextprotocol/server-legacy': patch
---

Build protocol-revision wire schemas lazily on first validation instead of at import. Each revision's schema set is now constructed by a module-level memoized factory, so importing the client or server package no longer pays the construction cost of both frozen wire-schema graphs up front. Method membership in the revision registries stays static, the schemas themselves are unchanged, and registry lookups keep returning reference-identical schema objects.
2,432 changes: 2,432 additions & 0 deletions packages/core-internal/src/wire/rev2025-11-25/buildSchemas.ts

Large diffs are not rendered by default.

12 changes: 8 additions & 4 deletions packages/core-internal/src/wire/rev2025-11-25/codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ import type * as z from 'zod/v4';
import type { CallToolResult, Result } from '../../types/types';
import type { DecodedResult, EnvelopeIssue, LiftedWireMaterial, OutboundEnvelopeMaterial, ValidateOutcome, WireCodec } from '../codec';
import { appendTextFallbackForNonObject } from '../textFallback';
import { buildSchemas2025 } from './buildSchemas';
import { isNonObjectJsonSchemaRoot, wrapOutputSchemaForLegacy } from './legacyWrap';
import { getNotificationSchema, getRequestSchema, getResultSchema, hasNotificationMethod2025, hasRequestMethod2025 } from './registry';
import { CreateMessageResultSchema, CreateMessageResultWithToolsSchema } from './schemas';

function isPlainObject(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === 'object' && !Array.isArray(value);
Expand Down Expand Up @@ -75,9 +75,13 @@ export const rev2025Codec: WireCodec = {
validateInputResponse: (): ValidateOutcome<never> => NOT_IN_ERA,

// Arrow literals can't carry overload signatures; the cast is sound (the
// boolean dispatches to exactly the schema each overload names).
samplingResultVariant: ((hasTools: boolean, raw: unknown) =>
triState(hasTools ? CreateMessageResultWithToolsSchema : CreateMessageResultSchema, raw)) as WireCodec['samplingResultVariant'],
// boolean dispatches to exactly the schema each overload names). The
// schemas are pulled through the era's memo so the codec module itself
// stays construction-free at import time.
samplingResultVariant: ((hasTools: boolean, raw: unknown) => {
const s = buildSchemas2025();
return triState(hasTools ? s.CreateMessageResultWithToolsSchema : s.CreateMessageResultSchema, raw);
}) as WireCodec['samplingResultVariant'],

// The 2025 era carries no per-request `_meta` envelope — legacy wire
// bytes stay identical (the never-stamp guarantee, outbound-request half).
Expand Down
320 changes: 169 additions & 151 deletions packages/core-internal/src/wire/rev2025-11-25/registry.ts

Large diffs are not rendered by default.

Loading
Loading