diff --git a/.changeset/preserve-request-meta-envelope-keys.md b/.changeset/preserve-request-meta-envelope-keys.md new file mode 100644 index 0000000000..d45a2c825a --- /dev/null +++ b/.changeset/preserve-request-meta-envelope-keys.md @@ -0,0 +1,6 @@ +--- +'@modelcontextprotocol/core-internal': patch +'@modelcontextprotocol/server': patch +--- + +Publish `RequestMetaEnvelope` with its wire keys intact, so `ctx.mcpReq.envelope?.[CLIENT_CAPABILITIES_META_KEY]` is typed as `ClientCapabilities | undefined`. The 2.4.0 declaration emit dropped the computed keys and published the type as `{}`. diff --git a/packages/core-internal/src/types/types.ts b/packages/core-internal/src/types/types.ts index 61cc7f9b36..601e4b2ecf 100644 --- a/packages/core-internal/src/types/types.ts +++ b/packages/core-internal/src/types/types.ts @@ -4,17 +4,7 @@ import type * as z from 'zod/v4'; -import type { - CLIENT_CAPABILITIES_META_KEY, - CLIENT_INFO_META_KEY, - INTERNAL_ERROR, - INVALID_PARAMS, - INVALID_REQUEST, - LOG_LEVEL_META_KEY, - METHOD_NOT_FOUND, - PARSE_ERROR, - PROTOCOL_VERSION_META_KEY -} from './constants'; +import type { INTERNAL_ERROR, INVALID_PARAMS, INVALID_REQUEST, METHOD_NOT_FOUND, PARSE_ERROR } from './constants'; import type { AnnotationsSchema, AudioContentSchema, @@ -240,20 +230,22 @@ export type NotificationParams = Infer; * The per-request `_meta` envelope carried by every request under protocol revision * 2026-07-28 (protocol version, client info, client capabilities, optional log level). * - * Neutral hand-written shape keyed by the public meta-key constants — never - * inferred from a wire-module schema (this neutral layer does not import from + * Neutral hand-written shape whose keys are the public meta-key constant + * values, written as string literals so the tsdown declaration bundler + * cannot drop them (it published this type as `{}` in 2.4.0). Never inferred + * from a wire-module schema (this neutral layer does not import from * `wire/rev*`). A `type` alias rather than an interface so it stays assignable * to `_meta`'s string-indexed object slot. */ export type RequestMetaEnvelope = { - [PROTOCOL_VERSION_META_KEY]: string; + 'io.modelcontextprotocol/protocolVersion': string; /** * Optional since spec PR #3002: clients SHOULD send it, servers must not * require it (and should not rely on it for behavior or security). */ - [CLIENT_INFO_META_KEY]?: Implementation; - [CLIENT_CAPABILITIES_META_KEY]: ClientCapabilities; - [LOG_LEVEL_META_KEY]?: LoggingLevel; + 'io.modelcontextprotocol/clientInfo'?: Implementation; + 'io.modelcontextprotocol/clientCapabilities': ClientCapabilities; + 'io.modelcontextprotocol/logLevel'?: LoggingLevel; }; /* Empty result */ diff --git a/packages/core-internal/test/types/requestMetaEnvelope.test.ts b/packages/core-internal/test/types/requestMetaEnvelope.test.ts new file mode 100644 index 0000000000..922aa3c33b --- /dev/null +++ b/packages/core-internal/test/types/requestMetaEnvelope.test.ts @@ -0,0 +1,21 @@ +import { describe, expectTypeOf, it } from 'vitest'; + +import { + CLIENT_CAPABILITIES_META_KEY, + CLIENT_INFO_META_KEY, + LOG_LEVEL_META_KEY, + PROTOCOL_VERSION_META_KEY +} from '../../src/types/constants'; +import type { ClientCapabilities, Implementation, LoggingLevel, RequestMetaEnvelope } from '../../src/types/types'; + +describe('RequestMetaEnvelope', () => { + it('is keyed by the public meta-key constants', () => { + expectTypeOf().toEqualTypeOf< + typeof PROTOCOL_VERSION_META_KEY | typeof CLIENT_INFO_META_KEY | typeof CLIENT_CAPABILITIES_META_KEY | typeof LOG_LEVEL_META_KEY + >(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + }); +}); diff --git a/scripts/smoke-dist-types.mjs b/scripts/smoke-dist-types.mjs index 71584b92cc..e352d4c619 100644 --- a/scripts/smoke-dist-types.mjs +++ b/scripts/smoke-dist-types.mjs @@ -18,12 +18,16 @@ try { "import { AjvJsonSchemaValidator } from '@modelcontextprotocol/client/validators/ajv';", "import { CfWorkerJsonSchemaValidator as ClientCf } from '@modelcontextprotocol/client/validators/cf-worker';", "import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';", - "import { McpServer } from '@modelcontextprotocol/server';", + "import { CLIENT_CAPABILITIES_META_KEY, McpServer } from '@modelcontextprotocol/server';", + "import type { ClientCapabilities, RequestMetaEnvelope } from '@modelcontextprotocol/server';", "import { AjvJsonSchemaValidator as ServerAjv } from '@modelcontextprotocol/server/validators/ajv';", "import { CfWorkerJsonSchemaValidator as ServerCf } from '@modelcontextprotocol/server/validators/cf-worker';", "import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';", "export const c = new Client({ name: 'smoke', version: '1.0.0' });", "export const s = new McpServer({ name: 'smoke', version: '1.0.0' });", + 'declare const envelope: Partial | undefined;', + 'const capabilities = envelope?.[CLIENT_CAPABILITIES_META_KEY];', + 'export const _caps: ClientCapabilities | undefined = capabilities;', 'export type T = ClientSchema;', 'export { AjvJsonSchemaValidator, ServerAjv, ClientCf, ServerCf, StdioClientTransport, StdioServerTransport };', '' @@ -77,10 +81,14 @@ try { [ "import { Client } from '@modelcontextprotocol/client';", "import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';", - "import { McpServer } from '@modelcontextprotocol/server';", + "import { CLIENT_CAPABILITIES_META_KEY, McpServer } from '@modelcontextprotocol/server';", + "import type { ClientCapabilities, RequestMetaEnvelope } from '@modelcontextprotocol/server';", "import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';", "export const c = new Client({ name: 'smoke', version: '1.0.0' });", "export const s = new McpServer({ name: 'smoke', version: '1.0.0' });", + 'declare const envelope: Partial | undefined;', + 'const capabilities = envelope?.[CLIENT_CAPABILITIES_META_KEY];', + 'export const _caps: ClientCapabilities | undefined = capabilities;', 'export { StdioClientTransport, StdioServerTransport };', '' ].join('\n')