diff --git a/.changeset/abort-handlers-on-close.md b/.changeset/abort-handlers-on-close.md index b6bc65e652..c09d8b5aac 100644 --- a/.changeset/abort-handlers-on-close.md +++ b/.changeset/abort-handlers-on-close.md @@ -1,5 +1,5 @@ --- -'@modelcontextprotocol/core': patch +'@modelcontextprotocol/core-internal': patch --- Abort in-flight request handlers when the connection closes. Previously, request handlers would continue running after the transport disconnected, wasting resources and preventing proper cleanup. Also fixes `InMemoryTransport.close()` firing `onclose` twice on the initiating side. diff --git a/.changeset/add-core-public-package.md b/.changeset/add-core-public-package.md new file mode 100644 index 0000000000..23cb56cb21 --- /dev/null +++ b/.changeset/add-core-public-package.md @@ -0,0 +1,5 @@ +--- +'@modelcontextprotocol/core': minor +--- + +Add `@modelcontextprotocol/core`: the public home for the MCP specification and OAuth/OpenID Zod schemas. It bundles the SDK's internal schema definitions and re-exports only the `*Schema` values, so consumers can validate protocol payloads (`Schema.parse(value)` / `.safeParse(value)`) without depending on a package's internal barrel. Alongside the spec schemas it also re-exports the auth schemas v1 exposed from `@modelcontextprotocol/sdk/shared/auth.js` (e.g. `OAuthTokensSchema`, `OAuthMetadataSchema`, `IdJagTokenExchangeResponseSchema`). Spec types, error classes, enums, and guards continue to live on `@modelcontextprotocol/server` and `@modelcontextprotocol/client`. diff --git a/.changeset/add-resource-size-field.md b/.changeset/add-resource-size-field.md index bef37cb408..07d06b436d 100644 --- a/.changeset/add-resource-size-field.md +++ b/.changeset/add-resource-size-field.md @@ -1,5 +1,5 @@ --- -'@modelcontextprotocol/core': patch +'@modelcontextprotocol/core-internal': patch --- Add missing `size` field to `ResourceSchema` to match the MCP specification diff --git a/.changeset/add-sdk-http-error.md b/.changeset/add-sdk-http-error.md index c3331a565e..05abc0d8f1 100644 --- a/.changeset/add-sdk-http-error.md +++ b/.changeset/add-sdk-http-error.md @@ -1,5 +1,5 @@ --- -"@modelcontextprotocol/core": minor +"@modelcontextprotocol/core-internal": minor "@modelcontextprotocol/client": minor --- diff --git a/.changeset/busy-weeks-hang.md b/.changeset/busy-weeks-hang.md index a045aaa41f..0a8801eb37 100644 --- a/.changeset/busy-weeks-hang.md +++ b/.changeset/busy-weeks-hang.md @@ -1,5 +1,5 @@ --- -'@modelcontextprotocol/core': patch +'@modelcontextprotocol/core-internal': patch '@modelcontextprotocol/server': patch --- diff --git a/.changeset/codemod-core-routing.md b/.changeset/codemod-core-routing.md new file mode 100644 index 0000000000..624a847238 --- /dev/null +++ b/.changeset/codemod-core-routing.md @@ -0,0 +1,5 @@ +--- +'@modelcontextprotocol/codemod': minor +--- + +Route v1 `@modelcontextprotocol/sdk/types.js` schema imports to the new `@modelcontextprotocol/core` package. The `*Schema` Zod constants now migrate as a behavior-preserving import-path swap — `Schema.parse(value)` / `.safeParse(value)` keep working — while spec types, error classes, enums, and guards continue to resolve to `@modelcontextprotocol/client` / `@modelcontextprotocol/server` by context. A single `import { CallToolResult, CallToolResultSchema } from '.../types.js'` is split accordingly. The v1 OAuth/OpenID `*Schema` constants imported from `@modelcontextprotocol/sdk/shared/auth.js` are routed to `@modelcontextprotocol/core` the same way (their auth TYPES keep resolving to `client` / `server`). The previous `specSchemaAccess` transform (which rewrote `.parse()` into `specTypeSchemas.X['~standard'].validate(...)`) is removed. diff --git a/.changeset/codemod-infer-project-type.md b/.changeset/codemod-infer-project-type.md new file mode 100644 index 0000000000..1fa114c775 --- /dev/null +++ b/.changeset/codemod-infer-project-type.md @@ -0,0 +1,5 @@ +--- +'@modelcontextprotocol/codemod': patch +--- + +Infer client/server project type from source for v1 projects. A project being migrated still declares the single v1 `@modelcontextprotocol/sdk` dependency, so detecting the project type from `package.json` came back "unknown" and every file importing only shared protocol symbols defaulted to `@modelcontextprotocol/server` with an action-required warning. The codemod now scans the source for quoted `@modelcontextprotocol/sdk/client/…` and `…/server/…` import specifiers to infer the type (both → "both", one → that side, neither → "unknown"), routing shared symbols to the installed package and replacing the spurious warnings with at most an info note for genuinely ambiguous "both" projects. diff --git a/.changeset/codemod-task-handler-methods.md b/.changeset/codemod-task-handler-methods.md new file mode 100644 index 0000000000..5ed163d21a --- /dev/null +++ b/.changeset/codemod-task-handler-methods.md @@ -0,0 +1,5 @@ +--- +'@modelcontextprotocol/codemod': patch +--- + +Map the task request/notification schemas to their v2 method strings in the handler-registration transform. `setRequestHandler(GetTaskRequestSchema, …)`, `setNotificationHandler(TaskStatusNotificationSchema, …)`, and the other task handlers (`tasks/get`, `tasks/result`, `tasks/list`, `tasks/cancel`, `notifications/tasks/status`) now rewrite to the v2 two-argument method-string form instead of falling through to the generic "use the 3-argument form" manual-migration diagnostic. diff --git a/.changeset/custom-methods-minimal.md b/.changeset/custom-methods-minimal.md index f722f4504e..103332b166 100644 --- a/.changeset/custom-methods-minimal.md +++ b/.changeset/custom-methods-minimal.md @@ -1,5 +1,5 @@ --- -'@modelcontextprotocol/core': minor +'@modelcontextprotocol/core-internal': minor '@modelcontextprotocol/client': minor '@modelcontextprotocol/server': minor --- diff --git a/.changeset/extract-task-manager.md b/.changeset/extract-task-manager.md index 6a72182837..51ee9b305a 100644 --- a/.changeset/extract-task-manager.md +++ b/.changeset/extract-task-manager.md @@ -1,5 +1,5 @@ --- -"@modelcontextprotocol/core": minor +"@modelcontextprotocol/core-internal": minor "@modelcontextprotocol/client": minor "@modelcontextprotocol/server": minor --- diff --git a/.changeset/finish-sdkerror-capability.md b/.changeset/finish-sdkerror-capability.md index f9145a5058..8e8b0bca31 100644 --- a/.changeset/finish-sdkerror-capability.md +++ b/.changeset/finish-sdkerror-capability.md @@ -1,5 +1,5 @@ --- -'@modelcontextprotocol/core': patch +'@modelcontextprotocol/core-internal': patch '@modelcontextprotocol/client': patch '@modelcontextprotocol/server': patch --- diff --git a/.changeset/fix-abort-listener-leak.md b/.changeset/fix-abort-listener-leak.md index f1dd3163b9..0a87e559c7 100644 --- a/.changeset/fix-abort-listener-leak.md +++ b/.changeset/fix-abort-listener-leak.md @@ -1,5 +1,5 @@ --- -'@modelcontextprotocol/core': patch +'@modelcontextprotocol/core-internal': patch --- Consolidate per-request cleanup in `_requestWithSchema` into a single `.finally()` block. This fixes an abort signal listener leak (listeners accumulated when a caller reused one `AbortSignal` across requests) and two cases where `_responseHandlers` entries leaked on send-failure paths. diff --git a/.changeset/fix-task-session-isolation.md b/.changeset/fix-task-session-isolation.md index 7220673374..8f75b6004f 100644 --- a/.changeset/fix-task-session-isolation.md +++ b/.changeset/fix-task-session-isolation.md @@ -1,5 +1,5 @@ --- -'@modelcontextprotocol/core': patch +'@modelcontextprotocol/core-internal': patch --- Fix InMemoryTaskStore to enforce session isolation. Previously, sessionId was accepted but ignored on all TaskStore methods, allowing any session to enumerate, read, and mutate tasks created by other sessions. The store now persists sessionId at creation time and enforces ownership on all reads and writes. diff --git a/.changeset/fix-transport-exact-optional-property-types.md b/.changeset/fix-transport-exact-optional-property-types.md index c3187db8ad..57b4d00cd5 100644 --- a/.changeset/fix-transport-exact-optional-property-types.md +++ b/.changeset/fix-transport-exact-optional-property-types.md @@ -1,5 +1,5 @@ --- -'@modelcontextprotocol/core': patch +'@modelcontextprotocol/core-internal': patch --- Add explicit `| undefined` to optional properties on the `Transport` interface and `TransportSendOptions` (`onclose`, `onerror`, `onmessage`, `sessionId`, `setProtocolVersion`, `setSupportedProtocolVersions`, `onresumptiontoken`). diff --git a/.changeset/fix-unknown-tool-protocol-error.md b/.changeset/fix-unknown-tool-protocol-error.md index 086158b4b6..d96a628cae 100644 --- a/.changeset/fix-unknown-tool-protocol-error.md +++ b/.changeset/fix-unknown-tool-protocol-error.md @@ -1,5 +1,5 @@ --- -"@modelcontextprotocol/core": minor +"@modelcontextprotocol/core-internal": minor "@modelcontextprotocol/server": major --- diff --git a/.changeset/funky-baths-attack.md b/.changeset/funky-baths-attack.md index f65f1263c8..8f7e20f73b 100644 --- a/.changeset/funky-baths-attack.md +++ b/.changeset/funky-baths-attack.md @@ -2,7 +2,7 @@ '@modelcontextprotocol/node': patch '@modelcontextprotocol/test-integration': patch '@modelcontextprotocol/server': patch -'@modelcontextprotocol/core': patch +'@modelcontextprotocol/core-internal': patch --- remove deprecated .tool, .prompt, .resource method signatures diff --git a/.changeset/idjag-spec-type-export.md b/.changeset/idjag-spec-type-export.md new file mode 100644 index 0000000000..8fb5816036 --- /dev/null +++ b/.changeset/idjag-spec-type-export.md @@ -0,0 +1,6 @@ +--- +'@modelcontextprotocol/client': minor +'@modelcontextprotocol/server': minor +--- + +Add the v2 `IdJagTokenExchangeResponse` type to the public API and register its schema as an MCP spec type. `IdJagTokenExchangeResponse` is now exported from `@modelcontextprotocol/client` and `@modelcontextprotocol/server`, `'IdJagTokenExchangeResponse'` joins the `SpecTypeName` union, and `isSpecType.IdJagTokenExchangeResponse(value)` / `specTypeSchemas.IdJagTokenExchangeResponse` validate it by name — matching how the OAuth/OpenID auth schemas are already exposed. The Zod schema itself, `IdJagTokenExchangeResponseSchema`, is available from `@modelcontextprotocol/core`. diff --git a/.changeset/pre.json b/.changeset/pre.json index c4c3cf31a8..881a7cc3d2 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -11,60 +11,100 @@ "@modelcontextprotocol/examples-server-quickstart": "2.0.0-alpha.0", "@modelcontextprotocol/examples-shared": "2.0.0-alpha.0", "@modelcontextprotocol/client": "2.0.0-alpha.0", - "@modelcontextprotocol/core": "2.0.0-alpha.0", + "@modelcontextprotocol/core-internal": "2.0.0-alpha.0", "@modelcontextprotocol/express": "2.0.0-alpha.0", "@modelcontextprotocol/fastify": "2.0.0-alpha.0", "@modelcontextprotocol/hono": "2.0.0-alpha.0", "@modelcontextprotocol/node": "2.0.0-alpha.0", "@modelcontextprotocol/server": "2.0.0-alpha.0", "@modelcontextprotocol/server-legacy": "2.0.0-alpha.0", + "@modelcontextprotocol/core": "2.0.0-alpha.0", "@modelcontextprotocol/codemod": "2.0.0-alpha.0", "@modelcontextprotocol/test-conformance": "2.0.0-alpha.0", "@modelcontextprotocol/test-helpers": "2.0.0-alpha.0", - "@modelcontextprotocol/test-integration": "2.0.0-alpha.0" + "@modelcontextprotocol/test-integration": "2.0.0-alpha.0", + "@modelcontextprotocol/test-e2e": "2.0.0-alpha.0" }, "changesets": [ "abort-handlers-on-close", + "add-consumer-sse-e2e", + "add-core-public-package", + "add-e2e-test-suite", "add-fastify-middleware", "add-hono-peer-dep", "add-resource-size-field", + "add-sdk-http-error", + "add-server-legacy-package", + "bound-resumability-version-gates", "brave-lions-glow", "busy-rice-smoke", "busy-weeks-hang", + "cfworker-out-of-barrel", + "codemod-core-routing", + "codemod-infer-project-type", + "codemod-resolve-legacy-imports", + "codemod-streamablehttperror-sdkhttperror", + "codemod-task-handler-methods", + "custom-methods-minimal", "cyan-cycles-pump", + "draft-spec-non-sep-conformance", "drop-zod-peer-dep", + "export-inmemory-transport", "expose-auth-server-discovery", + "expose-icons-on-tools-and-prompts", + "express-resource-server-auth", "extract-task-manager", "fast-dragons-lead", "finish-sdkerror-capability", "fix-abort-listener-leak", + "fix-conformance-server-leak", "fix-oauth-5xx-discovery", "fix-onerror-callbacks", "fix-server-protocol-version", "fix-session-status-codes", "fix-stdio-epipe-crash", "fix-stdio-windows-hide", + "fix-streamable-close-reentrant", "fix-streamable-http-error-response", "fix-task-session-isolation", "fix-transport-exact-optional-property-types", "fix-unknown-tool-protocol-error", + "fix-validate-client-metadata-url", "funky-baths-attack", + "gentle-planets-rest", "heavy-walls-swim", + "hono-peer-optional", + "idjag-spec-type-export", + "legacy-module-resolution-types", "oauth-error-http200", + "odd-forks-enjoy", "quick-islands-occur", "reconnection-scheduler", + "register-rawshape-compat", "remove-websocket-transport", "respect-capability-negotiation", + "restore-task-wire-types", "rich-hounds-report", "schema-object-type-for-unions", + "sep-2577-deprecate-runtime-apis", + "sep-2663-tasks-removal", + "sep-414-trace-context-meta-keys", "shy-times-learn", + "spec-reference-types-2026-07-28", + "spec-type-schema", "spotty-cats-tickle", + "stdio-max-buffer-size", "stdio-skip-non-json", + "stdio-subpath-export", "support-standard-json-schema", "tame-camels-greet", "tender-snails-fold", "token-provider-composable-auth", "twelve-dodos-taste", - "use-scopes-supported-in-dcr" + "use-scopes-supported-in-dcr", + "workerd-shim-vendors-cfworker", + "wraphandler-hook", + "zod-json-schema-compat", + "zod-jsonschema-fallback" ] } diff --git a/.changeset/quick-islands-occur.md b/.changeset/quick-islands-occur.md index 2ec83908d1..b229b82c36 100644 --- a/.changeset/quick-islands-occur.md +++ b/.changeset/quick-islands-occur.md @@ -4,7 +4,7 @@ '@modelcontextprotocol/node': patch '@modelcontextprotocol/client': patch '@modelcontextprotocol/server': patch -'@modelcontextprotocol/core': patch +'@modelcontextprotocol/core-internal': patch --- remove npm references, use pnpm diff --git a/.changeset/register-rawshape-compat.md b/.changeset/register-rawshape-compat.md index 5f1f167848..84fccdd726 100644 --- a/.changeset/register-rawshape-compat.md +++ b/.changeset/register-rawshape-compat.md @@ -1,5 +1,5 @@ --- -'@modelcontextprotocol/core': patch +'@modelcontextprotocol/core-internal': patch '@modelcontextprotocol/server': patch --- diff --git a/.changeset/restore-task-wire-types.md b/.changeset/restore-task-wire-types.md index ebe9b5ddf5..05c0875364 100644 --- a/.changeset/restore-task-wire-types.md +++ b/.changeset/restore-task-wire-types.md @@ -1,5 +1,5 @@ --- -'@modelcontextprotocol/core': minor +'@modelcontextprotocol/core-internal': minor '@modelcontextprotocol/server': minor '@modelcontextprotocol/client': minor --- diff --git a/.changeset/rich-hounds-report.md b/.changeset/rich-hounds-report.md index d1736bf72c..b05e7c4656 100644 --- a/.changeset/rich-hounds-report.md +++ b/.changeset/rich-hounds-report.md @@ -4,7 +4,7 @@ '@modelcontextprotocol/node': patch '@modelcontextprotocol/client': patch '@modelcontextprotocol/server': patch -'@modelcontextprotocol/core': patch +'@modelcontextprotocol/core-internal': patch --- clean up package manager usage, all pnpm diff --git a/.changeset/schema-object-type-for-unions.md b/.changeset/schema-object-type-for-unions.md index 7749bb6c5c..d307f2af58 100644 --- a/.changeset/schema-object-type-for-unions.md +++ b/.changeset/schema-object-type-for-unions.md @@ -1,5 +1,5 @@ --- -'@modelcontextprotocol/core': patch +'@modelcontextprotocol/core-internal': patch --- Ensure `standardSchemaToJsonSchema` emits `type: "object"` at the root, fixing discriminated-union tool/prompt schemas that previously produced `{oneOf: [...]}` without the MCP-required top-level type. Also throws a clear error when given an explicitly non-object schema (e.g. `z.string()`). Fixes #1643. diff --git a/.changeset/sep-2577-deprecate-runtime-apis.md b/.changeset/sep-2577-deprecate-runtime-apis.md index 60afaaceb2..e042112d08 100644 --- a/.changeset/sep-2577-deprecate-runtime-apis.md +++ b/.changeset/sep-2577-deprecate-runtime-apis.md @@ -1,5 +1,5 @@ --- -'@modelcontextprotocol/core': patch +'@modelcontextprotocol/core-internal': patch '@modelcontextprotocol/server': patch '@modelcontextprotocol/client': patch --- diff --git a/.changeset/sep-2663-tasks-removal.md b/.changeset/sep-2663-tasks-removal.md index 51ece994a4..8316165b50 100644 --- a/.changeset/sep-2663-tasks-removal.md +++ b/.changeset/sep-2663-tasks-removal.md @@ -1,5 +1,5 @@ --- -'@modelcontextprotocol/core': major +'@modelcontextprotocol/core-internal': major '@modelcontextprotocol/server': major '@modelcontextprotocol/client': major --- diff --git a/.changeset/sep-414-trace-context-meta-keys.md b/.changeset/sep-414-trace-context-meta-keys.md index f8f21b63aa..1281c9dafb 100644 --- a/.changeset/sep-414-trace-context-meta-keys.md +++ b/.changeset/sep-414-trace-context-meta-keys.md @@ -1,5 +1,5 @@ --- -'@modelcontextprotocol/core': patch +'@modelcontextprotocol/core-internal': patch --- Add reserved trace context `_meta` key constants (`TRACEPARENT_META_KEY`, `TRACESTATE_META_KEY`, `BAGGAGE_META_KEY`) per SEP-414, plus docs and a passthrough regression test. The spec reserves the unprefixed `_meta` keys `traceparent`, `tracestate`, and `baggage` (W3C Trace Context / W3C Baggage formats) for distributed tracing; the SDK passes them through untouched. diff --git a/.changeset/shy-times-learn.md b/.changeset/shy-times-learn.md index 99617f8b7d..24d47206eb 100644 --- a/.changeset/shy-times-learn.md +++ b/.changeset/shy-times-learn.md @@ -2,7 +2,7 @@ '@modelcontextprotocol/node': patch '@modelcontextprotocol/test-integration': patch '@modelcontextprotocol/server': patch -'@modelcontextprotocol/core': patch +'@modelcontextprotocol/core-internal': patch --- deprecated .tool, .prompt, .resource method removal diff --git a/.changeset/spec-reference-types-2026-07-28.md b/.changeset/spec-reference-types-2026-07-28.md index df0101e05f..3c2f5025a0 100644 --- a/.changeset/spec-reference-types-2026-07-28.md +++ b/.changeset/spec-reference-types-2026-07-28.md @@ -1,5 +1,5 @@ --- -'@modelcontextprotocol/core': patch +'@modelcontextprotocol/core-internal': patch '@modelcontextprotocol/codemod': patch --- diff --git a/.changeset/stdio-max-buffer-size.md b/.changeset/stdio-max-buffer-size.md index a4b71e9dcd..33a8c42d6d 100644 --- a/.changeset/stdio-max-buffer-size.md +++ b/.changeset/stdio-max-buffer-size.md @@ -1,7 +1,7 @@ --- -'@modelcontextprotocol/core': patch +'@modelcontextprotocol/core-internal': patch '@modelcontextprotocol/client': patch '@modelcontextprotocol/server': patch --- -Add a configurable `maxBufferSize` (default 10 MB) to the stdio transports. When a single message would push the read buffer past the limit, the transport now emits an `onerror` and closes instead of growing the buffer unbounded. Configure via `new StdioClientTransport({ ..., maxBufferSize })` or `new StdioServerTransport(stdin, stdout, { maxBufferSize })`. The default is exported from `@modelcontextprotocol/core` as `STDIO_DEFAULT_MAX_BUFFER_SIZE`. +Add a configurable `maxBufferSize` (default 10 MB) to the stdio transports. When a single message would push the read buffer past the limit, the transport now emits an `onerror` and closes instead of growing the buffer unbounded. Configure via `new StdioClientTransport({ ..., maxBufferSize })` or `new StdioServerTransport(stdin, stdout, { maxBufferSize })`. The default is exported from `@modelcontextprotocol/core-internal` as `STDIO_DEFAULT_MAX_BUFFER_SIZE`. diff --git a/.changeset/stdio-skip-non-json.md b/.changeset/stdio-skip-non-json.md index d20b740c9c..7c70e412f3 100644 --- a/.changeset/stdio-skip-non-json.md +++ b/.changeset/stdio-skip-non-json.md @@ -1,5 +1,5 @@ --- -'@modelcontextprotocol/core': patch +'@modelcontextprotocol/core-internal': patch --- `ReadBuffer.readMessage()` now silently skips non-JSON lines instead of throwing `SyntaxError`. This prevents noisy `onerror` callbacks when hot-reload tools (tsx, nodemon) write debug output like "Gracefully restarting..." to stdout. Lines that parse as JSON but fail JSONRPC schema validation still throw. diff --git a/.changeset/support-standard-json-schema.md b/.changeset/support-standard-json-schema.md index 792e15f1f7..38a9998893 100644 --- a/.changeset/support-standard-json-schema.md +++ b/.changeset/support-standard-json-schema.md @@ -1,5 +1,5 @@ --- -'@modelcontextprotocol/core': minor +'@modelcontextprotocol/core-internal': minor '@modelcontextprotocol/server': minor '@modelcontextprotocol/client': minor --- @@ -30,5 +30,5 @@ server.registerTool('greet', { **Breaking changes:** - `experimental.tasks.getTaskResult()` no longer accepts a `resultSchema` parameter. Returns `GetTaskPayloadResult` (a loose `Result`); cast to the expected type at the call site. -- Removed unused exports from `@modelcontextprotocol/core`: `SchemaInput`, `schemaToJson`, `parseSchemaAsync`, `getSchemaShape`, `getSchemaDescription`, `isOptionalSchema`, `unwrapOptionalSchema`. Use the new `standardSchemaToJsonSchema` and `validateStandardSchema` instead. +- Removed unused exports from `@modelcontextprotocol/core-internal`: `SchemaInput`, `schemaToJson`, `parseSchemaAsync`, `getSchemaShape`, `getSchemaDescription`, `isOptionalSchema`, `unwrapOptionalSchema`. Use the new `standardSchemaToJsonSchema` and `validateStandardSchema` instead. - `completable()` remains Zod-specific (it relies on Zod's `.shape` introspection). diff --git a/.changeset/workerd-shim-vendors-cfworker.md b/.changeset/workerd-shim-vendors-cfworker.md index 9759e73009..38c8222913 100644 --- a/.changeset/workerd-shim-vendors-cfworker.md +++ b/.changeset/workerd-shim-vendors-cfworker.md @@ -1,5 +1,5 @@ --- -'@modelcontextprotocol/core': minor +'@modelcontextprotocol/core-internal': minor '@modelcontextprotocol/client': patch '@modelcontextprotocol/server': patch --- diff --git a/.changeset/wraphandler-hook.md b/.changeset/wraphandler-hook.md index 935f576588..114ebf0332 100644 --- a/.changeset/wraphandler-hook.md +++ b/.changeset/wraphandler-hook.md @@ -1,5 +1,5 @@ --- -'@modelcontextprotocol/core': patch +'@modelcontextprotocol/core-internal': patch '@modelcontextprotocol/client': patch '@modelcontextprotocol/server': patch --- diff --git a/.changeset/zod-json-schema-compat.md b/.changeset/zod-json-schema-compat.md index 5ca1470e82..b3180fb06b 100644 --- a/.changeset/zod-json-schema-compat.md +++ b/.changeset/zod-json-schema-compat.md @@ -1,5 +1,5 @@ --- -'@modelcontextprotocol/core': patch +'@modelcontextprotocol/core-internal': patch --- Allow additional JSON Schema properties in elicitInput's requestedSchema type by adding .catchall(z.unknown()), matching the pattern used by inputSchema. This fixes type incompatibility when using Zod v4's .toJSONSchema() output which includes extra properties like $schema and additionalProperties. diff --git a/.changeset/zod-jsonschema-fallback.md b/.changeset/zod-jsonschema-fallback.md index e2936cf568..a02662da12 100644 --- a/.changeset/zod-jsonschema-fallback.md +++ b/.changeset/zod-jsonschema-fallback.md @@ -1,5 +1,5 @@ --- -'@modelcontextprotocol/core': patch +'@modelcontextprotocol/core-internal': patch '@modelcontextprotocol/server': patch '@modelcontextprotocol/client': patch --- diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6d184c9867..829b634bdc 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -39,5 +39,5 @@ jobs: - name: Publish preview packages run: - pnpm dlx pkg-pr-new publish --packageManager=npm --pnpm './packages/server' './packages/server-legacy' './packages/client' + pnpm dlx pkg-pr-new publish --packageManager=npm --pnpm './packages/core' './packages/server' './packages/server-legacy' './packages/client' './packages/codemod' './packages/middleware/express' './packages/middleware/fastify' './packages/middleware/hono' './packages/middleware/node' diff --git a/.github/workflows/update-spec-types.yml b/.github/workflows/update-spec-types.yml index 482fb04213..bd832bf691 100644 --- a/.github/workflows/update-spec-types.yml +++ b/.github/workflows/update-spec-types.yml @@ -39,11 +39,11 @@ jobs: - name: Check for changes id: check_changes run: | - if git diff --quiet packages/core/src/types/spec.types.2026-07-28.ts; then + if git diff --quiet packages/core-internal/src/types/spec.types.2026-07-28.ts; then echo "has_changes=false" >> $GITHUB_OUTPUT else echo "has_changes=true" >> $GITHUB_OUTPUT - LATEST_SHA=$(grep "Last updated from commit:" packages/core/src/types/spec.types.2026-07-28.ts | cut -d: -f2 | tr -d ' ') + LATEST_SHA=$(grep "Last updated from commit:" packages/core-internal/src/types/spec.types.2026-07-28.ts | cut -d: -f2 | tr -d ' ') echo "sha=$LATEST_SHA" >> $GITHUB_OUTPUT fi @@ -59,12 +59,12 @@ jobs: git config user.email "github-actions[bot]@users.noreply.github.com" git checkout -B update-spec-types - git add packages/core/src/types/spec.types.2026-07-28.ts + git add packages/core-internal/src/types/spec.types.2026-07-28.ts git commit -m "chore: update spec.types.2026-07-28.ts from upstream" git push -f --no-verify origin update-spec-types # Create PR if it doesn't exist, or update if it does - PR_BODY="This PR updates \`packages/core/src/types/spec.types.2026-07-28.ts\` from the Model Context Protocol specification. + PR_BODY="This PR updates \`packages/core-internal/src/types/spec.types.2026-07-28.ts\` from the Model Context Protocol specification. Source file: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/${{ steps.check_changes.outputs.sha }}/schema/draft/schema.ts diff --git a/CLAUDE.md b/CLAUDE.md index 8876b0bfb1..9d792c3280 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,10 +16,10 @@ pnpm check:all # typecheck + lint across all packages # Run a single package script (examples) # Run a single package script from the repo root with pnpm filter -pnpm --filter @modelcontextprotocol/core test # vitest run (core) -pnpm --filter @modelcontextprotocol/core test:watch # vitest (watch) -pnpm --filter @modelcontextprotocol/core test -- path/to/file.test.ts -pnpm --filter @modelcontextprotocol/core test -- -t "test name" +pnpm --filter @modelcontextprotocol/core-internal test # vitest run (core) +pnpm --filter @modelcontextprotocol/core-internal test:watch # vitest (watch) +pnpm --filter @modelcontextprotocol/core-internal test -- path/to/file.test.ts +pnpm --filter @modelcontextprotocol/core-internal test -- -t "test name" ``` ## Breaking Changes @@ -53,9 +53,9 @@ Run `pnpm sync:snippets` to sync example content into JSDoc comments and markdow The SDK is organized into three main layers: -1. **Types Layer** (`packages/core/src/types/types.ts`) - Protocol types generated from the MCP specification. All JSON-RPC message types, schemas, and protocol constants are defined here using Zod v4. +1. **Types Layer** (`packages/core-internal/src/types/types.ts`) - Protocol types generated from the MCP specification. All JSON-RPC message types, schemas, and protocol constants are defined here using Zod v4. -2. **Protocol Layer** (`packages/core/src/shared/protocol.ts`) - The abstract `Protocol` class that handles JSON-RPC message routing, request/response correlation, capability negotiation, and transport management. Both `Client` and `Server` extend this class. +2. **Protocol Layer** (`packages/core-internal/src/shared/protocol.ts`) - The abstract `Protocol` class that handles JSON-RPC message routing, request/response correlation, capability negotiation, and transport management. Both `Client` and `Server` extend this class. 3. **High-Level APIs**: - `Client` (`packages/client/src/client/client.ts`) - Client implementation extending Protocol with typed methods for MCP operations @@ -64,21 +64,22 @@ The SDK is organized into three main layers: ### Public API Exports -The SDK has a two-layer export structure to separate internal code from the public API: +The SDK separates internal code from the public API surface: -- **`@modelcontextprotocol/core`** (main entry, `packages/core/src/index.ts`) — Internal barrel. Exports everything (including Zod schemas, Protocol class, stdio utils). Only consumed by sibling packages within the monorepo (`private: true`). -- **`@modelcontextprotocol/core/public`** (`packages/core/src/exports/public/index.ts`) — Curated public API. Exports only TypeScript types, error classes, constants, and guards. Re-exported by client and server packages. -- **`@modelcontextprotocol/client`** and **`@modelcontextprotocol/server`** (`packages/*/src/index.ts`) — Final public surface. Package-specific exports (named explicitly) plus re-exports from `core/public`. +- **`@modelcontextprotocol/core-internal`** (main entry, `packages/core-internal/src/index.ts`) — Internal barrel. Exports everything (including Zod schemas, Protocol class, stdio utils). Only consumed by sibling packages within the monorepo (`private: true`). +- **`@modelcontextprotocol/core-internal/public`** (`packages/core-internal/src/exports/public/index.ts`) — Curated public API. Exports only TypeScript types, error classes, constants, and guards. Re-exported by client and server packages. +- **`@modelcontextprotocol/client`** and **`@modelcontextprotocol/server`** (`packages/*/src/index.ts`) — Final public surface. Package-specific exports (named explicitly) plus re-exports from `core-internal/public`. +- **`@modelcontextprotocol/core`** (`packages/core/src/index.ts`) — Public Zod-schema package. Re-exports **only** the `*Schema` Zod constants (MCP spec + OAuth/OpenID), bundled from `core-internal` at build time. The published home for raw runtime validation (`CallToolResultSchema.parse(...)`); runtime-neutral (`zod` is its only dependency). Not consumed by the sibling packages — `client`/`server` keep their own bundled schema copies and stay Zod-free in their public surface. When modifying exports: -- Use explicit named exports, not `export *`, in package `index.ts` files and `core/public`. +- Use explicit named exports, not `export *`, in package `index.ts` files and `core-internal/public`. - Adding a symbol to a package `index.ts` makes it public API — do so intentionally. -- Internal helpers should stay in the core internal barrel and not be added to `core/public` or package index files. +- Internal helpers should stay in the core internal barrel and not be added to `core-internal/public` or package index files. - The package root entry must stay runtime-neutral so browser and Cloudflare Workers bundlers can consume it. Exports whose module graph transitively touches unpolyfillable Node builtins (`node:child_process`, `node:net`, `cross-spawn`, etc.) must live at a named subpath export (e.g. `./stdio`) and be covered by a `barrelClean` test in that package. ### Transport System -Transports (`packages/core/src/shared/transport.ts`) provide the communication layer: +Transports (`packages/core-internal/src/shared/transport.ts`) provide the communication layer: - **Streamable HTTP** (`packages/server/src/server/streamableHttp.ts`, `packages/client/src/client/streamableHttp.ts`) - Recommended transport for remote servers, supports SSE for streaming - **SSE** (`packages/server/src/server/sse.ts`, `packages/client/src/client/sse.ts`) - Legacy HTTP+SSE transport for backwards compatibility @@ -110,11 +111,11 @@ Located in `packages/*/src/experimental/`. Currently empty. The SDK uses `zod/v4` internally. Schema utilities live in: -- `packages/core/src/util/schema.ts` - AnySchema alias and helpers for inspecting Zod objects +- `packages/core-internal/src/util/schema.ts` - AnySchema alias and helpers for inspecting Zod objects ### Validation -Pluggable JSON Schema validation (`packages/core/src/validators/`): +Pluggable JSON Schema validation (`packages/core-internal/src/validators/`): - `ajvProvider.ts` - Default Ajv-based validator - `cfWorkerProvider.ts` - Cloudflare Workers-compatible alternative diff --git a/docs/migration-SKILL.md b/docs/migration-SKILL.md index aef327622c..73a2e4a0b9 100644 --- a/docs/migration-SKILL.md +++ b/docs/migration-SKILL.md @@ -1,6 +1,6 @@ --- name: migrate-v1-to-v2 -description: Migrate MCP TypeScript SDK code from v1 (@modelcontextprotocol/sdk) to v2 (@modelcontextprotocol/core, /client, /server). Use when a user asks to migrate, upgrade, or port their MCP TypeScript code from v1 to v2. +description: Migrate MCP TypeScript SDK code from v1 (@modelcontextprotocol/sdk) to v2 (@modelcontextprotocol/client, /server, /core). Use when a user asks to migrate, upgrade, or port their MCP TypeScript code from v1 to v2. --- # MCP TypeScript SDK: v1 → v2 Migration @@ -20,15 +20,17 @@ Remove the old package and install only what you need: npm uninstall @modelcontextprotocol/sdk ``` -| You need | Install | -| --------------------- | ------------------------------------------------------------------------ | -| Client only | `npm install @modelcontextprotocol/client` | -| Server only | `npm install @modelcontextprotocol/server` | -| Server + Node.js HTTP | `npm install @modelcontextprotocol/server @modelcontextprotocol/node` | -| Server + Express | `npm install @modelcontextprotocol/server @modelcontextprotocol/express` | -| Server + Hono | `npm install @modelcontextprotocol/server @modelcontextprotocol/hono` | +| You need | Install | +| --------------------------- | ------------------------------------------------------------------------ | +| Client only | `npm install @modelcontextprotocol/client` | +| Server only | `npm install @modelcontextprotocol/server` | +| Server + Node.js HTTP | `npm install @modelcontextprotocol/server @modelcontextprotocol/node` | +| Server + Express | `npm install @modelcontextprotocol/server @modelcontextprotocol/express` | +| Server + Hono | `npm install @modelcontextprotocol/server @modelcontextprotocol/hono` | +| Raw Zod `*Schema` constants | `npm install @modelcontextprotocol/core` | -`@modelcontextprotocol/core` is installed automatically as a dependency. +`@modelcontextprotocol/client` and `@modelcontextprotocol/server` are self-contained — the shared types, protocol layer, and transports are bundled in and re-exported, so there is no separate runtime package to install for them. Install `@modelcontextprotocol/core` only if you +import the raw Zod `*Schema` constants directly (see §11). ## 3. Import Mapping @@ -59,18 +61,18 @@ Replace all `@modelcontextprotocol/sdk/...` imports using this table. ### Types / shared imports -| v1 import path | v2 package | -| ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `@modelcontextprotocol/sdk/types.js` | `@modelcontextprotocol/client` or `@modelcontextprotocol/server` | -| `@modelcontextprotocol/sdk/shared/protocol.js` | `@modelcontextprotocol/client` or `@modelcontextprotocol/server` | -| `@modelcontextprotocol/sdk/shared/transport.js` | `@modelcontextprotocol/client` or `@modelcontextprotocol/server` | -| `@modelcontextprotocol/sdk/shared/uriTemplate.js` | `@modelcontextprotocol/client` or `@modelcontextprotocol/server` | -| `@modelcontextprotocol/sdk/shared/auth.js` | `@modelcontextprotocol/client` or `@modelcontextprotocol/server` | -| `@modelcontextprotocol/sdk/shared/stdio.js` | `@modelcontextprotocol/client` or `@modelcontextprotocol/server` (`ReadBuffer`, `serializeMessage`, `deserializeMessage` are in the root barrel; the `./stdio` subpath only has the transport class) | +| v1 import path | v2 package | +| ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `@modelcontextprotocol/sdk/types.js` | Types / error classes / enums / guards → `@modelcontextprotocol/client` or `@modelcontextprotocol/server`; Zod `*Schema` constants → `@modelcontextprotocol/core` | +| `@modelcontextprotocol/sdk/shared/protocol.js` | `@modelcontextprotocol/client` or `@modelcontextprotocol/server` | +| `@modelcontextprotocol/sdk/shared/transport.js` | `@modelcontextprotocol/client` or `@modelcontextprotocol/server` | +| `@modelcontextprotocol/sdk/shared/uriTemplate.js` | `@modelcontextprotocol/client` or `@modelcontextprotocol/server` | +| `@modelcontextprotocol/sdk/shared/auth.js` | Types / classes → `@modelcontextprotocol/client` or `@modelcontextprotocol/server`; OAuth/OpenID Zod `*Schema` constants (e.g. `OAuthTokensSchema`, `OAuthMetadataSchema`) → `@modelcontextprotocol/core` | +| `@modelcontextprotocol/sdk/shared/stdio.js` | `@modelcontextprotocol/client` or `@modelcontextprotocol/server` (`ReadBuffer`, `serializeMessage`, `deserializeMessage` are in the root barrel; the `./stdio` subpath only has the transport class) | Notes: -- `@modelcontextprotocol/client` and `@modelcontextprotocol/server` both re-export shared types from `@modelcontextprotocol/core`, so import from whichever package you already depend on. Do not import from `@modelcontextprotocol/core` directly — it is an internal package. +- `@modelcontextprotocol/client` and `@modelcontextprotocol/server` both re-export shared types from `@modelcontextprotocol/core-internal`, so import from whichever package you already depend on. Do not import from `@modelcontextprotocol/core-internal` directly — it is an internal package. - When multiple v1 imports map to the same v2 package, consolidate them into a single import statement. ## 4. Renamed Symbols @@ -81,25 +83,28 @@ Notes: ## 5. Removed / Renamed Type Aliases and Symbols -| v1 (removed) | v2 (replacement) | -| ---------------------------------------- | --------------------------------------------------------------------------------------------------------------- | -| `JSONRPCError` | `JSONRPCErrorResponse` | -| `JSONRPCErrorSchema` | `JSONRPCErrorResponseSchema` | -| `isJSONRPCError` | `isJSONRPCErrorResponse` | -| `isJSONRPCResponse` (deprecated in v1) | `isJSONRPCResultResponse` (**not** v2's new `isJSONRPCResponse`, which correctly matches both result and error) | -| `ResourceReference` | `ResourceTemplateReference` | -| `ResourceReferenceSchema` | `ResourceTemplateReferenceSchema` | -| `IsomorphicHeaders` | REMOVED (use Web Standard `Headers`) | -| `AuthInfo` (from `server/auth/types.js`) | `AuthInfo` (now re-exported by `@modelcontextprotocol/client` and `@modelcontextprotocol/server`) | -| `McpError` | `ProtocolError` | -| `ErrorCode` | `ProtocolErrorCode` | -| `ErrorCode.RequestTimeout` | `SdkErrorCode.RequestTimeout` | -| `ErrorCode.ConnectionClosed` | `SdkErrorCode.ConnectionClosed` | -| `StreamableHTTPError` | REMOVED (use `SdkHttpError` with `SdkErrorCode.ClientHttp*`) | -| `WebSocketClientTransport` | REMOVED (use `StreamableHTTPClientTransport` or `StdioClientTransport`) | - -All other **type** symbols from `@modelcontextprotocol/sdk/types.js` retain their original names. **Zod schemas** (e.g., `CallToolResultSchema`, `ListToolsResultSchema`) are no longer part of the public API — they are internal to the SDK. For runtime validation, use -`isSpecType.TypeName(value)` (e.g., `isSpecType.CallToolResult(v)`) or `specTypeSchemas.TypeName` for the `StandardSchemaV1Sync` validator object. The keys are typed as `SpecTypeName`, a literal union of all spec type names. +| v1 (removed) | v2 (replacement) | +| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JSONRPCError` | `JSONRPCErrorResponse` | +| `JSONRPCErrorSchema` | `JSONRPCErrorResponseSchema` | +| `isJSONRPCError` | `isJSONRPCErrorResponse` | +| `isJSONRPCResponse` (deprecated in v1) | `isJSONRPCResultResponse` (**not** v2's new `isJSONRPCResponse`, which correctly matches both result and error) | +| `JSONRPCResponseSchema` (result-only in v1) | `JSONRPCResultResponseSchema` (from `@modelcontextprotocol/core`; **not** v2's new `JSONRPCResponseSchema`, a `z.union` that also accepts error responses) | +| `JSONRPCResponse` (type, result-only in v1) | `JSONRPCResultResponse` (**not** v2's new `JSONRPCResponse` type, which is the result\|error union) | +| `ResourceReference` | `ResourceTemplateReference` | +| `ResourceReferenceSchema` | `ResourceTemplateReferenceSchema` | +| `IsomorphicHeaders` | REMOVED (use Web Standard `Headers`) | +| `AuthInfo` (from `server/auth/types.js`) | `AuthInfo` (now re-exported by `@modelcontextprotocol/client` and `@modelcontextprotocol/server`) | +| `McpError` | `ProtocolError` | +| `ErrorCode` | `ProtocolErrorCode` | +| `ErrorCode.RequestTimeout` | `SdkErrorCode.RequestTimeout` | +| `ErrorCode.ConnectionClosed` | `SdkErrorCode.ConnectionClosed` | +| `StreamableHTTPError` | REMOVED (use `SdkHttpError` with `SdkErrorCode.ClientHttp*`) | +| `WebSocketClientTransport` | REMOVED (use `StreamableHTTPClientTransport` or `StdioClientTransport`) | + +All other **type** symbols from `@modelcontextprotocol/sdk/types.js` retain their original names — import them from `@modelcontextprotocol/client` or `@modelcontextprotocol/server`. The **Zod schemas** (e.g., `CallToolResultSchema`, `ListToolsResultSchema`) move to +`@modelcontextprotocol/core`; `Schema.parse(value)` / `.safeParse(value)` keep working unchanged (the codemod rewrites the import path). To validate **without** depending on Zod, use `isSpecType.TypeName(value)` (e.g., `isSpecType.CallToolResult(v)`) or +`specTypeSchemas.TypeName` (a `StandardSchemaV1Sync` validator) from `@modelcontextprotocol/client` / `@modelcontextprotocol/server`; the keys are typed as `SpecTypeName`, a literal union of all spec type names. ### Error class changes @@ -302,7 +307,7 @@ Note: the third argument (`metadata`) is required — pass `{}` if no metadata. ### Removed core exports -| Removed from `@modelcontextprotocol/core` | Replacement | +| Removed from `@modelcontextprotocol/core-internal` | Replacement | | ------------------------------------------------------------------------------------ | ----------------------------------------- | | `schemaToJson(schema)` | `standardSchemaToJsonSchema(schema)` | | `parseSchemaAsync(schema, data)` | `validateStandardSchema(schema, data)` | @@ -501,7 +506,8 @@ The 2025-11 task side-channel through `Protocol` is removed (was always `@experi `TaskStore` / `InMemoryTaskStore` / `CreateTaskOptions` / `isTerminal` (storage layer) are also removed; they will return with the SEP-2663 server-directed plugin. -NOT removed (wire surface, kept for 2025-11-25 interop): task Zod schemas + inferred types (`Task`, `TaskStatus`, `TaskMetadata`, `RelatedTaskMetadata`, `CreateTaskResult`, `GetTask*`, `ListTasks*`, `CancelTask*`, `TaskStatusNotification*`, `TaskAugmentedRequestParams`), task members of the request/result/notification unions, the `tasks` capability key, `isTaskAugmentedRequestParams`, `RELATED_TASK_META_KEY`. Inbound `tasks/*` requests → `-32601`. +NOT removed (wire surface, kept for 2025-11-25 interop): task Zod schemas + inferred types (`Task`, `TaskStatus`, `TaskMetadata`, `RelatedTaskMetadata`, `CreateTaskResult`, `GetTask*`, `ListTasks*`, `CancelTask*`, `TaskStatusNotification*`, `TaskAugmentedRequestParams`), task +members of the request/result/notification unions, the `tasks` capability key, `isTaskAugmentedRequestParams`, `RELATED_TASK_META_KEY`. Inbound `tasks/*` requests → `-32601`. ## 13. Behavioral Changes @@ -513,8 +519,10 @@ NOT removed (wire surface, kept for 2025-11-25 interop): task Zod schemas + infe No code changes required; these are wire-behavior notes: -- Resumability behavior (SSE priming events, `closeSSEStream` / `closeStandaloneSSEStream` callbacks) is only enabled for protocol versions in the transport's supported-versions list that are `>= 2025-11-25`. Unknown future version strings in an `initialize` request body no longer enable it. Behavior for all currently supported protocol versions is unchanged. -- Session-ID mismatch still responds `404 Not Found` with JSON-RPC error code `-32001` (`Session not found`), unchanged from v1. This `-32001` usage is an SDK convention, not a spec-assigned code, and may be re-derived as 2026 protocol revision error handling is adopted — migrated client code should key off the HTTP `404` status, not the `-32001` code. +- Resumability behavior (SSE priming events, `closeSSEStream` / `closeStandaloneSSEStream` callbacks) is only enabled for protocol versions in the transport's supported-versions list that are `>= 2025-11-25`. Unknown future version strings in an `initialize` request body no + longer enable it. Behavior for all currently supported protocol versions is unchanged. +- Session-ID mismatch still responds `404 Not Found` with JSON-RPC error code `-32001` (`Session not found`), unchanged from v1. This `-32001` usage is an SDK convention, not a spec-assigned code, and may be re-derived as 2026 protocol revision error handling is adopted — + migrated client code should key off the HTTP `404` status, not the `-32001` code. ## 14. Runtime-Specific JSON Schema Validators (Enhancement) @@ -558,4 +566,6 @@ Validator behavior: 8. If using server SSE transport, migrate to Streamable HTTP 9. If using server auth from the SDK: RS helpers (`requireBearerAuth`, `mcpAuthMetadataRouter`, `OAuthTokenVerifier`) → `@modelcontextprotocol/express`; AS helpers → `@modelcontextprotocol/server-legacy/auth` (deprecated); migrate AS to external IdP/OAuth library 10. If relying on `listTools()`/`listPrompts()`/etc. throwing on missing capabilities, set `enforceStrictCapabilities: true` -11. Verify: build with `tsc` / run tests +11. Format the changed files with the project's formatter (`prettier --write`, `eslint --fix`, or `biome format --write`) — edits are not reformatted automatically, and the wrapped schemas (step 5) and rewritten `setRequestHandler` method strings (section 9) frequently need it to + satisfy lint +12. Verify: build with `tsc` / run tests diff --git a/docs/migration.md b/docs/migration.md index 576f6c5ce4..1b6062225c 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -4,20 +4,26 @@ This guide covers the breaking changes introduced in v2 of the MCP TypeScript SD ## Overview -Version 2 of the MCP TypeScript SDK introduces several breaking changes to improve modularity, reduce dependency bloat, and provide a cleaner API surface. The biggest change is the split from a single `@modelcontextprotocol/sdk` package into separate `@modelcontextprotocol/core`, -`@modelcontextprotocol/client`, and `@modelcontextprotocol/server` packages. +Version 2 of the MCP TypeScript SDK introduces several breaking changes to improve modularity, reduce dependency bloat, and provide a cleaner API surface. The biggest change is the split from a single `@modelcontextprotocol/sdk` package into focused `@modelcontextprotocol/client` +and `@modelcontextprotocol/server` packages, with the shared Zod schema constants published separately as `@modelcontextprotocol/core`. + +> **Formatting:** The `@modelcontextprotocol/codemod` package automates most of the mechanical changes below, but it rewrites your code's AST without reformatting it — wrapped schemas and generated handler method strings may not match your project's style. After migrating (with +> the codemod or by hand), run your formatter on the changed files — for example `prettier --write`, `eslint --fix`, or `biome format --write` — and review the diff. ## Breaking Changes ### Package split (monorepo) -The single `@modelcontextprotocol/sdk` package has been split into three packages: +The single `@modelcontextprotocol/sdk` package has been split into focused packages: + +| v1 | v2 | +| --------------------------- | ----------------------------------------------------------------------------- | +| `@modelcontextprotocol/sdk` | `@modelcontextprotocol/client` (client implementation) | +| | `@modelcontextprotocol/server` (server implementation) | +| | `@modelcontextprotocol/core` (Zod `*Schema` constants — install only if used) | -| v1 | v2 | -| --------------------------- | ---------------------------------------------------------- | -| `@modelcontextprotocol/sdk` | `@modelcontextprotocol/core` (types, protocol, transports) | -| | `@modelcontextprotocol/client` (client implementation) | -| | `@modelcontextprotocol/server` (server implementation) | +`@modelcontextprotocol/client` and `@modelcontextprotocol/server` are self-contained: the shared protocol types, error classes, guards, and transports are bundled in and re-exported, so most projects install only one of them. `@modelcontextprotocol/core` is a separate, +lightweight package needed only if you import the raw Zod `*Schema` constants directly (see [Zod schema constants](#protocolrequest-ctxmcpreqsend-and-clientcalltool-no-longer-require-a-schema-parameter-for-spec-methods) below). Remove the old package and install only the packages you need: @@ -30,7 +36,8 @@ npm install @modelcontextprotocol/client # If you only need a server npm install @modelcontextprotocol/server -# Both packages depend on @modelcontextprotocol/core automatically +# Only if you import the raw Zod *Schema constants directly +npm install @modelcontextprotocol/core ``` Update your imports accordingly: @@ -59,7 +66,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/server/stdio'; import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node'; ``` -Note: `@modelcontextprotocol/client` and `@modelcontextprotocol/server` both re-export shared types from `@modelcontextprotocol/core`, so you can import types and error classes from whichever package you already depend on. Do not import from `@modelcontextprotocol/core` directly +Note: `@modelcontextprotocol/client` and `@modelcontextprotocol/server` both re-export shared types from `@modelcontextprotocol/core-internal`, so you can import types and error classes from whichever package you already depend on. Do not import from `@modelcontextprotocol/core-internal` directly — it is an internal package. ### Dropped Node.js 18 and CommonJS @@ -304,7 +311,7 @@ This applies to: - `outputSchema` in `registerTool()` - `argsSchema` in `registerPrompt()` -**Removed Zod-specific helpers** from `@modelcontextprotocol/core` (use Standard Schema equivalents): +**Removed Zod-specific helpers** from `@modelcontextprotocol/core-internal` (use Standard Schema equivalents): | Removed | Replacement | | ------------------------------------------------------------------------------------ | ----------------------------------------------------------------- | @@ -336,11 +343,11 @@ Note: the v2 signature takes a plain `string[]` instead of an options object. ### Resumability gating for unknown protocol versions (Streamable HTTP server) -The server-side Streamable HTTP transport enables resumability behavior introduced with protocol version `2025-11-25` — SSE priming events and the `closeSSEStream` / `closeStandaloneSSEStream` callbacks — based on the client's protocol version. Previously this was an -open-ended `protocolVersion >= '2025-11-25'` comparison, so an unrecognized future version string in an `initialize` request body (which, unlike the `MCP-Protocol-Version` header, is not validated against the supported-versions list) silently enabled the behavior. +The server-side Streamable HTTP transport enables resumability behavior introduced with protocol version `2025-11-25` — SSE priming events and the `closeSSEStream` / `closeStandaloneSSEStream` callbacks — based on the client's protocol version. Previously this was an open-ended +`protocolVersion >= '2025-11-25'` comparison, so an unrecognized future version string in an `initialize` request body (which, unlike the `MCP-Protocol-Version` header, is not validated against the supported-versions list) silently enabled the behavior. -The check is now bounded: the version must be one of the transport's supported protocol versions (after `connect()`, the server's `supportedProtocolVersions`) **and** at least `2025-11-25`. Behavior for all currently supported protocol versions (`2024-10-07` through -`2025-11-25`) is unchanged. Clients claiming an unknown future protocol version in the initialize body are now treated like clients without empty-SSE-data support: no priming event is sent and no early-close callbacks are provided. +The check is now bounded: the version must be one of the transport's supported protocol versions (after `connect()`, the server's `supportedProtocolVersions`) **and** at least `2025-11-25`. Behavior for all currently supported protocol versions (`2024-10-07` through `2025-11-25`) +is unchanged. Clients claiming an unknown future protocol version in the initialize body are now treated like clients without empty-SSE-data support: no priming event is sent and no early-close callbacks are provided. ### `setRequestHandler` and `setNotificationHandler` use method strings @@ -514,29 +521,41 @@ The return type is now inferred from the method name via `ResultTypeMap`. For ex For **custom (non-spec)** methods, keep the result-schema argument — see [Sending custom-method requests](#sending-custom-method-requests). Only drop the schema when calling a spec method. -If you were using `CallToolResultSchema` (or any `*Schema` constant) for **runtime validation** (not just in `request()`/`callTool()` calls), use `isSpecType` or `specTypeSchemas`: +If you were using `CallToolResultSchema` (or any `*Schema` constant) for **runtime validation** (not just in `request()`/`callTool()` calls), import the schema from `@modelcontextprotocol/core`. Your `.parse()` / `.safeParse()` calls keep working unchanged — only the import +path changes: ```typescript -// v1: runtime validation with Zod schema +// v1 import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js'; if (CallToolResultSchema.safeParse(value).success) { /* ... */ } -// v2: keyed type predicate +// v2 — same code, new import path +import { CallToolResultSchema } from '@modelcontextprotocol/core'; +if (CallToolResultSchema.safeParse(value).success) { + /* ... */ +} +``` + +`@modelcontextprotocol/core` is the canonical home for the Zod schema constants — both the spec schemas and the OAuth/OpenID schemas (e.g. `OAuthTokensSchema`, `OAuthMetadataSchema`) that v1 exported from `@modelcontextprotocol/sdk/shared/auth.js`. +`@modelcontextprotocol/server` and `@modelcontextprotocol/client` keep a Zod-free public surface (they export the corresponding TypeScript types, e.g. `OAuthTokens`), so the raw `*Schema` constants live in `core`. (The codemod rewrites these imports for you.) + +If you'd rather **not** depend on Zod, `@modelcontextprotocol/client` and `@modelcontextprotocol/server` also expose Zod-free validators keyed by `SpecTypeName` — a literal union of every named spec type, so you get autocomplete and a compile error on typos: + +```typescript import { isSpecType } from '@modelcontextprotocol/client'; if (isSpecType.CallToolResult(value)) { /* ... */ } const blocks = mixed.filter(isSpecType.ContentBlock); -// v2: or get the StandardSchemaV1Sync validator object directly +// or the StandardSchemaV1Sync validator object directly import { specTypeSchemas } from '@modelcontextprotocol/client'; const result = specTypeSchemas.CallToolResult['~standard'].validate(value); ``` -`isSpecType` and `specTypeSchemas` are keyed by `SpecTypeName` — a literal union of every named type in the MCP spec — so you get autocomplete and a compile error on typos. `specTypeSchemas.X` is a `StandardSchemaV1Sync` — `validate()` returns the result synchronously, -so you can access `.issues` / `.value` without `await`. It composes with any Standard-Schema-aware library. The pre-existing `isCallToolResult(value)` guard still works. +`specTypeSchemas.X` is a `StandardSchemaV1Sync` — `validate()` returns the result synchronously, so you can access `.issues` / `.value` without `await`. It composes with any Standard-Schema-aware library. The pre-existing `isCallToolResult(value)` guard still works. ### Client list methods return empty results for missing capabilities @@ -569,7 +588,7 @@ import { InMemoryTransport } from '@modelcontextprotocol/client'; ### Removed type aliases and deprecated exports -The following deprecated type aliases have been removed from `@modelcontextprotocol/core`: +The following deprecated type aliases have been removed from `@modelcontextprotocol/core-internal`: | Removed | Replacement | | ---------------------------------------- | ------------------------------------------------------------------------------------------------- | @@ -582,11 +601,18 @@ The following deprecated type aliases have been removed from `@modelcontextproto | `IsomorphicHeaders` | Use Web Standard `Headers` | | `AuthInfo` (from `server/auth/types.js`) | `AuthInfo` (now re-exported by `@modelcontextprotocol/client` and `@modelcontextprotocol/server`) | -All other types and schemas exported from `@modelcontextprotocol/sdk/types.js` retain their original names — import them from `@modelcontextprotocol/client` or `@modelcontextprotocol/server`. +All other symbols exported from `@modelcontextprotocol/sdk/types.js` retain their original names. Import the **types**, error classes, enums, and guards from `@modelcontextprotocol/client` or `@modelcontextprotocol/server`, and the **Zod schemas** (the `*Schema` constants) from +`@modelcontextprotocol/core`. > **Note on `isJSONRPCResponse`:** v1's `isJSONRPCResponse` was a deprecated alias that only checked for _result_ responses (it was equivalent to `isJSONRPCResultResponse`). v2 removes the deprecated alias and introduces a **new** `isJSONRPCResponse` with corrected semantics — it > checks for _any_ response (either result or error). If you are migrating v1 code that used `isJSONRPCResponse`, rename it to `isJSONRPCResultResponse` to preserve the original behavior. Use the new `isJSONRPCResponse` only when you want to match both result and error responses. +> **Note on `JSONRPCResponseSchema`:** the Zod schema follows the same pattern. v1's `JSONRPCResponseSchema` validated only _result_ responses; v2 reuses the name for a `z.union([JSONRPCResultResponseSchema, JSONRPCErrorResponseSchema])` that also accepts error responses. If you +> are migrating v1 code that called `JSONRPCResponseSchema.parse()`/`.safeParse()`, rename it to `JSONRPCResultResponseSchema` (re-exported by `@modelcontextprotocol/core`) to preserve the original validation. The codemod performs this rename automatically. + +> **Note on the `JSONRPCResponse` type:** the TypeScript type follows the same pattern. v1's `JSONRPCResponse` was the _result-only_ response type; v2 reuses the name for the result\|error union (`Infer`). If you are migrating v1 code that annotated values with +> `JSONRPCResponse`, rename it to `JSONRPCResultResponse` (re-exported by `@modelcontextprotocol/client` and `@modelcontextprotocol/server`) to preserve the original narrower type. The codemod performs this rename automatically. + **Before (v1):** ```typescript @@ -902,7 +928,9 @@ The 2025-11 experimental tasks side-channel woven through `Protocol` has been re **Also removed:** the storage layer (`TaskStore`, `InMemoryTaskStore`, `CreateTaskOptions`, `isTerminal`). It will return as part of the SEP-2663 server-directed plugin in a follow-up. -**Wire types remain.** The task wire surface defined by the 2025-11-25 protocol revision is still exported, for interoperability with peers on that revision: the task Zod schemas and their inferred types (`Task`, `TaskStatus`, `TaskMetadata`, `RelatedTaskMetadata`, `CreateTaskResult`, `GetTask*`, `GetTaskPayload*`, `ListTasks*`, `CancelTask*`, `TaskStatusNotification*`, `TaskAugmentedRequestParams`), the task members of the request/result/notification unions, the `tasks` capability key, the `isTaskAugmentedRequestParams` guard, and `RELATED_TASK_META_KEY`. Only the behavior is gone: servers built on this SDK do not advertise the `tasks` capability, and inbound `tasks/*` requests receive a standard `-32601` (method not found) error. +**Wire types remain.** The task wire surface defined by the 2025-11-25 protocol revision is still exported, for interoperability with peers on that revision: the task Zod schemas and their inferred types (`Task`, `TaskStatus`, `TaskMetadata`, `RelatedTaskMetadata`, +`CreateTaskResult`, `GetTask*`, `GetTaskPayload*`, `ListTasks*`, `CancelTask*`, `TaskStatusNotification*`, `TaskAugmentedRequestParams`), the task members of the request/result/notification unions, the `tasks` capability key, the `isTaskAugmentedRequestParams` guard, and +`RELATED_TASK_META_KEY`. Only the behavior is gone: servers built on this SDK do not advertise the `tasks` capability, and inbound `tasks/*` requests receive a standard `-32601` (method not found) error. There is no migration path for the removed surface; it was always `@experimental`. Task support is planned to return as an opt-in extension plugin per SEP-2663. @@ -997,9 +1025,9 @@ The following APIs are unchanged between v1 and v2 (only the import paths change - All Zod schemas and type definitions from `types.ts` (except the aliases listed above) - Tool, prompt, and resource callback return types -**Session-ID mismatch responses**: when session management is enabled and a request carries an `Mcp-Session-Id` header that doesn't match the active session, the Streamable HTTP server transport responds `404 Not Found` with a JSON-RPC error body using code `-32001` and -message `Session not found` — unchanged from v1. Note that this use of `-32001` is an SDK convention, not a spec-assigned error code, and it is expected to be re-derived as error handling for the 2026 protocol revision (`2026-07-28`) is adopted. Avoid hard-coding the -`-32001` code in client logic; key off the HTTP `404` status instead. +**Session-ID mismatch responses**: when session management is enabled and a request carries an `Mcp-Session-Id` header that doesn't match the active session, the Streamable HTTP server transport responds `404 Not Found` with a JSON-RPC error body using code `-32001` and message +`Session not found` — unchanged from v1. Note that this use of `-32001` is an SDK convention, not a spec-assigned error code, and it is expected to be re-derived as error handling for the 2026 protocol revision (`2026-07-28`) is adopted. Avoid hard-coding the `-32001` code in +client logic; key off the HTTP `404` status instead. ## Using an LLM to migrate your code diff --git a/examples/client-quickstart/tsconfig.json b/examples/client-quickstart/tsconfig.json index a2bf4fd724..cf326c78d8 100644 --- a/examples/client-quickstart/tsconfig.json +++ b/examples/client-quickstart/tsconfig.json @@ -10,11 +10,11 @@ "@modelcontextprotocol/client": ["./node_modules/@modelcontextprotocol/client/src/index.ts"], "@modelcontextprotocol/client/stdio": ["./node_modules/@modelcontextprotocol/client/src/stdio.ts"], "@modelcontextprotocol/client/_shims": ["./node_modules/@modelcontextprotocol/client/src/shimsNode.ts"], - "@modelcontextprotocol/core": [ - "./node_modules/@modelcontextprotocol/client/node_modules/@modelcontextprotocol/core/src/index.ts" + "@modelcontextprotocol/core-internal": [ + "./node_modules/@modelcontextprotocol/client/node_modules/@modelcontextprotocol/core-internal/src/index.ts" ], - "@modelcontextprotocol/core/public": [ - "./node_modules/@modelcontextprotocol/client/node_modules/@modelcontextprotocol/core/src/exports/public/index.ts" + "@modelcontextprotocol/core-internal/public": [ + "./node_modules/@modelcontextprotocol/client/node_modules/@modelcontextprotocol/core-internal/src/exports/public/index.ts" ] } }, diff --git a/examples/client/tsconfig.json b/examples/client/tsconfig.json index 5c1f7fc764..fa6b75f806 100644 --- a/examples/client/tsconfig.json +++ b/examples/client/tsconfig.json @@ -8,11 +8,11 @@ "@modelcontextprotocol/client": ["./node_modules/@modelcontextprotocol/client/src/index.ts"], "@modelcontextprotocol/client/stdio": ["./node_modules/@modelcontextprotocol/client/src/stdio.ts"], "@modelcontextprotocol/client/_shims": ["./node_modules/@modelcontextprotocol/client/src/shimsNode.ts"], - "@modelcontextprotocol/core": [ - "./node_modules/@modelcontextprotocol/client/node_modules/@modelcontextprotocol/core/src/index.ts" + "@modelcontextprotocol/core-internal": [ + "./node_modules/@modelcontextprotocol/client/node_modules/@modelcontextprotocol/core-internal/src/index.ts" ], - "@modelcontextprotocol/core/public": [ - "./node_modules/@modelcontextprotocol/client/node_modules/@modelcontextprotocol/core/src/exports/public/index.ts" + "@modelcontextprotocol/core-internal/public": [ + "./node_modules/@modelcontextprotocol/client/node_modules/@modelcontextprotocol/core-internal/src/exports/public/index.ts" ], "@modelcontextprotocol/eslint-config": ["./node_modules/@modelcontextprotocol/eslint-config/tsconfig.json"], "@modelcontextprotocol/vitest-config": ["./node_modules/@modelcontextprotocol/vitest-config/tsconfig.json"], diff --git a/examples/server-quickstart/tsconfig.json b/examples/server-quickstart/tsconfig.json index bd80e544cd..36dccebc19 100644 --- a/examples/server-quickstart/tsconfig.json +++ b/examples/server-quickstart/tsconfig.json @@ -10,11 +10,11 @@ "@modelcontextprotocol/server": ["./node_modules/@modelcontextprotocol/server/src/index.ts"], "@modelcontextprotocol/server/stdio": ["./node_modules/@modelcontextprotocol/server/src/stdio.ts"], "@modelcontextprotocol/server/_shims": ["./node_modules/@modelcontextprotocol/server/src/shimsNode.ts"], - "@modelcontextprotocol/core": [ - "./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/src/index.ts" + "@modelcontextprotocol/core-internal": [ + "./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core-internal/src/index.ts" ], - "@modelcontextprotocol/core/public": [ - "./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/src/exports/public/index.ts" + "@modelcontextprotocol/core-internal/public": [ + "./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core-internal/src/exports/public/index.ts" ] } }, diff --git a/examples/server/tsconfig.json b/examples/server/tsconfig.json index 37a3e874f7..3f08072b3c 100644 --- a/examples/server/tsconfig.json +++ b/examples/server/tsconfig.json @@ -11,11 +11,11 @@ "@modelcontextprotocol/express": ["./node_modules/@modelcontextprotocol/express/src/index.ts"], "@modelcontextprotocol/node": ["./node_modules/@modelcontextprotocol/node/src/index.ts"], "@modelcontextprotocol/hono": ["./node_modules/@modelcontextprotocol/hono/src/index.ts"], - "@modelcontextprotocol/core": [ - "./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/src/index.ts" + "@modelcontextprotocol/core-internal": [ + "./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core-internal/src/index.ts" ], - "@modelcontextprotocol/core/public": [ - "./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/src/exports/public/index.ts" + "@modelcontextprotocol/core-internal/public": [ + "./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core-internal/src/exports/public/index.ts" ], "@modelcontextprotocol/examples-shared": ["./node_modules/@modelcontextprotocol/examples-shared/src/index.ts"], "@modelcontextprotocol/eslint-config": ["./node_modules/@modelcontextprotocol/eslint-config/tsconfig.json"], diff --git a/examples/shared/package.json b/examples/shared/package.json index a55c3dd2e6..5522bd6c8d 100644 --- a/examples/shared/package.json +++ b/examples/shared/package.json @@ -29,7 +29,7 @@ "test:watch": "vitest" }, "dependencies": { - "@modelcontextprotocol/core": "workspace:^", + "@modelcontextprotocol/core-internal": "workspace:^", "@modelcontextprotocol/server": "workspace:^", "@modelcontextprotocol/express": "workspace:^", "better-auth": "^1.4.17", diff --git a/examples/shared/tsconfig.json b/examples/shared/tsconfig.json index bfc4eab524..ba240dda77 100644 --- a/examples/shared/tsconfig.json +++ b/examples/shared/tsconfig.json @@ -10,11 +10,11 @@ "@modelcontextprotocol/server": ["./node_modules/@modelcontextprotocol/server/src/index.ts"], "@modelcontextprotocol/server/_shims": ["./node_modules/@modelcontextprotocol/server/src/shimsNode.ts"], "@modelcontextprotocol/express": ["./node_modules/@modelcontextprotocol/express/src/index.ts"], - "@modelcontextprotocol/core": [ - "./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/src/index.ts" + "@modelcontextprotocol/core-internal": [ + "./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core-internal/src/index.ts" ], - "@modelcontextprotocol/core/public": [ - "./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/src/exports/public/index.ts" + "@modelcontextprotocol/core-internal/public": [ + "./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core-internal/src/exports/public/index.ts" ], "@modelcontextprotocol/eslint-config": ["./node_modules/@modelcontextprotocol/eslint-config/tsconfig.json"], "@modelcontextprotocol/vitest-config": ["./node_modules/@modelcontextprotocol/vitest-config/tsconfig.json"], diff --git a/packages/client/CHANGELOG.md b/packages/client/CHANGELOG.md index bc024d94f3..7c59a69ea4 100644 --- a/packages/client/CHANGELOG.md +++ b/packages/client/CHANGELOG.md @@ -1,5 +1,104 @@ # @modelcontextprotocol/client +## 2.0.0-alpha.3 + +### Major Changes + +- [#2128](https://github.com/modelcontextprotocol/typescript-sdk/pull/2128) [`c8d7401`](https://github.com/modelcontextprotocol/typescript-sdk/commit/c8d7401b46f34b6c49b7cfb7b321714d0d4048f6) Thanks [@felixweinberger](https://github.com/felixweinberger)! - SEP-2663: remove + 2025-11 experimental tasks (TaskManager, experimental.tasks.\* accessors). Tasks are now Extensions Track. + +### Minor Changes + +- [#2049](https://github.com/modelcontextprotocol/typescript-sdk/pull/2049) [`4f226c1`](https://github.com/modelcontextprotocol/typescript-sdk/commit/4f226c1e35200616d62f1d7e46a2daa33d91172a) Thanks [@KKonstantinov](https://github.com/KKonstantinov)! - Add `SdkHttpError` subclass + with typed `.status` / `.statusText` accessors for HTTP transport failures. `StreamableHTTPClientTransport` now throws `SdkHttpError` (which extends `SdkError`) for non-OK HTTP responses; `SSEClientTransport` throws `SdkHttpError` for 401-after-reauth (circuit breaker). + +- [#1974](https://github.com/modelcontextprotocol/typescript-sdk/pull/1974) [`db83829`](https://github.com/modelcontextprotocol/typescript-sdk/commit/db83829c5bd5d6659c5e7b96638b11953b0e262d) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Add custom (non-spec) + method support: a 3-arg `setRequestHandler(method, schemas, handler)` / `setNotificationHandler(method, schemas, handler)` form for vendor-prefixed methods, and a `request(req, resultSchema)` overload (also on `ctx.mcpReq.send`) for typed custom-method results. Spec-method + calls are unchanged. + + Response result-schema validation failure now rejects with `SdkError(InvalidResult)` instead of a raw `ZodError`. Adds `SdkErrorCode.InvalidResult`. + +- [#1653](https://github.com/modelcontextprotocol/typescript-sdk/pull/1653) [`6bec24a`](https://github.com/modelcontextprotocol/typescript-sdk/commit/6bec24a14cb7e3dbe9f5e04aeb893cd0d6e8cb83) Thanks [@rechedev9](https://github.com/rechedev9)! - Add `validateClientMetadataUrl()` + utility for early validation of `clientMetadataUrl` + + Exports a `validateClientMetadataUrl()` function that `OAuthClientProvider` implementations can call in their constructors to fail fast on invalid URL-based client IDs, instead of discovering the error deep in the auth flow. + +- [#2354](https://github.com/modelcontextprotocol/typescript-sdk/pull/2354) [`0fb8406`](https://github.com/modelcontextprotocol/typescript-sdk/commit/0fb8406d83a3578a12a605e1b43c352d565071b1) Thanks [@KKonstantinov](https://github.com/KKonstantinov)! - Add the v2 + `IdJagTokenExchangeResponse` type to the public API and register its schema as an MCP spec type. `IdJagTokenExchangeResponse` is now exported from `@modelcontextprotocol/client` and `@modelcontextprotocol/server`, `'IdJagTokenExchangeResponse'` joins the `SpecTypeName` union, + and `isSpecType.IdJagTokenExchangeResponse(value)` / `specTypeSchemas.IdJagTokenExchangeResponse` validate it by name — matching how the OAuth/OpenID auth schemas are already exposed. The Zod schema itself, `IdJagTokenExchangeResponseSchema`, is available from + `@modelcontextprotocol/core`. + +- [#2248](https://github.com/modelcontextprotocol/typescript-sdk/pull/2248) [`db28156`](https://github.com/modelcontextprotocol/typescript-sdk/commit/db28156a23032290b3ce3bae00a17544c4807b8f) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Restore the 2025-11-25 + task wire types that were removed together with the task feature: the task schemas and inferred types, task members of the request/result/notification unions, the `task` request-params augmentation, the `tasks` capability key, the `isTaskAugmentedRequestParams` guard, and + `RELATED_TASK_META_KEY`. The task feature itself remains removed — servers do not advertise the `tasks` capability and inbound `tasks/*` requests receive `-32601` — but the wire surface stays so SDKs interoperate cleanly with peers on the 2025-11-25 revision. + +- [#1887](https://github.com/modelcontextprotocol/typescript-sdk/pull/1887) [`96db044`](https://github.com/modelcontextprotocol/typescript-sdk/commit/96db044fe965f0b7d5109e6d68598eaddce961c9) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Export `isSpecType` and + `specTypeSchemas` records for runtime validation of any MCP spec type by name. `isSpecType.ContentBlock(value)` is a type predicate; `specTypeSchemas.ContentBlock` is a `StandardSchemaV1Sync` validator — `validate()` returns the result synchronously. Guards are + standalone functions, so `arr.filter(isSpecType.ContentBlock)` works. Also export the `SpecTypeName`, `SpecTypes`, and `StandardSchemaV1Sync` types. + +- [#1871](https://github.com/modelcontextprotocol/typescript-sdk/pull/1871) [`9fc9070`](https://github.com/modelcontextprotocol/typescript-sdk/commit/9fc9070b7b8e18227127aaee9869f8809a87fdb1) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Move stdio transports + to a `./stdio` subpath export. Import `StdioClientTransport`, `getDefaultEnvironment`, `DEFAULT_INHERITED_ENV_VARS`, and `StdioServerParameters` from `@modelcontextprotocol/client/stdio`, and `StdioServerTransport` from `@modelcontextprotocol/server/stdio`. The + `@modelcontextprotocol/client` root entry no longer pulls in `node:child_process`, `node:stream`, or `cross-spawn`, fixing bundling for browser and Cloudflare Workers targets; the `@modelcontextprotocol/server` root entry drops its `node:stream` reference. Node.js, Bun, and + Deno consumers update the import path; runtime behavior is unchanged. + +### Patch Changes + +- [#1897](https://github.com/modelcontextprotocol/typescript-sdk/pull/1897) [`434b2f1`](https://github.com/modelcontextprotocol/typescript-sdk/commit/434b2f11ecec452f3dca0199f68afccd8b119dd4) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Stop bundling + `@cfworker/json-schema` into the main package barrel. Previously `CfWorkerJsonSchemaValidator` was re-exported from the core internal barrel, so tsdown inlined the `@cfworker/json-schema` dependency into every consumer's bundle even when it was never used. The named validator + classes are now reachable only via the explicit `@modelcontextprotocol/{client,server}/validators/{ajv,cf-worker}` subpaths and the runtime `_shims` conditional, so consumers that import only from the root entry point no longer ship the validator dep. + +- [#2269](https://github.com/modelcontextprotocol/typescript-sdk/pull/2269) [`e84c3e9`](https://github.com/modelcontextprotocol/typescript-sdk/commit/e84c3e9ad040eb09299b1f99dd8bdd14251ae790) Thanks [@mattzcarey](https://github.com/mattzcarey)! - Non-SEP draft spec conformance + fixes + - `McpServer` now eagerly installs list/read/call handlers for every primitive capability (`tools`, `resources`, `prompts`) declared in `ServerOptions.capabilities`. Per the draft spec, a server that declares a capability MUST respond to its list method (potentially with an + empty result) instead of returning "Method not found". Previously, handlers were only installed lazily on first registration, so a server constructed with e.g. `capabilities: { tools: {} }` and zero registered tools answered `tools/list` with `-32601`. Low-level `Server` + users remain responsible for registering handlers for declared capabilities (documented on `ServerOptions.capabilities`). + - Fixed pagination doc examples on `Client.listTools`/`listPrompts`/`listResources` to loop `while (cursor !== undefined)` instead of `while (cursor)` — per the draft spec, clients MUST NOT treat an empty-string cursor as the end of results. + +- [#1834](https://github.com/modelcontextprotocol/typescript-sdk/pull/1834) [`42cb6b2`](https://github.com/modelcontextprotocol/typescript-sdk/commit/42cb6b2b728347d8b58a0d1940b7e63366a29ab9) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Export + `InMemoryTransport` for in-process testing. + +- [#1898](https://github.com/modelcontextprotocol/typescript-sdk/pull/1898) [`2a7611d`](https://github.com/modelcontextprotocol/typescript-sdk/commit/2a7611d46b0d4f4e7bd4147c7a3ad3da00e57e52) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Add top-level `types` + field (and `typesVersions` on client/server for their subpath exports) so consumers on legacy `moduleResolution: "node"` can resolve type declarations. The `exports` map remains the source of truth for `nodenext`/`bundler` resolution. The `typesVersions` map includes entries + for subpaths added by sibling PRs in this series (`zod-schemas`, `stdio`); those entries are no-ops until the corresponding `dist/*.d.mts` files exist. + +- [#1655](https://github.com/modelcontextprotocol/typescript-sdk/pull/1655) [`1eb3123`](https://github.com/modelcontextprotocol/typescript-sdk/commit/1eb31236e707c4f4ab9234d87db21ab3f34bf0bc) Thanks [@nielskaspers](https://github.com/nielskaspers)! - fix(client): append custom + Accept headers to spec-required defaults in StreamableHTTPClientTransport + + Custom Accept headers provided via `requestInit.headers` are now appended to the spec-mandated Accept types instead of being overwritten. This ensures the required media types (`application/json, text/event-stream` for POST; `text/event-stream` for GET SSE) are always present + while allowing users to include additional types for proxy/gateway routing. + +- [#2268](https://github.com/modelcontextprotocol/typescript-sdk/pull/2268) [`49c0a71`](https://github.com/modelcontextprotocol/typescript-sdk/commit/49c0a711c8bf2d385f9e03b4f28ba0ff0d0db0bd) Thanks [@mattzcarey](https://github.com/mattzcarey)! - Mark the roots, sampling, and + logging runtime APIs as `@deprecated` per SEP-2577 (deprecated as of protocol version 2026-07-28; functional for at least twelve months). Annotates `Server.createMessage`/`listRoots`/`sendLoggingMessage`, `McpServer.sendLoggingMessage`, + `Client.setLoggingLevel`/`sendRootsListChanged`, the `ServerContext.mcpReq.log`/`requestSampling` helpers, and the `roots`/`sampling`/`logging` capability schema fields. JSDoc/docs only — no behavior change. + +- [#2275](https://github.com/modelcontextprotocol/typescript-sdk/pull/2275) [`1b53a41`](https://github.com/modelcontextprotocol/typescript-sdk/commit/1b53a415ea2c33aa11ac413fc9c2d68ccffde784) Thanks [@KKonstantinov](https://github.com/KKonstantinov)! - Add a configurable + `maxBufferSize` (default 10 MB) to the stdio transports. When a single message would push the read buffer past the limit, the transport now emits an `onerror` and closes instead of growing the buffer unbounded. Configure via `new StdioClientTransport({ ..., maxBufferSize })` or + `new StdioServerTransport(stdin, stdout, { maxBufferSize })`. The default is exported from `@modelcontextprotocol/core-internal` as `STDIO_DEFAULT_MAX_BUFFER_SIZE`. + +- [#2088](https://github.com/modelcontextprotocol/typescript-sdk/pull/2088) [`16d13ab`](https://github.com/modelcontextprotocol/typescript-sdk/commit/16d13abf78b5dba5de73dfa284325b13d4219bb2) Thanks [@mattzcarey](https://github.com/mattzcarey)! - Bundle automatic JSON Schema + validator defaults in `@modelcontextprotocol/client` and `@modelcontextprotocol/server` runtime shims. + + Client and server pick the right validator automatically based on the runtime: the Node shim uses AJV, the browser/workerd shim uses `@cfworker/json-schema`. Both backends are bundled into the shim chunks that select them, so the default code path needs no extra installs — + `import { McpServer } from '@modelcontextprotocol/server'` does not pull `ajv` or `@cfworker/json-schema` into the root entry chunk. + + The named validator classes remain part of the public surface for consumers who want to customize the built-in backend (pre-register schemas by `$id`, register custom AJV formats, switch dialects, change `@cfworker/json-schema` draft). They are exposed through explicit + subpaths so they do not bloat the root index chunk: + - `import { AjvJsonSchemaValidator } from '@modelcontextprotocol/{client,server}/validators/ajv'` + - `import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/{client,server}/validators/cf-worker'` + + Importing from one of these subpaths means the corresponding peer dep (`ajv` + `ajv-formats`, or `@cfworker/json-schema`) must be in your `package.json`. The shim keeps its own vendored copy for the default path, so a project can use the subpath in some files and rely on the + default in others. + + The `jsonSchemaValidator` interface remains the public extension point for replacing validation entirely with a custom implementation. + +- [#1976](https://github.com/modelcontextprotocol/typescript-sdk/pull/1976) [`55b1f06`](https://github.com/modelcontextprotocol/typescript-sdk/commit/55b1f06cd4569e334f3435b7971f0446f1ef9be9) Thanks [@felixweinberger](https://github.com/felixweinberger)! - refactor: subclasses + override `_wrapHandler` hook instead of redeclaring `setRequestHandler`. + +- [#1895](https://github.com/modelcontextprotocol/typescript-sdk/pull/1895) [`b256546`](https://github.com/modelcontextprotocol/typescript-sdk/commit/b256546750277faeb7c886792aae5ed26e6904d5) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Fix runtime crash on + `tools/list` when a tool's `inputSchema` comes from zod 4.0–4.1. The SDK requires `~standard.jsonSchema` (StandardJSONSchemaV1, added in zod 4.2.0); previously a missing `jsonSchema` crashed at `undefined[io]`. `standardSchemaToJsonSchema` now detects zod 4 schemas lacking + `jsonSchema` and falls back to the SDK-bundled `z.toJSONSchema()`, emitting a one-time console warning. zod 3 schemas (which the bundled zod 4 converter cannot introspect) and non-zod schema libraries without `jsonSchema` get a clear error pointing to `fromJsonSchema()`. The + workspace zod catalog is also bumped to `^4.2.0`. + ## 2.0.0-alpha.2 ### Patch Changes @@ -61,7 +160,7 @@ For raw JSON Schema (e.g. TypeBox output), use the new `fromJsonSchema` adapter: ```typescript - import { fromJsonSchema, AjvJsonSchemaValidator } from '@modelcontextprotocol/core'; + import { fromJsonSchema, AjvJsonSchemaValidator } from '@modelcontextprotocol/core-internal'; server.registerTool( 'greet', @@ -74,7 +173,8 @@ **Breaking changes:** - `experimental.tasks.getTaskResult()` no longer accepts a `resultSchema` parameter. Returns `GetTaskPayloadResult` (a loose `Result`); cast to the expected type at the call site. - - Removed unused exports from `@modelcontextprotocol/core`: `SchemaInput`, `schemaToJson`, `parseSchemaAsync`, `getSchemaShape`, `getSchemaDescription`, `isOptionalSchema`, `unwrapOptionalSchema`. Use the new `standardSchemaToJsonSchema` and `validateStandardSchema` instead. + - Removed unused exports from `@modelcontextprotocol/core-internal`: `SchemaInput`, `schemaToJson`, `parseSchemaAsync`, `getSchemaShape`, `getSchemaDescription`, `isOptionalSchema`, `unwrapOptionalSchema`. Use the new `standardSchemaToJsonSchema` and `validateStandardSchema` + instead. - `completable()` remains Zod-specific (it relies on Zod's `.shape` introspection). - [#1710](https://github.com/modelcontextprotocol/typescript-sdk/pull/1710) [`e563e63`](https://github.com/modelcontextprotocol/typescript-sdk/commit/e563e63bd2b3c2c1d1137406bef3f842c946201e) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Add `AuthProvider` for diff --git a/packages/client/eslint.config.mjs b/packages/client/eslint.config.mjs index 4f034f2235..dd9e88588a 100644 --- a/packages/client/eslint.config.mjs +++ b/packages/client/eslint.config.mjs @@ -6,7 +6,7 @@ export default [ ...baseConfig, { settings: { - 'import/internal-regex': '^@modelcontextprotocol/core' + 'import/internal-regex': '^@modelcontextprotocol/core-internal' } } ]; diff --git a/packages/client/package.json b/packages/client/package.json index c712172139..0ecbdaa6be 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/client", - "version": "2.0.0-alpha.2", + "version": "2.0.0-alpha.3", "description": "Model Context Protocol implementation for TypeScript - Client package", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -92,7 +92,7 @@ "zod": "catalog:runtimeShared" }, "devDependencies": { - "@modelcontextprotocol/core": "workspace:^", + "@modelcontextprotocol/core-internal": "workspace:^", "@modelcontextprotocol/tsconfig": "workspace:^", "@modelcontextprotocol/vitest-config": "workspace:^", "@modelcontextprotocol/eslint-config": "workspace:^", diff --git a/packages/client/src/client/auth.examples.ts b/packages/client/src/client/auth.examples.ts index 01531c9780..fc4c52deaf 100644 --- a/packages/client/src/client/auth.examples.ts +++ b/packages/client/src/client/auth.examples.ts @@ -7,7 +7,7 @@ * @module */ -import type { AuthorizationServerMetadata } from '@modelcontextprotocol/core'; +import type { AuthorizationServerMetadata } from '@modelcontextprotocol/core-internal'; import type { OAuthClientProvider } from './auth'; import { fetchToken } from './auth'; diff --git a/packages/client/src/client/auth.ts b/packages/client/src/client/auth.ts index 5f55fb7a08..9e47a38203 100644 --- a/packages/client/src/client/auth.ts +++ b/packages/client/src/client/auth.ts @@ -9,7 +9,7 @@ import type { OAuthMetadata, OAuthProtectedResourceMetadata, OAuthTokens -} from '@modelcontextprotocol/core'; +} from '@modelcontextprotocol/core-internal'; import { checkResourceAllowed, LATEST_PROTOCOL_VERSION, @@ -22,7 +22,7 @@ import { OAuthTokensSchema, OpenIdProviderDiscoveryMetadataSchema, resourceUrlFromServerUrl -} from '@modelcontextprotocol/core'; +} from '@modelcontextprotocol/core-internal'; import pkceChallenge from 'pkce-challenge'; /** diff --git a/packages/client/src/client/authExtensions.ts b/packages/client/src/client/authExtensions.ts index c62d635138..857118410c 100644 --- a/packages/client/src/client/authExtensions.ts +++ b/packages/client/src/client/authExtensions.ts @@ -5,7 +5,7 @@ * for common machine-to-machine authentication scenarios. */ -import type { FetchLike, OAuthClientInformation, OAuthClientMetadata, OAuthTokens } from '@modelcontextprotocol/core'; +import type { FetchLike, OAuthClientInformation, OAuthClientMetadata, OAuthTokens } from '@modelcontextprotocol/core-internal'; import type { CryptoKey, JWK } from 'jose'; import type { AddClientAuthentication, OAuthClientProvider } from './auth'; diff --git a/packages/client/src/client/client.examples.ts b/packages/client/src/client/client.examples.ts index e748463574..a02f7dcb3d 100644 --- a/packages/client/src/client/client.examples.ts +++ b/packages/client/src/client/client.examples.ts @@ -7,7 +7,7 @@ * @module */ -import type { Prompt, Resource, Tool } from '@modelcontextprotocol/core'; +import type { Prompt, Resource, Tool } from '@modelcontextprotocol/core-internal'; import { Client } from './client'; import { SSEClientTransport } from './sse'; diff --git a/packages/client/src/client/client.ts b/packages/client/src/client/client.ts index bc3a91150b..8277225d79 100644 --- a/packages/client/src/client/client.ts +++ b/packages/client/src/client/client.ts @@ -32,7 +32,7 @@ import type { Tool, Transport, UnsubscribeRequest -} from '@modelcontextprotocol/core'; +} from '@modelcontextprotocol/core-internal'; import { CallToolResultSchema, CompleteResultSchema, @@ -58,7 +58,7 @@ import { ReadResourceResultSchema, SdkError, SdkErrorCode -} from '@modelcontextprotocol/core'; +} from '@modelcontextprotocol/core-internal'; /** * Elicitation default application helper. Applies defaults to the `data` based on the `schema`. diff --git a/packages/client/src/client/crossAppAccess.ts b/packages/client/src/client/crossAppAccess.ts index 7db9371cae..2783f2002a 100644 --- a/packages/client/src/client/crossAppAccess.ts +++ b/packages/client/src/client/crossAppAccess.ts @@ -8,8 +8,8 @@ * @module */ -import type { FetchLike } from '@modelcontextprotocol/core'; -import { IdJagTokenExchangeResponseSchema, OAuthErrorResponseSchema, OAuthTokensSchema } from '@modelcontextprotocol/core'; +import type { FetchLike } from '@modelcontextprotocol/core-internal'; +import { IdJagTokenExchangeResponseSchema, OAuthErrorResponseSchema, OAuthTokensSchema } from '@modelcontextprotocol/core-internal'; import type { ClientAuthMethod } from './auth'; import { applyClientAuthentication, discoverAuthorizationServerMetadata } from './auth'; diff --git a/packages/client/src/client/middleware.ts b/packages/client/src/client/middleware.ts index f5f36ae215..c86db72d2c 100644 --- a/packages/client/src/client/middleware.ts +++ b/packages/client/src/client/middleware.ts @@ -1,4 +1,4 @@ -import type { FetchLike } from '@modelcontextprotocol/core'; +import type { FetchLike } from '@modelcontextprotocol/core-internal'; import type { OAuthClientProvider } from './auth'; import { auth, extractWWWAuthenticateParams, UnauthorizedError } from './auth'; diff --git a/packages/client/src/client/sse.ts b/packages/client/src/client/sse.ts index fcd93f0594..3038ebfd82 100644 --- a/packages/client/src/client/sse.ts +++ b/packages/client/src/client/sse.ts @@ -1,4 +1,4 @@ -import type { FetchLike, JSONRPCMessage, Transport } from '@modelcontextprotocol/core'; +import type { FetchLike, JSONRPCMessage, Transport } from '@modelcontextprotocol/core-internal'; import { createFetchWithInit, JSONRPCMessageSchema, @@ -6,7 +6,7 @@ import { SdkError, SdkErrorCode, SdkHttpError -} from '@modelcontextprotocol/core'; +} from '@modelcontextprotocol/core-internal'; import type { ErrorEvent, EventSourceInit } from 'eventsource'; import { EventSource } from 'eventsource'; diff --git a/packages/client/src/client/stdio.ts b/packages/client/src/client/stdio.ts index 37c6d9a252..29b0027eae 100644 --- a/packages/client/src/client/stdio.ts +++ b/packages/client/src/client/stdio.ts @@ -3,8 +3,8 @@ import process from 'node:process'; import type { Stream } from 'node:stream'; import { PassThrough } from 'node:stream'; -import type { JSONRPCMessage, Transport } from '@modelcontextprotocol/core'; -import { ReadBuffer, SdkError, SdkErrorCode, serializeMessage } from '@modelcontextprotocol/core'; +import type { JSONRPCMessage, Transport } from '@modelcontextprotocol/core-internal'; +import { ReadBuffer, SdkError, SdkErrorCode, serializeMessage } from '@modelcontextprotocol/core-internal'; import spawn from 'cross-spawn'; export type StdioServerParameters = { diff --git a/packages/client/src/client/streamableHttp.ts b/packages/client/src/client/streamableHttp.ts index 7a7da69dc1..8962cf5639 100644 --- a/packages/client/src/client/streamableHttp.ts +++ b/packages/client/src/client/streamableHttp.ts @@ -1,6 +1,6 @@ import type { ReadableWritablePair } from 'node:stream/web'; -import type { FetchLike, JSONRPCMessage, Transport } from '@modelcontextprotocol/core'; +import type { FetchLike, JSONRPCMessage, Transport } from '@modelcontextprotocol/core-internal'; import { createFetchWithInit, isInitializedNotification, @@ -12,7 +12,7 @@ import { SdkError, SdkErrorCode, SdkHttpError -} from '@modelcontextprotocol/core'; +} from '@modelcontextprotocol/core-internal'; import { EventSourceParserStream } from 'eventsource-parser/stream'; import type { AuthProvider, OAuthClientProvider } from './auth'; diff --git a/packages/client/src/fromJsonSchema.ts b/packages/client/src/fromJsonSchema.ts index 575db2a8c4..588a096d5f 100644 --- a/packages/client/src/fromJsonSchema.ts +++ b/packages/client/src/fromJsonSchema.ts @@ -1,6 +1,6 @@ import { DefaultJsonSchemaValidator } from '@modelcontextprotocol/client/_shims'; -import type { JsonSchemaType, jsonSchemaValidator, StandardSchemaWithJSON } from '@modelcontextprotocol/core'; -import { fromJsonSchema as coreFromJsonSchema } from '@modelcontextprotocol/core'; +import type { JsonSchemaType, jsonSchemaValidator, StandardSchemaWithJSON } from '@modelcontextprotocol/core-internal'; +import { fromJsonSchema as coreFromJsonSchema } from '@modelcontextprotocol/core-internal'; let _defaultValidator: jsonSchemaValidator | undefined; diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 69cee24488..b48d7cd0e8 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -2,7 +2,7 @@ // // This file defines the complete public surface. It consists of: // - Package-specific exports: listed explicitly below (named imports) -// - Protocol-level types: re-exported from @modelcontextprotocol/core/public +// - Protocol-level types: re-exported from @modelcontextprotocol/core-internal/public // // Any new export added here becomes public API. Use named exports, not wildcards. @@ -75,4 +75,4 @@ export { StreamableHTTPClientTransport } from './client/streamableHttp'; export { fromJsonSchema } from './fromJsonSchema'; // re-export curated public API from core -export * from '@modelcontextprotocol/core/public'; +export * from '@modelcontextprotocol/core-internal/public'; diff --git a/packages/client/src/shimsBrowser.ts b/packages/client/src/shimsBrowser.ts index de126d53bf..ee5d6373f9 100644 --- a/packages/client/src/shimsBrowser.ts +++ b/packages/client/src/shimsBrowser.ts @@ -3,7 +3,7 @@ * * This file is selected via package.json export conditions when running in a browser. */ -export { CfWorkerJsonSchemaValidator as DefaultJsonSchemaValidator } from '@modelcontextprotocol/core/validators/cfWorker'; +export { CfWorkerJsonSchemaValidator as DefaultJsonSchemaValidator } from '@modelcontextprotocol/core-internal/validators/cfWorker'; /** * Whether `fetch()` may throw `TypeError` due to CORS. Only true in browser contexts diff --git a/packages/client/src/shimsNode.ts b/packages/client/src/shimsNode.ts index de48ea2de6..5c411d731b 100644 --- a/packages/client/src/shimsNode.ts +++ b/packages/client/src/shimsNode.ts @@ -3,7 +3,7 @@ * * This file is selected via package.json export conditions when running in Node.js. */ -export { AjvJsonSchemaValidator as DefaultJsonSchemaValidator } from '@modelcontextprotocol/core/validators/ajv'; +export { AjvJsonSchemaValidator as DefaultJsonSchemaValidator } from '@modelcontextprotocol/core-internal/validators/ajv'; /** * Whether `fetch()` may throw `TypeError` due to CORS. CORS is a browser-only concept — diff --git a/packages/client/src/shimsWorkerd.ts b/packages/client/src/shimsWorkerd.ts index 9e6660b9ab..bd680e4358 100644 --- a/packages/client/src/shimsWorkerd.ts +++ b/packages/client/src/shimsWorkerd.ts @@ -3,7 +3,7 @@ * * This file is selected via package.json export conditions when running in workerd. */ -export { CfWorkerJsonSchemaValidator as DefaultJsonSchemaValidator } from '@modelcontextprotocol/core/validators/cfWorker'; +export { CfWorkerJsonSchemaValidator as DefaultJsonSchemaValidator } from '@modelcontextprotocol/core-internal/validators/cfWorker'; /** * Whether `fetch()` may throw `TypeError` due to CORS. CORS is a browser-only concept — diff --git a/packages/client/src/validators/ajv.ts b/packages/client/src/validators/ajv.ts index 770df3f57a..059c6b73f2 100644 --- a/packages/client/src/validators/ajv.ts +++ b/packages/client/src/validators/ajv.ts @@ -11,4 +11,4 @@ * const validator = new AjvJsonSchemaValidator(ajv); * ``` */ -export { addFormats, Ajv, AjvJsonSchemaValidator } from '@modelcontextprotocol/core/validators/ajv'; +export { addFormats, Ajv, AjvJsonSchemaValidator } from '@modelcontextprotocol/core-internal/validators/ajv'; diff --git a/packages/client/src/validators/cfWorker.ts b/packages/client/src/validators/cfWorker.ts index 2969b4dc9d..f1d9379afc 100644 --- a/packages/client/src/validators/cfWorker.ts +++ b/packages/client/src/validators/cfWorker.ts @@ -1,3 +1,3 @@ /** Customisation entry point for the `@cfworker/json-schema` validator. */ -export type { CfWorkerSchemaDraft } from '@modelcontextprotocol/core/validators/cfWorker'; -export { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/core/validators/cfWorker'; +export type { CfWorkerSchemaDraft } from '@modelcontextprotocol/core-internal/validators/cfWorker'; +export { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/core-internal/validators/cfWorker'; diff --git a/packages/client/test/client/auth.test.ts b/packages/client/test/client/auth.test.ts index 35e86ffc35..a4b22e0c44 100644 --- a/packages/client/test/client/auth.test.ts +++ b/packages/client/test/client/auth.test.ts @@ -1,5 +1,5 @@ -import type { AuthorizationServerMetadata, OAuthClientMetadata, OAuthTokens } from '@modelcontextprotocol/core'; -import { LATEST_PROTOCOL_VERSION, OAuthError, OAuthErrorCode } from '@modelcontextprotocol/core'; +import type { AuthorizationServerMetadata, OAuthClientMetadata, OAuthTokens } from '@modelcontextprotocol/core-internal'; +import { LATEST_PROTOCOL_VERSION, OAuthError, OAuthErrorCode } from '@modelcontextprotocol/core-internal'; import type { Mock } from 'vitest'; import { expect, vi } from 'vitest'; @@ -3443,7 +3443,7 @@ describe('OAuth Authorization', () => { describe('RequestInit headers passthrough', () => { it('custom headers from RequestInit are passed to auth discovery requests', async () => { - const { createFetchWithInit } = await import('@modelcontextprotocol/core'); + const { createFetchWithInit } = await import('@modelcontextprotocol/core-internal'); const customFetch = vi.fn().mockResolvedValue({ ok: true, @@ -3476,7 +3476,7 @@ describe('OAuth Authorization', () => { }); it('auth-specific headers override base headers from RequestInit', async () => { - const { createFetchWithInit } = await import('@modelcontextprotocol/core'); + const { createFetchWithInit } = await import('@modelcontextprotocol/core-internal'); const customFetch = vi.fn().mockResolvedValue({ ok: true, @@ -3514,7 +3514,7 @@ describe('OAuth Authorization', () => { }); it('other RequestInit options are passed through', async () => { - const { createFetchWithInit } = await import('@modelcontextprotocol/core'); + const { createFetchWithInit } = await import('@modelcontextprotocol/core-internal'); const customFetch = vi.fn().mockResolvedValue({ ok: true, diff --git a/packages/client/test/client/crossAppAccess.test.ts b/packages/client/test/client/crossAppAccess.test.ts index 166f08fa9b..f403bf80a2 100644 --- a/packages/client/test/client/crossAppAccess.test.ts +++ b/packages/client/test/client/crossAppAccess.test.ts @@ -1,4 +1,4 @@ -import type { FetchLike } from '@modelcontextprotocol/core'; +import type { FetchLike } from '@modelcontextprotocol/core-internal'; import { describe, expect, it, vi } from 'vitest'; import { discoverAndRequestJwtAuthGrant, exchangeJwtAuthGrant, requestJwtAuthorizationGrant } from '../../src/client/crossAppAccess'; diff --git a/packages/client/test/client/crossSpawn.test.ts b/packages/client/test/client/crossSpawn.test.ts index f7e8823091..41839565ab 100644 --- a/packages/client/test/client/crossSpawn.test.ts +++ b/packages/client/test/client/crossSpawn.test.ts @@ -1,6 +1,6 @@ import type { ChildProcess } from 'node:child_process'; -import type { JSONRPCMessage } from '@modelcontextprotocol/core'; +import type { JSONRPCMessage } from '@modelcontextprotocol/core-internal'; import spawn from 'cross-spawn'; import type { Mock, MockedFunction } from 'vitest'; diff --git a/packages/client/test/client/jsonSchemaValidatorOverride.test.ts b/packages/client/test/client/jsonSchemaValidatorOverride.test.ts index f61a66033e..82c13a7021 100644 --- a/packages/client/test/client/jsonSchemaValidatorOverride.test.ts +++ b/packages/client/test/client/jsonSchemaValidatorOverride.test.ts @@ -1,5 +1,5 @@ -import type { JSONRPCMessage, JsonSchemaType, JsonSchemaValidatorResult, jsonSchemaValidator } from '@modelcontextprotocol/core'; -import { InMemoryTransport, LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/core'; +import type { JSONRPCMessage, JsonSchemaType, JsonSchemaValidatorResult, jsonSchemaValidator } from '@modelcontextprotocol/core-internal'; +import { InMemoryTransport, LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/core-internal'; import { Client } from '../../src/client/client'; import { fromJsonSchema } from '../../src/fromJsonSchema'; diff --git a/packages/client/test/client/middleware.test.ts b/packages/client/test/client/middleware.test.ts index f4090b4ed0..c0c0886952 100644 --- a/packages/client/test/client/middleware.test.ts +++ b/packages/client/test/client/middleware.test.ts @@ -1,4 +1,4 @@ -import type { FetchLike } from '@modelcontextprotocol/core'; +import type { FetchLike } from '@modelcontextprotocol/core-internal'; import type { Mocked, MockedFunction, MockInstance } from 'vitest'; import type { OAuthClientProvider } from '../../src/client/auth'; diff --git a/packages/client/test/client/sse.test.ts b/packages/client/test/client/sse.test.ts index f44681e589..a5e79f6c99 100644 --- a/packages/client/test/client/sse.test.ts +++ b/packages/client/test/client/sse.test.ts @@ -2,8 +2,8 @@ import type { IncomingMessage, Server, ServerResponse } from 'node:http'; import { createServer } from 'node:http'; import type { AddressInfo } from 'node:net'; -import type { JSONRPCMessage, OAuthTokens } from '@modelcontextprotocol/core'; -import { OAuthError, OAuthErrorCode, SdkErrorCode, SdkHttpError } from '@modelcontextprotocol/core'; +import type { JSONRPCMessage, OAuthTokens } from '@modelcontextprotocol/core-internal'; +import { OAuthError, OAuthErrorCode, SdkErrorCode, SdkHttpError } from '@modelcontextprotocol/core-internal'; import { listenOnRandomPort } from '@modelcontextprotocol/test-helpers'; import type { Mock, Mocked, MockedFunction, MockInstance } from 'vitest'; diff --git a/packages/client/test/client/stdio.test.ts b/packages/client/test/client/stdio.test.ts index 6b13d7d819..594ad6dc63 100644 --- a/packages/client/test/client/stdio.test.ts +++ b/packages/client/test/client/stdio.test.ts @@ -1,4 +1,4 @@ -import type { JSONRPCMessage } from '@modelcontextprotocol/core'; +import type { JSONRPCMessage } from '@modelcontextprotocol/core-internal'; import type { StdioServerParameters } from '../../src/client/stdio'; import { StdioClientTransport } from '../../src/client/stdio'; diff --git a/packages/client/test/client/streamableHttp.test.ts b/packages/client/test/client/streamableHttp.test.ts index ef152d5892..42709717e4 100644 --- a/packages/client/test/client/streamableHttp.test.ts +++ b/packages/client/test/client/streamableHttp.test.ts @@ -1,5 +1,5 @@ -import type { JSONRPCMessage, JSONRPCRequest, OAuthTokens } from '@modelcontextprotocol/core'; -import { OAuthError, OAuthErrorCode, SdkErrorCode, SdkHttpError } from '@modelcontextprotocol/core'; +import type { JSONRPCMessage, JSONRPCRequest, OAuthTokens } from '@modelcontextprotocol/core-internal'; +import { OAuthError, OAuthErrorCode, SdkErrorCode, SdkHttpError } from '@modelcontextprotocol/core-internal'; import type { Mock, Mocked } from 'vitest'; import type { OAuthClientProvider } from '../../src/client/auth'; diff --git a/packages/client/test/client/tokenProvider.test.ts b/packages/client/test/client/tokenProvider.test.ts index 89c2c69041..71cf11ab58 100644 --- a/packages/client/test/client/tokenProvider.test.ts +++ b/packages/client/test/client/tokenProvider.test.ts @@ -1,8 +1,8 @@ import type { IncomingMessage, Server } from 'node:http'; import { createServer } from 'node:http'; -import type { JSONRPCMessage, OAuthClientInformation, OAuthClientMetadata, OAuthTokens } from '@modelcontextprotocol/core'; -import { SdkErrorCode, SdkHttpError } from '@modelcontextprotocol/core'; +import type { JSONRPCMessage, OAuthClientInformation, OAuthClientMetadata, OAuthTokens } from '@modelcontextprotocol/core-internal'; +import { SdkErrorCode, SdkHttpError } from '@modelcontextprotocol/core-internal'; import { listenOnRandomPort } from '@modelcontextprotocol/test-helpers'; import type { Mock } from 'vitest'; diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json index 5f47efeceb..f351f4cb76 100644 --- a/packages/client/tsconfig.json +++ b/packages/client/tsconfig.json @@ -5,11 +5,15 @@ "compilerOptions": { "paths": { "*": ["./*"], - "@modelcontextprotocol/core": ["./node_modules/@modelcontextprotocol/core/src/index.ts"], - "@modelcontextprotocol/core/public": ["./node_modules/@modelcontextprotocol/core/src/exports/public/index.ts"], - "@modelcontextprotocol/core/validators/ajv": ["./node_modules/@modelcontextprotocol/core/src/validators/ajvProvider.ts"], - "@modelcontextprotocol/core/validators/cfWorker": [ - "./node_modules/@modelcontextprotocol/core/src/validators/cfWorkerProvider.ts" + "@modelcontextprotocol/core-internal": ["./node_modules/@modelcontextprotocol/core-internal/src/index.ts"], + "@modelcontextprotocol/core-internal/public": [ + "./node_modules/@modelcontextprotocol/core-internal/src/exports/public/index.ts" + ], + "@modelcontextprotocol/core-internal/validators/ajv": [ + "./node_modules/@modelcontextprotocol/core-internal/src/validators/ajvProvider.ts" + ], + "@modelcontextprotocol/core-internal/validators/cfWorker": [ + "./node_modules/@modelcontextprotocol/core-internal/src/validators/cfWorkerProvider.ts" ], "@modelcontextprotocol/test-helpers": ["./node_modules/@modelcontextprotocol/test-helpers/src/index.ts"], "@modelcontextprotocol/client/_shims": ["./src/shimsNode.ts"] diff --git a/packages/client/tsdown.config.ts b/packages/client/tsdown.config.ts index 773e07c920..883fbf6f40 100644 --- a/packages/client/tsdown.config.ts +++ b/packages/client/tsdown.config.ts @@ -24,13 +24,13 @@ export default defineConfig({ compilerOptions: { baseUrl: '.', paths: { - '@modelcontextprotocol/core': ['../core/src/index.ts'], - '@modelcontextprotocol/core/public': ['../core/src/exports/public/index.ts'], - '@modelcontextprotocol/core/validators/ajv': ['../core/src/validators/ajvProvider.ts'], - '@modelcontextprotocol/core/validators/cfWorker': ['../core/src/validators/cfWorkerProvider.ts'] + '@modelcontextprotocol/core-internal': ['../core-internal/src/index.ts'], + '@modelcontextprotocol/core-internal/public': ['../core-internal/src/exports/public/index.ts'], + '@modelcontextprotocol/core-internal/validators/ajv': ['../core-internal/src/validators/ajvProvider.ts'], + '@modelcontextprotocol/core-internal/validators/cfWorker': ['../core-internal/src/validators/cfWorkerProvider.ts'] } } }, - noExternal: ['@modelcontextprotocol/core', 'ajv', 'ajv-formats', '@cfworker/json-schema'], + noExternal: ['@modelcontextprotocol/core-internal', 'ajv', 'ajv-formats', '@cfworker/json-schema'], external: ['@modelcontextprotocol/client/_shims'] }); diff --git a/packages/codemod/CHANGELOG.md b/packages/codemod/CHANGELOG.md new file mode 100644 index 0000000000..4fbc0a3f9c --- /dev/null +++ b/packages/codemod/CHANGELOG.md @@ -0,0 +1,33 @@ +# @modelcontextprotocol/codemod + +## 2.0.0-alpha.1 + +### Minor Changes + +- [#2354](https://github.com/modelcontextprotocol/typescript-sdk/pull/2354) [`0fb8406`](https://github.com/modelcontextprotocol/typescript-sdk/commit/0fb8406d83a3578a12a605e1b43c352d565071b1) Thanks [@KKonstantinov](https://github.com/KKonstantinov)! - Route v1 + `@modelcontextprotocol/sdk/types.js` schema imports to the new `@modelcontextprotocol/core` package. The `*Schema` Zod constants now migrate as a behavior-preserving import-path swap — `Schema.parse(value)` / `.safeParse(value)` keep working — while spec types, error + classes, enums, and guards continue to resolve to `@modelcontextprotocol/client` / `@modelcontextprotocol/server` by context. A single `import { CallToolResult, CallToolResultSchema } from '.../types.js'` is split accordingly. The v1 OAuth/OpenID `*Schema` constants imported + from `@modelcontextprotocol/sdk/shared/auth.js` are routed to `@modelcontextprotocol/core` the same way (their auth TYPES keep resolving to `client` / `server`). The previous `specSchemaAccess` transform (which rewrote `.parse()` into + `specTypeSchemas.X['~standard'].validate(...)`) is removed. + +- [#2206](https://github.com/modelcontextprotocol/typescript-sdk/pull/2206) [`e03bca9`](https://github.com/modelcontextprotocol/typescript-sdk/commit/e03bca90c1f925f80843dc27fb4eb2421408a0c1) Thanks [@KKonstantinov](https://github.com/KKonstantinov)! - Codemod now resolves SSE + server and OAuth auth imports to @modelcontextprotocol/server-legacy sub-paths instead of removing them. An info diagnostic suggests eventual migration to v2 equivalents. + +### Patch Changes + +- [#2354](https://github.com/modelcontextprotocol/typescript-sdk/pull/2354) [`0fb8406`](https://github.com/modelcontextprotocol/typescript-sdk/commit/0fb8406d83a3578a12a605e1b43c352d565071b1) Thanks [@KKonstantinov](https://github.com/KKonstantinov)! - Infer client/server project + type from source for v1 projects. A project being migrated still declares the single v1 `@modelcontextprotocol/sdk` dependency, so detecting the project type from `package.json` came back "unknown" and every file importing only shared protocol symbols defaulted to + `@modelcontextprotocol/server` with an action-required warning. The codemod now scans the source for quoted `@modelcontextprotocol/sdk/client/…` and `…/server/…` import specifiers to infer the type (both → "both", one → that side, neither → "unknown"), routing shared symbols to + the installed package and replacing the spurious warnings with at most an info note for genuinely ambiguous "both" projects. + +- [#2137](https://github.com/modelcontextprotocol/typescript-sdk/pull/2137) [`542d5c9`](https://github.com/modelcontextprotocol/typescript-sdk/commit/542d5c95860c03d0c1a689f579b925250e25de6c) Thanks [@KKonstantinov](https://github.com/KKonstantinov)! - The v1→v2 codemod now + migrates the removed `StreamableHTTPError` to `SdkHttpError` (instead of the base `SdkError`), matching the shipped error type and the migration guide. Diagnostics now point at the typed `error.status` / `error.statusText` accessors and note that unexpected-content-type + responses are thrown as the base `SdkError`. + +- [#2354](https://github.com/modelcontextprotocol/typescript-sdk/pull/2354) [`0fb8406`](https://github.com/modelcontextprotocol/typescript-sdk/commit/0fb8406d83a3578a12a605e1b43c352d565071b1) Thanks [@KKonstantinov](https://github.com/KKonstantinov)! - Map the task + request/notification schemas to their v2 method strings in the handler-registration transform. `setRequestHandler(GetTaskRequestSchema, …)`, `setNotificationHandler(TaskStatusNotificationSchema, …)`, and the other task handlers (`tasks/get`, `tasks/result`, `tasks/list`, + `tasks/cancel`, `notifications/tasks/status`) now rewrite to the v2 two-argument method-string form instead of falling through to the generic "use the 3-argument form" manual-migration diagnostic. + +- [#2252](https://github.com/modelcontextprotocol/typescript-sdk/pull/2252) [`8d55531`](https://github.com/modelcontextprotocol/typescript-sdk/commit/8d55531dabd5aa2de8864d691520cd6c6fe77541) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Add per-revision spec + reference types (2025-11-25 and 2026-07-28) with split comparison tests, and the 2026-07-28 wire contract surface: request-meta key constants, `RequestMetaEnvelopeSchema`, `server/discover` shapes, the typed `-32004` error, the `-32003` code constant, and a `resultType` + passthrough on the base result. Types and constants only — no behavior changes. diff --git a/packages/codemod/batch-test/repos.json b/packages/codemod/batch-test/repos.json index e8d5515800..28dbc4348d 100644 --- a/packages/codemod/batch-test/repos.json +++ b/packages/codemod/batch-test/repos.json @@ -1,40 +1,14 @@ [ { - "repo": "modelcontextprotocol/servers", + "repo": "firebase/firebase-tools", "ref": "main", "packages": [ { - "dir": "src/everything", - "sourceDir": ".", + "dir": ".", + "sourceDir": "src/mcp", "checks": { - "typecheck": "npx tsc --noEmit", - "build": "npm run build", - "test": "npm run test", - "lint": "npm run prettier:check" - } - } - ] - }, - { - "repo": "modelcontextprotocol/inspector", - "ref": "main", - "packages": [ - { - "dir": "client", - "sourceDir": "src", - "checks": { - "typecheck": "npx tsc --noEmit", - "build": "npm run build", - "test": "npm run test", - "lint": "npm run lint" - } - }, - { - "dir": "server", - "sourceDir": "src", - "checks": { - "typecheck": "npx tsc --noEmit", - "build": "npm run build", + "typecheck": "npx tsc -p tsconfig.compile.json", + "build": null, "test": null, "lint": null } diff --git a/packages/codemod/package.json b/packages/codemod/package.json index 264f973ac6..fa4a15ec3e 100644 --- a/packages/codemod/package.json +++ b/packages/codemod/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/codemod", - "version": "2.0.0-alpha.0", + "version": "2.0.0-alpha.1", "description": "Codemod to migrate MCP TypeScript SDK code from v1 to v2", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -35,8 +35,7 @@ "scripts": { "typecheck": "tsgo -p tsconfig.json --noEmit", "generate:versions": "tsx scripts/generateVersions.ts", - "generate:spec-schemas": "tsx scripts/generateSpecSchemaMap.ts", - "prebuild": "pnpm run generate:versions && pnpm run generate:spec-schemas", + "prebuild": "pnpm run generate:versions", "build": "tsdown", "build:watch": "tsdown --watch", "prepack": "pnpm run build", diff --git a/packages/codemod/scripts/generateSpecSchemaMap.ts b/packages/codemod/scripts/generateSpecSchemaMap.ts deleted file mode 100644 index 29796f62ab..0000000000 --- a/packages/codemod/scripts/generateSpecSchemaMap.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { readFileSync, writeFileSync } from 'node:fs'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const specTypeSchemaPath = path.resolve(__dirname, '../../core/src/types/specTypeSchema.ts'); - -const source = readFileSync(specTypeSchemaPath, 'utf8'); - -// Extract SPEC_SCHEMA_KEYS array entries -const keysMatch = source.match(/const SPEC_SCHEMA_KEYS = \[([\s\S]*?)\] as const/); -if (!keysMatch) throw new Error('Could not find SPEC_SCHEMA_KEYS in specTypeSchema.ts'); - -const protocolSchemas = [...keysMatch[1]!.matchAll(/'([^']+)'/g)].map(m => m[1]!); - -// Extract auth schema keys -const authMatch = source.match(/const authSchemas = \{([\s\S]*?)\} as const/); -if (!authMatch) throw new Error('Could not find authSchemas in specTypeSchema.ts'); - -const authSchemas = [...authMatch[1]!.matchAll(/(\w+Schema)/g)].map(m => m[1]!); - -const allSchemas = [...protocolSchemas, ...authSchemas].toSorted(); - -const entries = allSchemas.map((s, i) => ` '${s}'${i < allSchemas.length - 1 ? ',' : ''}`).join('\n'); - -const output = `// AUTO-GENERATED — do not edit. Run \`pnpm run generate:spec-schemas\` to regenerate. -export const SPEC_SCHEMA_NAMES: ReadonlySet = new Set([ -${entries} -]); - -export function specSchemaToTypeName(schemaName: string): string | undefined { - if (!SPEC_SCHEMA_NAMES.has(schemaName)) return undefined; - return schemaName.slice(0, -'Schema'.length); -} -`; - -const outPath = path.resolve(__dirname, '../src/generated/specSchemaMap.ts'); -writeFileSync(outPath, output); -console.log(`Wrote ${outPath} (${allSchemas.length} schemas)`); diff --git a/packages/codemod/scripts/generateVersions.ts b/packages/codemod/scripts/generateVersions.ts index 3a77b592bf..f4d827d76d 100644 --- a/packages/codemod/scripts/generateVersions.ts +++ b/packages/codemod/scripts/generateVersions.ts @@ -10,7 +10,8 @@ const PACKAGE_DIRS: Record = { '@modelcontextprotocol/server': 'server', '@modelcontextprotocol/node': 'middleware/node', '@modelcontextprotocol/express': 'middleware/express', - '@modelcontextprotocol/server-legacy': 'server-legacy' + '@modelcontextprotocol/server-legacy': 'server-legacy', + '@modelcontextprotocol/core': 'core' }; const versions: Record = {}; diff --git a/packages/codemod/src/bin/batchTest.ts b/packages/codemod/src/bin/batchTest.ts index 7c9761cb69..f935e90ca5 100644 --- a/packages/codemod/src/bin/batchTest.ts +++ b/packages/codemod/src/bin/batchTest.ts @@ -85,9 +85,10 @@ const BATCH_DIR = path.resolve(SDK_ROOT, 'packages/codemod/batch-test'); const LOCAL_PACKAGE_DIRS: Record = { '@modelcontextprotocol/client': path.join(SDK_ROOT, 'packages/client'), - '@modelcontextprotocol/core': path.join(SDK_ROOT, 'packages/core'), + '@modelcontextprotocol/core-internal': path.join(SDK_ROOT, 'packages/core-internal'), '@modelcontextprotocol/server': path.join(SDK_ROOT, 'packages/server'), '@modelcontextprotocol/server-legacy': path.join(SDK_ROOT, 'packages/server-legacy'), + '@modelcontextprotocol/core': path.join(SDK_ROOT, 'packages/core'), '@modelcontextprotocol/express': path.join(SDK_ROOT, 'packages/middleware/express'), '@modelcontextprotocol/fastify': path.join(SDK_ROOT, 'packages/middleware/fastify'), '@modelcontextprotocol/hono': path.join(SDK_ROOT, 'packages/middleware/hono'), @@ -110,6 +111,22 @@ function detectPm(repoRoot: string): string { return 'npm'; } +function installCommand(pm: string): string { + if (pm !== 'pnpm') return `${pm} install --ignore-scripts`; + // pnpm walks up to find a workspace; clones live inside this SDK's pnpm workspace, so a plain + // `pnpm install` targets the OUTER workspace and never populates the clone's node_modules — every + // downstream check (tsc base config, tsup, vitest) then fails identically at baseline and post, + // masking real codemod signal. + // --ignore-workspace: treat the clone as a standalone project (not part of the SDK workspace). + // --no-frozen-lockfile: the codemod rewrites package.json to swap v1 → v2 deps, so the lockfile + // must be allowed to change. CI=true (set in shell()) otherwise defaults + // pnpm to a frozen lockfile and the post-codemod reinstall silently skips + // the new v2 deps, leaving the clone on v1. + // npm/yarn/bun key off a `workspaces` field in package.json (absent at this repo root), so they + // need no equivalent flags. + return 'pnpm install --ignore-scripts --ignore-workspace --no-frozen-lockfile'; +} + function detectCheckCmd(pkgDir: string, checkType: string): string | null { const pkgJsonPath = path.join(pkgDir, 'package.json'); if (!existsSync(pkgJsonPath)) return null; @@ -132,7 +149,11 @@ function shell(cmd: string, cwd?: string): { exitCode: number; stdout: string; s cwd, stdio: ['pipe', 'pipe', 'pipe'], maxBuffer: 10 * 1024 * 1024, - timeout: 5 * 60 * 1000 + timeout: 5 * 60 * 1000, + // Commands are spawned without a TTY (piped stdio). Set CI so package managers run fully + // non-interactively — without it, pnpm aborts rebuilding a clone's modules dir with + // ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY when --ignore-workspace changes its link mode. + env: { ...process.env, CI: 'true' } }).toString(); return { exitCode: 0, stdout, stderr: '' }; } catch (error: unknown) { @@ -318,7 +339,7 @@ function main(): void { // Step 3: Install console.log(' Installing dependencies...'); - const installResult = shell(`${pm} install --ignore-scripts`, clonePath); + const installResult = shell(installCommand(pm), clonePath); if (installResult.exitCode !== 0) { console.log(` ERROR: install failed, skipping\n ${installResult.stderr.split('\n')[0]}`); continue; @@ -366,7 +387,7 @@ function main(): void { console.log(` Rewrote ${rewrites} deps to local tarballs`); } console.log(' Re-installing dependencies...'); - shell(`${pm} install --ignore-scripts`, clonePath); + shell(installCommand(pm), clonePath); // Step 7: Post-codemod checks console.log(' Running post-codemod checks...'); diff --git a/packages/codemod/src/cli.ts b/packages/codemod/src/cli.ts index a1c467761f..8325b352e6 100644 --- a/packages/codemod/src/cli.ts +++ b/packages/codemod/src/cli.ts @@ -9,11 +9,36 @@ import { Command } from 'commander'; import { listMigrations } from './migrations/index'; import { run } from './runner'; import { DiagnosticLevel } from './types'; +import { detectFormatter } from './utils/detectFormatter'; import { CODEMOD_ERROR_PREFIX, formatDiagnostic } from './utils/diagnostics'; const require = createRequire(import.meta.url); const { version } = require('../package.json') as { version: string }; +function quoteArg(arg: string): string { + return /\s/.test(arg) ? `"${arg}"` : arg; +} + +/** + * The codemod transforms the AST but does not reformat — wrapped schemas and + * generated string literals can violate a repo's lint/formatting rules. Point + * the user at their own formatter (which respects their config) for the exact + * files that changed. + */ +function printFormatGuidance(targetDir: string, changedFiles: string[]): void { + if (changedFiles.length === 0) return; + + const formatter = detectFormatter(targetDir); + const fileArgs = changedFiles.map(file => quoteArg(path.relative(process.cwd(), file) || file)); + + console.log("This codemod doesn't reformat its output. Run your formatter on the changed file(s):"); + if (formatter) { + console.log(` ${formatter.bin} ${[...formatter.writeArgs, ...fileArgs].join(' ')}\n`); + } else { + console.log(` e.g. prettier --write ${fileArgs.join(' ')}\n`); + } +} + const program = new Command(); program.name('mcp-codemod').description('Codemod to migrate MCP TypeScript SDK code between versions').version(version); @@ -150,6 +175,8 @@ for (const [name, migration] of listMigrations()) { if (opts['dryRun']) { console.log('Run without --dry-run to apply changes.\n'); } else { + const changedFiles = result.fileResults.filter(fr => fr.changes > 0).map(fr => fr.filePath); + printFormatGuidance(resolvedDir, changedFiles); if (result.packageJsonChanges) { console.log('Run your package manager to install the new packages.\n'); } diff --git a/packages/codemod/src/generated/versions.ts b/packages/codemod/src/generated/versions.ts index 7166154021..4fa12a1a87 100644 --- a/packages/codemod/src/generated/versions.ts +++ b/packages/codemod/src/generated/versions.ts @@ -4,5 +4,6 @@ export const V2_PACKAGE_VERSIONS: Record = { '@modelcontextprotocol/server': '^2.0.0-alpha.2', '@modelcontextprotocol/node': '^2.0.0-alpha.2', '@modelcontextprotocol/express': '^2.0.0-alpha.2', - '@modelcontextprotocol/server-legacy': '^2.0.0-alpha.2' + '@modelcontextprotocol/server-legacy': '^2.0.0-alpha.2', + '@modelcontextprotocol/core': '^2.0.0-alpha.0' }; diff --git a/packages/codemod/src/migrations/v1-to-v2/mappings/authSchemaNames.ts b/packages/codemod/src/migrations/v1-to-v2/mappings/authSchemaNames.ts new file mode 100644 index 0000000000..89a9083b15 --- /dev/null +++ b/packages/codemod/src/migrations/v1-to-v2/mappings/authSchemaNames.ts @@ -0,0 +1,30 @@ +// The auth (OAuth / OpenID) Zod schema CONSTANTS that v1 exported from +// `@modelcontextprotocol/sdk/shared/auth.js` and that core still re-exports in v2. The import +// transform routes a `*Schema` symbol imported from that v1 path to core when its name is in this +// set (the corresponding TYPES, e.g. OAuthTokens, resolve by context to @modelcontextprotocol/client | +// /server). This is the v1 auth-schema set — a SUBSET of core's auth exports. v2-only auth schemas +// (e.g. IdJagTokenExchangeResponseSchema) are exported by core but NOT listed here: v1 never had +// them, so there is nothing to migrate. test/v1-to-v2/authSchemaNames.test.ts asserts every name here is +// exported by core (so the rewritten import resolves). Keep alphabetized. +export const AUTH_SCHEMA_NAMES: ReadonlySet = new Set([ + 'OAuthClientInformationFullSchema', + 'OAuthClientInformationSchema', + 'OAuthClientMetadataSchema', + 'OAuthClientRegistrationErrorSchema', + 'OAuthErrorResponseSchema', + 'OAuthMetadataSchema', + 'OAuthProtectedResourceMetadataSchema', + 'OAuthTokenRevocationRequestSchema', + 'OAuthTokensSchema', + 'OpenIdProviderDiscoveryMetadataSchema', + 'OpenIdProviderMetadataSchema' +]); + +// v1's `@modelcontextprotocol/sdk/shared/auth.js` also exported these as Zod schema CONSTANTS, but they +// are typeless internal URL field-validators (no public spec type), so v2's core deliberately does +// NOT re-export them (see packages/core/src/index.ts) and no other public v2 package exports them. +// They therefore have no v2 home: routed by context they would produce a codemod-introduced "has no +// exported member" error. importPaths emits an actionRequired diagnostic instead of silently breaking. +// test/v1-to-v2/authSchemaNames.test.ts asserts these are NOT in AUTH_SCHEMA_NAMES (and so are not +// claimed to be core exports). +export const AUTH_SCHEMA_NAMES_NO_V2_PUBLIC_EXPORT: ReadonlySet = new Set(['SafeUrlSchema', 'OptionalSafeUrlSchema']); diff --git a/packages/codemod/src/migrations/v1-to-v2/mappings/importMap.ts b/packages/codemod/src/migrations/v1-to-v2/mappings/importMap.ts index 24d086f8de..b49f7eba38 100644 --- a/packages/codemod/src/migrations/v1-to-v2/mappings/importMap.ts +++ b/packages/codemod/src/migrations/v1-to-v2/mappings/importMap.ts @@ -4,6 +4,17 @@ export interface ImportMapping { renamedSymbols?: Record; /** Route specific symbols to a different target package than `target`. */ symbolTargetOverrides?: Record; + /** + * Route an imported symbol to this package (instead of `target`) when its rename-resolved name is + * a Zod schema constant re-exported by core — a member of `SPEC_SCHEMA_NAMES` (spec schemas, + * for `sdk/types.js`) or `AUTH_SCHEMA_NAMES` (OAuth/OpenID schemas, for `sdk/shared/auth.js`). The + * schemas now live in `@modelcontextprotocol/core` (so `Schema.parse(...)` keeps + * working), while the corresponding types/constants/guards resolve by context. Matching on + * membership (not a `*Schema` suffix) keeps TYPES whose name ends in `Schema` — e.g. the + * elicitation primitives `BooleanSchema`/`StringSchema`/`EnumSchema` — routed by context, where + * their types live. `symbolTargetOverrides` (exact-name) takes precedence. + */ + schemaSymbolTarget?: string; removalMessage?: string; /** No entries currently set this; scaffolding for when a v1 symbol has no v2 equivalent yet. */ isV2Gap?: boolean; @@ -119,6 +130,7 @@ export const IMPORT_MAP: Record = { '@modelcontextprotocol/sdk/types.js': { target: 'RESOLVE_BY_CONTEXT', status: 'moved', + schemaSymbolTarget: '@modelcontextprotocol/core', renamedSymbols: { ResourceTemplate: 'ResourceTemplateType' } @@ -137,7 +149,12 @@ export const IMPORT_MAP: Record = { }, '@modelcontextprotocol/sdk/shared/auth.js': { target: 'RESOLVE_BY_CONTEXT', - status: 'moved' + status: 'moved', + // OAuth/OpenID Zod schema constants (AUTH_SCHEMA_NAMES) are re-exported by core as a + // separate group, so route them there (keeping `OAuthTokensSchema.parse(...)` working). The + // OAuth/OpenID TYPES (OAuthTokens, etc.) carry no `schemaSymbolTarget` match and resolve by + // context to @modelcontextprotocol/client | /server. + schemaSymbolTarget: '@modelcontextprotocol/core' }, '@modelcontextprotocol/sdk/shared/stdio.js': { target: 'RESOLVE_BY_CONTEXT', @@ -190,3 +207,28 @@ for (const barrelSpecifier of ['@modelcontextprotocol/sdk/validation/index.js', export function isAuthImport(specifier: string): boolean { return specifier.includes('/server/auth/') || specifier.includes('/server/auth.'); } + +// SDK subpath specifiers can be written with or without a JS extension +// (e.g. `@modelcontextprotocol/sdk/types` vs `.../types.js`) depending on the +// consumer's module resolution (`bundler`/`nodenext` allow the extensionless form). +// Normalize the extension so both spellings resolve to the same mapping. Built +// after every IMPORT_MAP entry above is populated; entries whose `.js` and +// extensionless forms coexist (e.g. `experimental/tasks`) share an identical +// mapping, so the collapse is lossless. +function stripJsExtension(specifier: string): string { + return specifier.replace(/\.(?:js|mjs|cjs)$/, ''); +} + +const NORMALIZED_IMPORT_MAP: Record = {}; +for (const [key, mapping] of Object.entries(IMPORT_MAP)) { + NORMALIZED_IMPORT_MAP[stripJsExtension(key)] = mapping; +} + +/** + * Resolves the v2 mapping for a v1 SDK import/export/mock specifier, tolerating + * JS extension variance. An exact match always wins; otherwise the specifier is + * matched ignoring a trailing `.js`/`.mjs`/`.cjs` (or its absence). + */ +export function lookupImportMapping(specifier: string): ImportMapping | undefined { + return IMPORT_MAP[specifier] ?? NORMALIZED_IMPORT_MAP[stripJsExtension(specifier)]; +} diff --git a/packages/codemod/src/migrations/v1-to-v2/mappings/schemaRouting.ts b/packages/codemod/src/migrations/v1-to-v2/mappings/schemaRouting.ts new file mode 100644 index 0000000000..7aead7c17a --- /dev/null +++ b/packages/codemod/src/migrations/v1-to-v2/mappings/schemaRouting.ts @@ -0,0 +1,39 @@ +// Shared per-symbol routing logic for v1→v2 import/export/mock rewrites. Centralized here so the +// import-path transform (static imports/re-exports) and the mock-path transform (vi.mock/jest.mock +// factories, dynamic import() destructurings) route a given symbol to exactly the same v2 package. +import { AUTH_SCHEMA_NAMES } from './authSchemaNames'; +import type { ImportMapping } from './importMap'; +import { SPEC_SCHEMA_NAMES } from './specSchemaNames'; +import { SIMPLE_RENAMES } from './symbolMap'; + +/** The v2 name a symbol resolves to after renames (per-mapping override, then global SIMPLE_RENAMES). */ +export function resolveRenamedName(name: string, mapping: ImportMapping): string { + return mapping.renamedSymbols?.[name] ?? SIMPLE_RENAMES[name] ?? name; +} + +/** + * True when `name` (after renames) is a Zod schema CONSTANT that core re-exports — either a spec + * schema (`SPEC_SCHEMA_NAMES`) or an OAuth/OpenID schema (`AUTH_SCHEMA_NAMES`). Membership (not a + * `*Schema` suffix) is what keeps TYPES whose name ends in `Schema` — e.g. `BooleanSchema` — out. + */ +export function isSharedSchemaConst(name: string, mapping: ImportMapping): boolean { + const resolved = resolveRenamedName(name, mapping); + return SPEC_SCHEMA_NAMES.has(resolved) || AUTH_SCHEMA_NAMES.has(resolved); +} + +/** + * The per-symbol target package for a symbol imported/re-exported/mocked from `mapping`'s module, or + * `undefined` when the symbol should use the mapping's resolved `target`. Exact-name + * `symbolTargetOverrides` win over `schemaSymbolTarget`, which routes a symbol to the shared-schemas + * package only when its rename-resolved name is a schema constant re-exported by core (see + * `isSharedSchemaConst`). + */ +export function symbolTargetOverride(name: string, mapping: ImportMapping): string | undefined { + if (mapping.symbolTargetOverrides && name in mapping.symbolTargetOverrides) { + return mapping.symbolTargetOverrides[name]; + } + if (mapping.schemaSymbolTarget && isSharedSchemaConst(name, mapping)) { + return mapping.schemaSymbolTarget; + } + return undefined; +} diff --git a/packages/codemod/src/migrations/v1-to-v2/mappings/schemaToMethodMap.ts b/packages/codemod/src/migrations/v1-to-v2/mappings/schemaToMethodMap.ts index daa7278c8f..783cf52875 100644 --- a/packages/codemod/src/migrations/v1-to-v2/mappings/schemaToMethodMap.ts +++ b/packages/codemod/src/migrations/v1-to-v2/mappings/schemaToMethodMap.ts @@ -14,7 +14,11 @@ export const SCHEMA_TO_METHOD: Record = { SetLevelRequestSchema: 'logging/setLevel', PingRequestSchema: 'ping', CompleteRequestSchema: 'completion/complete', - ListRootsRequestSchema: 'roots/list' + ListRootsRequestSchema: 'roots/list', + GetTaskRequestSchema: 'tasks/get', + GetTaskPayloadRequestSchema: 'tasks/result', + ListTasksRequestSchema: 'tasks/list', + CancelTaskRequestSchema: 'tasks/cancel' }; export const NOTIFICATION_SCHEMA_TO_METHOD: Record = { @@ -27,5 +31,6 @@ export const NOTIFICATION_SCHEMA_TO_METHOD: Record = { CancelledNotificationSchema: 'notifications/cancelled', InitializedNotificationSchema: 'notifications/initialized', RootsListChangedNotificationSchema: 'notifications/roots/list_changed', - ElicitationCompleteNotificationSchema: 'notifications/elicitation/complete' + ElicitationCompleteNotificationSchema: 'notifications/elicitation/complete', + TaskStatusNotificationSchema: 'notifications/tasks/status' }; diff --git a/packages/codemod/src/generated/specSchemaMap.ts b/packages/codemod/src/migrations/v1-to-v2/mappings/specSchemaNames.ts similarity index 87% rename from packages/codemod/src/generated/specSchemaMap.ts rename to packages/codemod/src/migrations/v1-to-v2/mappings/specSchemaNames.ts index 77f3d3dfc8..55712252bc 100644 --- a/packages/codemod/src/generated/specSchemaMap.ts +++ b/packages/codemod/src/migrations/v1-to-v2/mappings/specSchemaNames.ts @@ -1,4 +1,11 @@ -// AUTO-GENERATED — do not edit. Run `pnpm run generate:spec-schemas` to regenerate. +// AUTO-VERIFIED against @modelcontextprotocol/core's public exports by +// test/v1-to-v2/specSchemaNames.test.ts (drift guard). These are the spec Zod schema CONSTANTS that +// core re-exports as standalone values; the v1->v2 import transform routes a `*Schema` symbol +// imported from `@modelcontextprotocol/sdk/types.js` to core ONLY when its (rename-resolved) +// name is in this set. Names that merely END in `Schema` but are NOT here — e.g. the elicitation +// primitive TYPES `BooleanSchema`/`StringSchema`/`EnumSchema` (whose Zod const is `SchemaSchema`) +// — fall through to context resolution (@modelcontextprotocol/client | /server), where their TYPES +// live. Keep alphabetized. export const SPEC_SCHEMA_NAMES: ReadonlySet = new Set([ 'AnnotationsSchema', 'AudioContentSchema', @@ -84,17 +91,6 @@ export const SPEC_SCHEMA_NAMES: ReadonlySet = new Set([ 'MultiSelectEnumSchemaSchema', 'NotificationSchema', 'NumberSchemaSchema', - 'OAuthClientInformationFullSchema', - 'OAuthClientInformationSchema', - 'OAuthClientMetadataSchema', - 'OAuthClientRegistrationErrorSchema', - 'OAuthErrorResponseSchema', - 'OAuthMetadataSchema', - 'OAuthProtectedResourceMetadataSchema', - 'OAuthTokenRevocationRequestSchema', - 'OAuthTokensSchema', - 'OpenIdProviderDiscoveryMetadataSchema', - 'OpenIdProviderMetadataSchema', 'PaginatedRequestParamsSchema', 'PaginatedRequestSchema', 'PaginatedResultSchema', @@ -166,8 +162,3 @@ export const SPEC_SCHEMA_NAMES: ReadonlySet = new Set([ 'UntitledMultiSelectEnumSchemaSchema', 'UntitledSingleSelectEnumSchemaSchema' ]); - -export function specSchemaToTypeName(schemaName: string): string | undefined { - if (!SPEC_SCHEMA_NAMES.has(schemaName)) return undefined; - return schemaName.slice(0, -'Schema'.length); -} diff --git a/packages/codemod/src/migrations/v1-to-v2/mappings/symbolMap.ts b/packages/codemod/src/migrations/v1-to-v2/mappings/symbolMap.ts index 7671a3ee0d..b2670dd097 100644 --- a/packages/codemod/src/migrations/v1-to-v2/mappings/symbolMap.ts +++ b/packages/codemod/src/migrations/v1-to-v2/mappings/symbolMap.ts @@ -4,6 +4,15 @@ export const SIMPLE_RENAMES: Record = { JSONRPCErrorSchema: 'JSONRPCErrorResponseSchema', isJSONRPCError: 'isJSONRPCErrorResponse', isJSONRPCResponse: 'isJSONRPCResultResponse', + // v1's JSONRPCResponse type / JSONRPCResponseSchema constant both validated only *result* + // responses. v2 reuses each name for the result|error form — the type becomes + // `Infer` and the schema the + // matching `z.union([...])` — so a migrated `JSONRPCResponseSchema.parse(...)` (or a typed + // `JSONRPCResponse` value) would silently widen. Rename both to the result-only equivalents to + // preserve v1 behavior — mirroring the isJSONRPCResponse guard rename above. Both the type and the + // schema are public in v2 (re-exported from core via @modelcontextprotocol/client | /server). + JSONRPCResponse: 'JSONRPCResultResponse', + JSONRPCResponseSchema: 'JSONRPCResultResponseSchema', ResourceReference: 'ResourceTemplateReference', ResourceReferenceSchema: 'ResourceTemplateReferenceSchema' }; diff --git a/packages/codemod/src/migrations/v1-to-v2/transforms/importPaths.ts b/packages/codemod/src/migrations/v1-to-v2/transforms/importPaths.ts index 008b576686..89397901f8 100644 --- a/packages/codemod/src/migrations/v1-to-v2/transforms/importPaths.ts +++ b/packages/codemod/src/migrations/v1-to-v2/transforms/importPaths.ts @@ -1,11 +1,15 @@ import type { SourceFile } from 'ts-morph'; +import { SyntaxKind } from 'ts-morph'; import type { Diagnostic, Transform, TransformContext, TransformResult } from '../../../types'; import { renameAllReferences } from '../../../utils/astUtils'; import { actionRequired, info, v2Gap, warning } from '../../../utils/diagnostics'; +import type { NamedImportSpec } from '../../../utils/importUtils'; import { addOrMergeImport, getSdkExports, getSdkImports, isTypeOnlyImport } from '../../../utils/importUtils'; import { resolveTypesPackage } from '../../../utils/projectAnalyzer'; -import { IMPORT_MAP, isAuthImport } from '../mappings/importMap'; +import { AUTH_SCHEMA_NAMES_NO_V2_PUBLIC_EXPORT } from '../mappings/authSchemaNames'; +import { isAuthImport, lookupImportMapping } from '../mappings/importMap'; +import { isSharedSchemaConst, resolveRenamedName, symbolTargetOverride } from '../mappings/schemaRouting'; import { SIMPLE_RENAMES } from '../mappings/symbolMap'; const REEXPORT_WARNINGS: Record = { @@ -50,17 +54,29 @@ export const importPathsTransform: Transform = { const insertIndex = sourceFile.getImportDeclarations().indexOf(sdkImports[0]!); + // A leading file-header / JSDoc comment attaches to the first SDK import as leading trivia. When + // that import is removed and re-emitted (the per-symbol split/merge path calls imp.remove()), + // ts-morph drops the comment with it. Capture it now and restore it after emitting if it was lost. + // Capture the EXACT source bytes spanning all leading comment ranges (first range's start to last + // range's end) rather than re-joining each range with '\n' — a join drops the original separators + // (a blank line, or CRLF in CRLF files), so the later survival check would never match a header + // that actually survived (in-place setModuleSpecifier rewrite) and would re-insert it, duplicating + // it. The slice reproduces the block verbatim, so the includes() guard below is byte-exact. + const leadingRanges = sdkImports[0]!.getLeadingCommentRanges(); + const leadingCommentText = + leadingRanges.length > 0 ? sourceFile.getFullText().slice(leadingRanges[0]!.getPos(), leadingRanges.at(-1)!.getEnd()) : ''; + interface PendingImport { - names: string[]; + specs: NamedImportSpec[]; isTypeOnly: boolean; } const pendingImports = new Map(); - function addPending(target: string, names: string[], isTypeOnly: boolean): void { + function addPending(target: string, specs: NamedImportSpec[], isTypeOnly: boolean): void { if (!pendingImports.has(target)) { pendingImports.set(target, []); } - pendingImports.get(target)!.push({ names, isTypeOnly }); + pendingImports.get(target)!.push({ specs, isTypeOnly }); } for (const imp of sdkImports) { @@ -71,7 +87,7 @@ export const importPathsTransform: Transform = { const defaultImport = imp.getDefaultImport(); const namespaceImport = imp.getNamespaceImport(); - let mapping = IMPORT_MAP[specifier]; + let mapping = lookupImportMapping(specifier); if (!mapping && isAuthImport(specifier)) { mapping = { @@ -94,15 +110,22 @@ export const importPathsTransform: Transform = { continue; } + // Resolve a RESOLVE_BY_CONTEXT mapping (sdk/types.js, sdk/shared/auth.js) only when a binding + // actually routes to the context package. resolveTypesPackage's diagnostic sink emits a "could + // not determine project type" warning (or, for a 'both' project, an info note), so resolving + // eagerly would emit that note even for an import of nothing but `*Schema` constants — which + // routes entirely to core and never uses the context package. A namespace or default + // binding always needs context; a named symbol needs it only when it has no per-symbol override + // (i.e. it is not a `*Schema` routed to core). let targetPackage = mapping.target; if (targetPackage === 'RESOLVE_BY_CONTEXT') { - targetPackage = resolveTypesPackage(context, hasClientImport, hasServerImport, { - filePath, - line, - diagnostics - }); - if (mapping.subpathSuffix) { - targetPackage = `${targetPackage}${mapping.subpathSuffix}`; + const needsContext = + namespaceImport != null || + defaultImport != null || + namedImports.some(n => symbolTargetOverride(n.getName(), mapping!) === undefined); + if (needsContext) { + const base = resolveTypesPackage(context, hasClientImport, hasServerImport, { filePath, line, diagnostics }); + targetPackage = mapping.subpathSuffix ? `${base}${mapping.subpathSuffix}` : base; } } @@ -116,20 +139,42 @@ export const importPathsTransform: Transform = { } } - const hasAlias = namedImports.some(n => n.getAliasNode() !== undefined); - if (defaultImport || namespaceImport || hasAlias) { - let effectiveTarget = targetPackage; - if (mapping.symbolTargetOverrides && !namespaceImport && !defaultImport) { - const allOverridden = namedImports.length > 0 && namedImports.every(n => n.getName() in mapping.symbolTargetOverrides!); - if (allOverridden) { - effectiveTarget = mapping.symbolTargetOverrides[namedImports[0]!.getName()]!; - } else if (namedImports.some(n => n.getName() in mapping.symbolTargetOverrides!)) { + // A namespace import (`import * as ns from …`) cannot be split per-symbol — usages are + // qualified (`ns.Foo`), so the whole binding moves to one package. Named imports (aliased or + // not), including the named siblings of a default import, DO fall through to the per-symbol + // splitter below — so an all-`*Schema` import routes entirely to core, a single aliased + // specifier no longer forces unrelated symbols into the wrong package, and a mixed + // `import sdk, { CallToolResultSchema }` routes the schema to core while the default + // binding (handled at the end of the per-symbol path) moves to the context package. + if (namespaceImport) { + const effectiveTarget = targetPackage; + // Any `ns.Schema` accesses would silently resolve against the wrong package (the + // namespace can't be split), so flag them. + if (mapping.schemaSymbolTarget) { + const nsName = namespaceImport.getText(); + // Map each accessed v1 name to the v2 name core actually exports — some are + // renamed (e.g. JSONRPCErrorSchema → JSONRPCErrorResponseSchema), and core only + // exports the v2 name. Dedupe by the accessed (v1) name. + const schemaAccesses = [ + ...new Map( + sourceFile + .getDescendantsOfKind(SyntaxKind.PropertyAccessExpression) + .filter(pa => pa.getExpression().getText() === nsName && isSharedSchemaConst(pa.getName(), mapping)) + .map(pa => [pa.getName(), resolveRenamedName(pa.getName(), mapping)] as const) + ) + ]; + if (schemaAccesses.length > 0) { + const accessed = schemaAccesses.map(([v1]) => v1).join(', '); + const importName = schemaAccesses[0]![1]; + const renamed = schemaAccesses.filter(([v1, v2]) => v1 !== v2); + const renameNote = + renamed.length > 0 ? ` Renamed in v2: ${renamed.map(([v1, v2]) => `${v1} → ${v2}`).join(', ')}.` : ''; diagnostics.push( actionRequired( filePath, imp, - `Aliased import from ${specifier} mixes symbols that belong to different v2 packages. ` + - `Split the import manually so each symbol targets the correct package.` + `Namespace import of ${specifier} is used to access Zod schema(s) (${accessed}) that moved to ${mapping.schemaSymbolTarget}.${renameNote} ` + + `Import them with a named import (e.g. \`import { ${importName} } from '${mapping.schemaSymbolTarget}'\`) and update the qualified usages.` ) ); } @@ -137,22 +182,14 @@ export const importPathsTransform: Transform = { usedPackages.add(effectiveTarget); imp.setModuleSpecifier(effectiveTarget); if (mapping.renamedSymbols) { - for (const n of namedImports) { - const newName = mapping.renamedSymbols[n.getName()]; - if (newName) { - n.setName(newName); - } - } - if (namespaceImport) { - diagnostics.push( - actionRequired( - filePath, - imp, - `Namespace import of ${specifier}: exported symbol(s) ${Object.keys(mapping.renamedSymbols).join(', ')} ` + - `were renamed in ${effectiveTarget}. Update qualified accesses manually.` - ) - ); - } + diagnostics.push( + actionRequired( + filePath, + imp, + `Namespace import of ${specifier}: exported symbol(s) ${Object.keys(mapping.renamedSymbols).join(', ')} ` + + `were renamed in ${effectiveTarget}. Update qualified accesses manually.` + ) + ); } changesCount++; if (mapping.migrationHint) { @@ -166,13 +203,40 @@ export const importPathsTransform: Transform = { for (const n of namedImports) { const name = n.getName(); + const alias = n.getAliasNode()?.getText(); const resolvedName = mapping.renamedSymbols?.[name] ?? name; const specifierTypeOnly = typeOnly || n.isTypeOnly(); - const symbolTarget = mapping.symbolTargetOverrides?.[name] ?? targetPackage; + const symbolTarget = symbolTargetOverride(name, mapping) ?? targetPackage; + // A v1 auth-schema constant with no public v2 home (SafeUrlSchema/OptionalSafeUrlSchema) + // routes by context to a package that doesn't export it. Flag it so the user inlines the + // validation instead of hitting a silent "has no exported member" error. + if (mapping.schemaSymbolTarget && AUTH_SCHEMA_NAMES_NO_V2_PUBLIC_EXPORT.has(name)) { + diagnostics.push( + actionRequired( + filePath, + imp, + `${name} was an internal URL field-validator in v1's ${specifier} with no public v2 equivalent ` + + `(it is not re-exported by @modelcontextprotocol/core). Remove this import and inline the ` + + `validation (e.g. validate the URL with the WHATWG \`URL\` constructor or your own Zod schema).` + ) + ); + } usedPackages.add(symbolTarget); - addPending(symbolTarget, [resolvedName], specifierTypeOnly); + addPending(symbolTarget, [alias ? { name: resolvedName, alias } : resolvedName], specifierTypeOnly); + } + if (defaultImport) { + // The default binding can't be split per-symbol, so move it (and the module specifier) to + // the resolved context/target package. The named siblings were just routed per-symbol + // above, so drop them from this now default-only import. + const effectiveTarget = targetPackage; + usedPackages.add(effectiveTarget); + if (namedImports.length > 0) { + imp.removeNamedImports(); + } + imp.setModuleSpecifier(effectiveTarget); + } else { + imp.remove(); } - imp.remove(); changesCount++; if (mapping.migrationHint) { diagnostics.push(info(filePath, line, mapping.migrationHint)); @@ -182,28 +246,34 @@ export const importPathsTransform: Transform = { } } + const specLocal = (spec: NamedImportSpec): string => (typeof spec === 'string' ? spec : (spec.alias ?? spec.name)); for (const [target, groups] of pendingImports) { - const typeOnlyNames = new Set(); - const valueNames = new Set(); + // Dedupe by local binding name (alias when present), keeping the spec so aliases survive. + const typeOnlySpecs = new Map(); + const valueSpecs = new Map(); for (const group of groups) { - for (const name of group.names) { - if (group.isTypeOnly) { - typeOnlyNames.add(name); - } else { - valueNames.add(name); - } + for (const spec of group.specs) { + (group.isTypeOnly ? typeOnlySpecs : valueSpecs).set(specLocal(spec), spec); } } - if (valueNames.size > 0) { - addOrMergeImport(sourceFile, target, [...valueNames], false, insertIndex); + if (valueSpecs.size > 0) { + addOrMergeImport(sourceFile, target, [...valueSpecs.values()], false, insertIndex); } - if (typeOnlyNames.size > 0) { - const typeInsertIndex = valueNames.size > 0 ? insertIndex + 1 : insertIndex; - addOrMergeImport(sourceFile, target, [...typeOnlyNames], true, typeInsertIndex); + if (typeOnlySpecs.size > 0) { + const typeInsertIndex = valueSpecs.size > 0 ? insertIndex + 1 : insertIndex; + addOrMergeImport(sourceFile, target, [...typeOnlySpecs.values()], true, typeInsertIndex); } } + // Restore the captured leading comment if the rewrite dropped it (guard against duplication when + // the first import was rewritten in place and kept its comment). + if (leadingCommentText && !sourceFile.getFullText().includes(leadingCommentText)) { + const imports = sourceFile.getImportDeclarations(); + const anchor = imports[Math.min(insertIndex, imports.length - 1)]; + sourceFile.insertText(anchor ? anchor.getStart() : 0, `${leadingCommentText}\n`); + } + return { changesCount, diagnostics, usedPackages }; } }; @@ -223,7 +293,7 @@ function rewriteExportDeclarations( if (!specifier) continue; const line = exp.getStartLineNumber(); - let mapping = IMPORT_MAP[specifier]; + let mapping = lookupImportMapping(specifier); if (!mapping && isAuthImport(specifier)) { mapping = { @@ -262,12 +332,30 @@ function rewriteExportDeclarations( } } - if (mapping.symbolTargetOverrides) { + if (mapping.symbolTargetOverrides || mapping.schemaSymbolTarget) { const namedExports = exp.getNamedExports(); - const allOverridden = namedExports.length > 0 && namedExports.every(s => s.getName() in mapping.symbolTargetOverrides!); - if (allOverridden) { - targetPackage = mapping.symbolTargetOverrides[namedExports[0]!.getName()]!; - } else if (namedExports.some(s => s.getName() in mapping.symbolTargetOverrides!)) { + // A star re-export (`export * from …`, including `export * as ns from …`) has no named + // exports to route per-symbol, so it moves wholesale to the context package — which exports + // none of the Zod `*Schema` constants the v1 module re-exported. Downstream consumers of this + // barrel would hit "has no exported member" with no pointer to where the schemas went, so flag + // it (mirroring the namespace-import diagnostic on the import side). + if (mapping.schemaSymbolTarget && namedExports.length === 0) { + diagnostics.push( + actionRequired( + filePath, + exp, + `Star re-export of ${specifier} will not include the Zod schema constants that moved to ` + + `${mapping.schemaSymbolTarget} (they are no longer exported by ${targetPackage}). ` + + `Add an explicit \`export { … } from '${mapping.schemaSymbolTarget}'\` for any re-exported \`*Schema\` constants.` + ) + ); + } + const overrides = namedExports.map(s => symbolTargetOverride(s.getName(), mapping)); + const uniqueOverrides = new Set(overrides.filter((t): t is string => t !== undefined)); + const allOverridden = namedExports.length > 0 && overrides.every(t => t !== undefined); + if (allOverridden && uniqueOverrides.size === 1) { + targetPackage = [...uniqueOverrides][0]!; + } else if (uniqueOverrides.size > 0) { diagnostics.push( actionRequired( filePath, diff --git a/packages/codemod/src/migrations/v1-to-v2/transforms/index.ts b/packages/codemod/src/migrations/v1-to-v2/transforms/index.ts index 47cb1988ce..767f882c92 100644 --- a/packages/codemod/src/migrations/v1-to-v2/transforms/index.ts +++ b/packages/codemod/src/migrations/v1-to-v2/transforms/index.ts @@ -7,7 +7,6 @@ import { mcpServerApiTransform } from './mcpServerApi'; import { mockPathsTransform } from './mockPaths'; import { removedApisTransform } from './removedApis'; import { schemaParamRemovalTransform } from './schemaParamRemoval'; -import { specSchemaAccessTransform } from './specSchemaAccess'; import { symbolRenamesTransform } from './symbolRenames'; // Ordering matters — do not reorder without understanding dependencies: @@ -31,11 +30,7 @@ import { symbolRenamesTransform } from './symbolRenames'; // 5. handlerRegistration, schemaParamRemoval, and expressMiddleware are // independent of each other but all depend on importPaths having run. // -// 6. specSchemaAccess runs after handlerRegistration and schemaParamRemoval: -// those transforms remove spec schema references they handle. specSchemaAccess -// then processes remaining standalone usages (safeParse, parse, z.infer, etc.). -// -// 7. mockPaths runs last: handles test mocks and dynamic imports, +// 6. mockPaths runs last: handles test mocks and dynamic imports, // independent of the other transforms. export const v1ToV2Transforms: Transform[] = [ importPathsTransform, @@ -44,7 +39,6 @@ export const v1ToV2Transforms: Transform[] = [ mcpServerApiTransform, handlerRegistrationTransform, schemaParamRemovalTransform, - specSchemaAccessTransform, expressMiddlewareTransform, contextTypesTransform, mockPathsTransform diff --git a/packages/codemod/src/migrations/v1-to-v2/transforms/mockPaths.ts b/packages/codemod/src/migrations/v1-to-v2/transforms/mockPaths.ts index 0316c33b78..7ffdd53887 100644 --- a/packages/codemod/src/migrations/v1-to-v2/transforms/mockPaths.ts +++ b/packages/codemod/src/migrations/v1-to-v2/transforms/mockPaths.ts @@ -5,9 +5,29 @@ import type { Diagnostic, Transform, TransformContext, TransformResult } from '. import { actionRequired, v2Gap, warning } from '../../../utils/diagnostics'; import { isSdkSpecifier } from '../../../utils/importUtils'; import { resolveTypesPackage } from '../../../utils/projectAnalyzer'; -import { IMPORT_MAP, isAuthImport } from '../mappings/importMap'; +import type { ImportMapping } from '../mappings/importMap'; +import { isAuthImport, lookupImportMapping } from '../mappings/importMap'; +import { isSharedSchemaConst, resolveRenamedName, symbolTargetOverride } from '../mappings/schemaRouting'; import { SIMPLE_RENAMES } from '../mappings/symbolMap'; +/** + * Resolve the single per-symbol target package shared by every `symbol` (mocked factory keys or + * destructured `import()` bindings), or report that they mix v2 packages. A mock/dynamic-import + * specifier is a single string and cannot be split, so a mix can only be flagged, not rewritten. + * Returns `target: undefined` when no symbol carries a per-symbol override (the caller keeps the + * mapping's resolved context/`target` package). Mirrors `symbolTargetOverride` routing used by the + * static import/export transform so e.g. a factory of only `*Schema` constants routes to core. + */ +function routeSymbols(symbols: string[], mapping: ImportMapping): { target?: string; mixed: boolean } { + if (symbols.length === 0) return { mixed: false }; + const targets = symbols.map(s => symbolTargetOverride(s, mapping)); + const overridden = targets.filter((t): t is string => t !== undefined); + const unique = new Set(overridden); + if (overridden.length === symbols.length && unique.size === 1) return { target: [...unique][0]!, mixed: false }; + if (unique.size > 0) return { mixed: true }; + return { mixed: false }; +} + const MOCK_METHODS = new Set([ 'mock', 'doMock', @@ -53,13 +73,14 @@ function resolveTarget( specifier: string, context: TransformContext, sourceFile: SourceFile, + symbols: string[], diagnosticSink?: { filePath: string; line: number; diagnostics: Diagnostic[] } -): - | { target: string; renamedSymbols?: Record; symbolTargetOverrides?: Record } - | { removed: true; isV2Gap?: boolean; removalMessage?: string } - | null { - const mapping = IMPORT_MAP[specifier]; - if (!mapping && isAuthImport(specifier)) return { target: '@modelcontextprotocol/server-legacy/auth' }; +): { target: string; mapping: ImportMapping } | { removed: true; isV2Gap?: boolean; removalMessage?: string } | null { + const mapping = lookupImportMapping(specifier); + if (!mapping && isAuthImport(specifier)) { + const authMapping: ImportMapping = { target: '@modelcontextprotocol/server-legacy/auth', status: 'moved' }; + return { target: authMapping.target, mapping: authMapping }; + } if (!mapping) return null; if (mapping.status === 'removed') return { removed: true, isV2Gap: mapping.isV2Gap, removalMessage: mapping.removalMessage }; @@ -73,13 +94,24 @@ function resolveTarget( const s = i.getModuleSpecifierValue(); return s.includes('/server/') || s === '@modelcontextprotocol/server'; }); - target = resolveTypesPackage(context, hasClient, hasServer, diagnosticSink); + // Resolve lazily: only pass the diagnostic sink to resolveTypesPackage when the routed target + // actually falls back to the context package. A factory/destructuring whose symbols all route + // elsewhere (e.g. only `*Schema` constants → core) never uses the context package, so emitting a + // "could not determine project type" warning (or a 'both'-project info note) for it would be + // spurious. Mirrors the lazy `needsContext` guard in the static import transform. A + // non-destructured/non-routable binding has no symbols, so `routeSymbols` returns no target and + // context is (correctly) treated as needed. + const needsContext = routeSymbols(symbols, mapping).target === undefined; + target = resolveTypesPackage(context, hasClient, hasServer, needsContext ? diagnosticSink : undefined); if (mapping.subpathSuffix) { target = `${target}${mapping.subpathSuffix}`; } } - return { target, renamedSymbols: mapping.renamedSymbols, symbolTargetOverrides: mapping.symbolTargetOverrides }; + // Return the original mapping (not just `renamedSymbols`/`symbolTargetOverrides`) so per-symbol + // routing can consult `schemaSymbolTarget` via the shared `symbolTargetOverride`/`routeSymbols`, + // matching how the static import transform routes `*Schema` constants to core. + return { target, mapping }; } function rewriteMockCall( @@ -98,7 +130,8 @@ function rewriteMockCall( const specifier = firstArg.getLiteralValue(); if (!isSdkSpecifier(specifier)) return 0; - const resolved = resolveTarget(specifier, context, sourceFile, { + const factorySymbols = args.length >= 2 ? collectFactorySymbols(args[1]!) : []; + const resolved = resolveTarget(specifier, context, sourceFile, factorySymbols, { filePath: sourceFile.getFilePath(), line: call.getStartLineNumber(), diagnostics @@ -122,13 +155,15 @@ function rewriteMockCall( let changes = 0; let effectiveTarget = resolved.target; - if (resolved.symbolTargetOverrides && args.length >= 2) { - const factorySymbols = collectFactorySymbols(args[1]!); - const allOverridden = factorySymbols.length > 0 && factorySymbols.every(s => s in resolved.symbolTargetOverrides!); - const someOverridden = factorySymbols.some(s => s in resolved.symbolTargetOverrides!); - if (allOverridden) { - effectiveTarget = resolved.symbolTargetOverrides[factorySymbols[0]!]!; - } else if (someOverridden) { + if (args.length >= 2) { + // Route the factory's mocked symbols the same way the static import transform would: a factory of + // only `*Schema` constants (from sdk/types.js or sdk/shared/auth.js) moves to core; a factory + // of only `StreamableHTTPServerTransport` moves to @modelcontextprotocol/node. A single mock path + // can't be split, so a mix of packages is flagged for manual migration. + const { target: routedTarget, mixed } = routeSymbols(factorySymbols, resolved.mapping); + if (routedTarget) { + effectiveTarget = routedTarget; + } else if (mixed) { diagnostics.push( actionRequired( sourceFile.getFilePath(), @@ -144,7 +179,7 @@ function rewriteMockCall( firstArg.setLiteralValue(effectiveTarget); changes++; - const allRenames: Record = { ...SIMPLE_RENAMES, ...resolved.renamedSymbols }; + const allRenames: Record = { ...SIMPLE_RENAMES, ...resolved.mapping.renamedSymbols }; if (args.length >= 2) { changes += renameSymbolsInFactory(args[1]!, allRenames); } @@ -216,6 +251,106 @@ function renameSymbolsInFactory(factoryArg: import('ts-morph').Node, renamedSymb return changes; } +/** + * The object binding pattern through which a dynamic import's named symbols are pulled — either + * `const { … } = await import('…')` or `import('…').then(({ … }) => …)`. Returns undefined for a + * non-destructured binding (`const mod = await import()`), an identifier `.then` param (`m => …`), or + * an unassigned `await import()`. Both destructured shapes expose named symbols that can be routed and + * renamed per-symbol (the specifier itself can't be split). + */ +function getModuleBindingPattern(node: import('ts-morph').CallExpression): import('ts-morph').ObjectBindingPattern | undefined { + const parent = node.getParent(); + if (parent && Node.isAwaitExpression(parent)) { + const grandParent = parent.getParent(); + if (grandParent && Node.isVariableDeclaration(grandParent)) { + const nameNode = grandParent.getNameNode(); + if (Node.isObjectBindingPattern(nameNode)) return nameNode; + } + return undefined; + } + if (parent && Node.isPropertyAccessExpression(parent) && parent.getName() === 'then') { + const thenCall = parent.getParent(); + if (thenCall && Node.isCallExpression(thenCall)) { + const cb = thenCall.getArguments()[0]; + if (cb && (Node.isArrowFunction(cb) || Node.isFunctionExpression(cb))) { + const paramName = cb.getParameters()[0]?.getNameNode(); + if (paramName && Node.isObjectBindingPattern(paramName)) return paramName; + } + } + } + return undefined; +} + +/** + * The destructured binding keys of a dynamic import — for both `const { … } = await import('…')` and + * `import('…').then(({ … }) => …)` — or `[]` for a non-destructured binding, an identifier `.then` + * param, or an unassigned `await import()`. The keys feed per-symbol routing; the specifier itself + * can't be split. + */ +function getDestructuredKeys(node: import('ts-morph').CallExpression): string[] { + const pattern = getModuleBindingPattern(node); + if (!pattern) return []; + return pattern.getElements().map(el => el.getPropertyNameNode()?.getText() ?? el.getName()); +} + +/** + * For a dynamic import whose module binding is NOT a destructurable object pattern — a non-destructured + * `const mod = await import('…')` or a `.then(m => …)` chain — collect the Zod schema constants + * accessed off that binding (e.g. `mod.OAuthTokensSchema`). The destructured form is routed/renamed + * elsewhere; these forms can't be split per-symbol, so the schema accesses are surfaced as a diagnostic + * (mirroring the namespace-import branch of the static import transform — see `importPaths.ts`). Returns + * deduped `[v1Name, v2Name]` pairs (a schema may be renamed, e.g. JSONRPCResponseSchema → + * JSONRPCResultResponseSchema, and core only exports the v2 name). Empty unless the mapping carries a + * `schemaSymbolTarget`. + */ +function collectModuleSchemaAccesses( + node: import('ts-morph').CallExpression, + mapping: ImportMapping, + sourceFile: SourceFile +): Array { + if (!mapping.schemaSymbolTarget) return []; + + let bindingName: string | undefined; + let scope: import('ts-morph').Node | undefined; + + const parent = node.getParent(); + if (parent && Node.isAwaitExpression(parent)) { + // const mod = await import('…') → `mod` is in scope for the rest of the file. + const grandParent = parent.getParent(); + if (grandParent && Node.isVariableDeclaration(grandParent)) { + const nameNode = grandParent.getNameNode(); + if (Node.isIdentifier(nameNode)) { + bindingName = nameNode.getText(); + scope = sourceFile; + } + } + } else if (parent && Node.isPropertyAccessExpression(parent) && parent.getName() === 'then') { + // import('…').then(m => m.XxxSchema…) → the module is the `.then` callback's first parameter. + const thenCall = parent.getParent(); + if (thenCall && Node.isCallExpression(thenCall)) { + const cb = thenCall.getArguments()[0]; + if (cb && (Node.isArrowFunction(cb) || Node.isFunctionExpression(cb))) { + const paramName = cb.getParameters()[0]?.getNameNode(); + if (paramName && Node.isIdentifier(paramName)) { + bindingName = paramName.getText(); + scope = cb; + } + } + } + } + + if (!bindingName || !scope) return []; + + return [ + ...new Map( + scope + .getDescendantsOfKind(SyntaxKind.PropertyAccessExpression) + .filter(pa => pa.getExpression().getText() === bindingName && isSharedSchemaConst(pa.getName(), mapping)) + .map(pa => [pa.getName(), resolveRenamedName(pa.getName(), mapping)] as const) + ) + ]; +} + function rewriteDynamicImports( sourceFile: SourceFile, context: TransformContext, @@ -239,7 +374,8 @@ function rewriteDynamicImports( const specifier = firstArg.getLiteralValue(); if (!isSdkSpecifier(specifier)) return; - const resolved = resolveTarget(specifier, context, sourceFile, { + const destructuredKeys = getDestructuredKeys(node); + const resolved = resolveTarget(specifier, context, sourceFile, destructuredKeys, { filePath: sourceFile.getFilePath(), line: node.getStartLineNumber(), diagnostics @@ -263,59 +399,81 @@ function rewriteDynamicImports( } let effectiveTarget = resolved.target; - const allRenames: Record = { ...SIMPLE_RENAMES, ...resolved.renamedSymbols }; - - // Check if destructured symbols should route to an override target - if (resolved.symbolTargetOverrides) { - const parent = node.getParent(); - if (parent && Node.isAwaitExpression(parent)) { - const grandParent = parent.getParent(); - if (grandParent && Node.isVariableDeclaration(grandParent)) { - const nameNode = grandParent.getNameNode(); - if (Node.isObjectBindingPattern(nameNode)) { - const elements = nameNode.getElements(); - const allOverridden = - elements.length > 0 && - elements.every(el => { - const key = el.getPropertyNameNode()?.getText() ?? el.getName(); - return key in resolved.symbolTargetOverrides!; - }); - if (allOverridden) { - effectiveTarget = - resolved.symbolTargetOverrides[elements[0]!.getPropertyNameNode()?.getText() ?? elements[0]!.getName()]!; - } - } - } - } + const allRenames: Record = { ...SIMPLE_RENAMES, ...resolved.mapping.renamedSymbols }; + + // Route the destructured bindings the same way the static import transform would: a destructuring + // of only `*Schema` constants (e.g. `const { CallToolResultSchema } = await import('…/types.js')`) + // moves to core, and `StreamableHTTPServerTransport` moves to @modelcontextprotocol/node. A + // single import() specifier can't be split, so a mix of packages is flagged for manual migration. + const { target: routedTarget, mixed } = routeSymbols(destructuredKeys, resolved.mapping); + if (routedTarget) { + effectiveTarget = routedTarget; + } else if (mixed) { + diagnostics.push( + actionRequired( + sourceFile.getFilePath(), + node, + `Dynamic import of ${specifier} destructures symbols that belong to different v2 packages. ` + + `Split the import manually so each symbol targets the correct package.` + ) + ); + } + + // A non-destructured binding (`const mod = await import('…')`) or a `.then(m => …)` chain can't be + // routed per-symbol, so the specifier moves to the context package — which does NOT export the + // Zod `*Schema` constants (those live in `schemaSymbolTarget`/core). Any `mod.Schema` / + // `m.Schema` accesses would silently break, so flag them (mirroring the namespace-import + // branch of the static import transform). The destructured form is handled by `routeSymbols` above. + const schemaAccesses = collectModuleSchemaAccesses(node, resolved.mapping, sourceFile); + if (schemaAccesses.length > 0) { + const accessed = schemaAccesses.map(([v1]) => v1).join(', '); + const importName = schemaAccesses[0]![1]; + const renamed = schemaAccesses.filter(([v1, v2]) => v1 !== v2); + const renameNote = renamed.length > 0 ? ` Renamed in v2: ${renamed.map(([v1, v2]) => `${v1} → ${v2}`).join(', ')}.` : ''; + diagnostics.push( + actionRequired( + sourceFile.getFilePath(), + node, + `Dynamic import of ${specifier} is used to access Zod schema(s) (${accessed}) that moved to ${resolved.mapping.schemaSymbolTarget}.${renameNote} ` + + `Import them with a named import (e.g. \`import { ${importName} } from '${resolved.mapping.schemaSymbolTarget}'\`) and update the qualified usages.` + ) + ); } usedPackages.add(effectiveTarget); firstArg.setLiteralValue(effectiveTarget); changes++; - const parent = node.getParent(); - if (parent && Node.isAwaitExpression(parent)) { - const grandParent = parent.getParent(); - if (grandParent && Node.isVariableDeclaration(grandParent)) { - const nameNode = grandParent.getNameNode(); - if (Node.isObjectBindingPattern(nameNode)) { - for (const element of nameNode.getElements()) { - const propertyName = element.getPropertyNameNode()?.getText(); - const bindingName = element.getName(); - const lookupKey = propertyName ?? bindingName; - const newName = allRenames[lookupKey]; - if (newName) { - if (propertyName) { - element.getPropertyNameNode()!.replaceWithText(newName); - } else { - element.replaceWithText(`${newName}: ${bindingName}`); - } - changes++; - } + // Apply symbol renames to the destructured binding elements — for both `await import()` + // destructuring and a `.then(({ … }) => …)` param (both routed per-symbol above when their + // symbols share a target, e.g. schema-only → core). + const bindingPattern = getModuleBindingPattern(node); + if (bindingPattern) { + for (const element of bindingPattern.getElements()) { + const propertyName = element.getPropertyNameNode()?.getText(); + const bindingName = element.getName(); + const lookupKey = propertyName ?? bindingName; + const newName = allRenames[lookupKey]; + if (newName) { + if (propertyName) { + element.getPropertyNameNode()!.replaceWithText(newName); + } else { + element.replaceWithText(`${newName}: ${bindingName}`); } + changes++; } - const moduleRenames = resolved.renamedSymbols ?? {}; - if (!Node.isObjectBindingPattern(nameNode) && Object.keys(moduleRenames).length > 0) { + } + } + + // A non-destructured awaited binding (`const mod = await import('…')`) can't have per-symbol + // renames applied, so flag them if the mapping carries any. (Identifier `.then` params and bare + // `mod.Schema` accesses are surfaced by `collectModuleSchemaAccesses` above.) + const awaitParent = node.getParent(); + if (awaitParent && Node.isAwaitExpression(awaitParent)) { + const decl = awaitParent.getParent(); + if (decl && Node.isVariableDeclaration(decl) && !Node.isObjectBindingPattern(decl.getNameNode())) { + const moduleRenames = resolved.mapping.renamedSymbols ?? {}; + if (Object.keys(moduleRenames).length > 0) { diagnostics.push( actionRequired( sourceFile.getFilePath(), diff --git a/packages/codemod/src/migrations/v1-to-v2/transforms/schemaParamRemoval.ts b/packages/codemod/src/migrations/v1-to-v2/transforms/schemaParamRemoval.ts index bc48a3cfb0..66143b2534 100644 --- a/packages/codemod/src/migrations/v1-to-v2/transforms/schemaParamRemoval.ts +++ b/packages/codemod/src/migrations/v1-to-v2/transforms/schemaParamRemoval.ts @@ -2,7 +2,7 @@ import type { SourceFile } from 'ts-morph'; import { Node, SyntaxKind } from 'ts-morph'; import type { Transform, TransformContext, TransformResult } from '../../../types'; -import { isImportedFromMcp, removeUnusedImport, resolveOriginalImportName } from '../../../utils/importUtils'; +import { hasMcpImports, isImportedFromMcp, removeUnusedImport, resolveOriginalImportName } from '../../../utils/importUtils'; const TARGET_METHODS = new Set(['request', 'callTool']); @@ -12,6 +12,11 @@ export const schemaParamRemovalTransform: Transform = { apply(sourceFile: SourceFile, _context: TransformContext): TransformResult { let changesCount = 0; + // `request`/`callTool` are common method names on non-MCP receivers too. The schema-identifier + // path guards per-symbol via `isImportedFromMcp`; the `undefined` path has no symbol to check, so + // gate it on a file-level MCP signal to avoid rewriting unrelated calls. + const fileHasMcpImports = hasMcpImports(sourceFile); + const calls = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression); for (const call of calls) { @@ -27,6 +32,19 @@ export const schemaParamRemovalTransform: Transform = { const secondArg = args[1]!; if (!Node.isIdentifier(secondArg)) continue; + // `request(req, undefined, options)` / `callTool(params, undefined, options)`: v1 passed an + // explicit `undefined` result schema before the trailing options argument. v2 removed the + // schema parameter for spec methods, so the literal `undefined` leaves the call with one + // argument too many (TS2554). Drop it only when a third argument follows — a 2-arg + // `callTool(params, undefined)` already type-checks, since `undefined` is a valid options arg. + if (secondArg.getText() === 'undefined') { + if (fileHasMcpImports && args.length >= 3) { + call.removeArgument(1); + changesCount++; + } + continue; + } + const schemaName = secondArg.getText(); const originalName = resolveOriginalImportName(sourceFile, schemaName) ?? schemaName; if (!originalName.endsWith('Schema')) continue; diff --git a/packages/codemod/src/migrations/v1-to-v2/transforms/specSchemaAccess.ts b/packages/codemod/src/migrations/v1-to-v2/transforms/specSchemaAccess.ts deleted file mode 100644 index 37891e361e..0000000000 --- a/packages/codemod/src/migrations/v1-to-v2/transforms/specSchemaAccess.ts +++ /dev/null @@ -1,392 +0,0 @@ -import type { SourceFile } from 'ts-morph'; -import { Node, SyntaxKind } from 'ts-morph'; - -import { SPEC_SCHEMA_NAMES, specSchemaToTypeName } from '../../../generated/specSchemaMap'; -import type { Diagnostic, Transform, TransformContext, TransformResult } from '../../../types'; -import { isKeyPositionIdentifier } from '../../../utils/astUtils'; -import { actionRequired, warning } from '../../../utils/diagnostics'; -import { addOrMergeImport, isAnyMcpSpecifier, removeUnusedImport } from '../../../utils/importUtils'; - -export const specSchemaAccessTransform: Transform = { - name: 'Spec schema standalone usage', - id: 'spec-schemas', - apply(sourceFile: SourceFile, _context: TransformContext): TransformResult { - const diagnostics: Diagnostic[] = []; - let changesCount = 0; - - const schemaImports = collectSpecSchemaImports(sourceFile); - if (schemaImports.size === 0) return { changesCount: 0, diagnostics: [] }; - - for (const [localName, originalName] of schemaImports) { - const typeName = specSchemaToTypeName(originalName); - if (!typeName) continue; - - const refs = findNonImportReferences(sourceFile, localName); - if (refs.length === 0) continue; - - for (const ref of refs) { - const result = handleReference(ref, localName, typeName, sourceFile, diagnostics); - if (result) changesCount++; - } - removeUnusedImport(sourceFile, localName, true); - } - - return { changesCount, diagnostics }; - } -}; - -function collectSpecSchemaImports(sourceFile: SourceFile): Map { - const result = new Map(); - for (const imp of sourceFile.getImportDeclarations()) { - if (!isAnyMcpSpecifier(imp.getModuleSpecifierValue())) continue; - for (const n of imp.getNamedImports()) { - const exportName = n.getName(); - if (!SPEC_SCHEMA_NAMES.has(exportName)) continue; - const localName = n.getAliasNode()?.getText() ?? exportName; - result.set(localName, exportName); - } - } - return result; -} - -function findNonImportReferences(sourceFile: SourceFile, localName: string): import('ts-morph').Node[] { - const refs: import('ts-morph').Node[] = []; - sourceFile.forEachDescendant(node => { - if (!Node.isIdentifier(node)) return; - if (node.getText() !== localName) return; - const parent = node.getParent(); - if (parent && Node.isImportSpecifier(parent)) return; - refs.push(node); - }); - return refs; -} - -function handleReference( - ref: import('ts-morph').Node, - localName: string, - typeName: string, - sourceFile: SourceFile, - diagnostics: Diagnostic[] -): boolean { - // Pattern: z.infer — type position - if (isTypeofInTypePosition(ref)) { - diagnostics.push( - actionRequired( - sourceFile.getFilePath(), - ref, - `Replace \`z.infer\` with the \`${typeName}\` type (already exported from the same v2 package).` - ) - ); - return false; - } - - // Pattern: XSchema.safeParse(v).success — auto-transform to isSpecType.X(v) - if (isSafeParseSuccessPattern(ref)) { - const safeParseAccess = ref.getParent() as import('ts-morph').PropertyAccessExpression; - const safeParseCall = safeParseAccess.getParent() as import('ts-morph').CallExpression; - const successAccess = safeParseCall.getParent() as import('ts-morph').PropertyAccessExpression; - const args = safeParseCall.getArguments(); - const argText = args.length > 0 ? args[0]!.getText() : ''; - successAccess.replaceWithText(`isSpecType.${typeName}(${argText})`); - ensureImport(sourceFile, 'isSpecType'); - return true; - } - - // Pattern: const x = XSchema.safeParse(v) — auto-transform when result is captured in a variable - if (isSafeParsePattern(ref)) { - const safeParseAccess = ref.getParent() as import('ts-morph').PropertyAccessExpression; - const safeParseCall = safeParseAccess.getParent() as import('ts-morph').CallExpression; - - if (isCapturedSafeParsePattern(safeParseCall)) { - return rewriteCapturedSafeParse(safeParseCall, localName, typeName, sourceFile, diagnostics); - } - - return rewriteUnsupportedSchemaCall(ref, safeParseCall, localName, typeName, 'safeParse', sourceFile, diagnostics); - } - - // Pattern: XSchema.parse(v) — rewrite to the StandardSchema validate() primitive (or, when the - // result is used, swap the identifier) so we never leave behind an import of a non-exported schema. - if (isParsePattern(ref)) { - const parseAccess = ref.getParent() as import('ts-morph').PropertyAccessExpression; - const parseCall = parseAccess.getParent() as import('ts-morph').CallExpression; - return rewriteUnsupportedSchemaCall(ref, parseCall, localName, typeName, 'parse', sourceFile, diagnostics); - } - - // Pattern: XSchema used as value (function arg, assignment, etc.) - const parent = ref.getParent(); - if (parent && Node.isPropertyAccessExpression(parent) && parent.getExpression() === ref) { - const line = ref.getStartLineNumber(); - ref.replaceWithText(`specTypeSchemas.${typeName}`); - ensureImport(sourceFile, 'specTypeSchemas'); - diagnostics.push( - warning( - sourceFile.getFilePath(), - line, - `Replaced ${localName} with specTypeSchemas.${typeName}. Note: typed as StandardSchemaV1, not ZodType — Zod methods like .safeParse()/.parse()/.parseAsync() are not available. Manual rewrite required.` - ) - ); - return true; - } - - if (parent && Node.isExportSpecifier(parent)) { - diagnostics.push( - actionRequired( - sourceFile.getFilePath(), - ref, - `Re-export of ${localName} requires manual update: replace with specTypeSchemas.${typeName} or remove.` - ) - ); - return false; - } - - if (parent && Node.isShorthandPropertyAssignment(parent)) { - const line = ref.getStartLineNumber(); - parent.replaceWithText(`'${localName}': specTypeSchemas.${typeName}`); - ensureImport(sourceFile, 'specTypeSchemas'); - diagnostics.push( - warning( - sourceFile.getFilePath(), - line, - `Replaced ${localName} with specTypeSchemas.${typeName}. Note: typed as StandardSchemaV1, not ZodType — Zod methods like .safeParse()/.parse() are not available.` - ) - ); - return true; - } - - if (parent && isKeyPositionIdentifier(ref)) { - return false; - } - - // Value position: replace identifier with specTypeSchemas.X - const line = ref.getStartLineNumber(); - ref.replaceWithText(`specTypeSchemas.${typeName}`); - ensureImport(sourceFile, 'specTypeSchemas'); - diagnostics.push( - warning( - sourceFile.getFilePath(), - line, - `Replaced ${localName} with specTypeSchemas.${typeName}. Note: typed as StandardSchemaV1, not ZodType — Zod methods like .safeParse()/.parse() are not available.` - ) - ); - return true; -} - -function isSafeParseSuccessPattern(ref: import('ts-morph').Node): boolean { - const parent = ref.getParent(); - if (!parent || !Node.isPropertyAccessExpression(parent)) return false; - if (parent.getName() !== 'safeParse' || parent.getExpression() !== ref) return false; - const grandParent = parent.getParent(); - if (!grandParent || !Node.isCallExpression(grandParent)) return false; - const greatGrandParent = grandParent.getParent(); - if (!greatGrandParent || !Node.isPropertyAccessExpression(greatGrandParent)) return false; - return greatGrandParent.getName() === 'success'; -} - -function isSafeParsePattern(ref: import('ts-morph').Node): boolean { - const parent = ref.getParent(); - if (!parent || !Node.isPropertyAccessExpression(parent)) return false; - if (parent.getName() !== 'safeParse' || parent.getExpression() !== ref) return false; - const grandParent = parent.getParent(); - return !!grandParent && Node.isCallExpression(grandParent); -} - -function isParsePattern(ref: import('ts-morph').Node): boolean { - const parent = ref.getParent(); - if (!parent || !Node.isPropertyAccessExpression(parent)) return false; - if (parent.getName() !== 'parse' || parent.getExpression() !== ref) return false; - const grandParent = parent.getParent(); - return !!grandParent && Node.isCallExpression(grandParent); -} - -function isTypeofInTypePosition(ref: import('ts-morph').Node): boolean { - const parent = ref.getParent(); - if (!parent) return false; - return Node.isTypeQuery(parent); -} - -/** - * Checks if a safeParse call result is captured in a `const` variable declaration. - * Pattern: `const x = Schema.safeParse(v);` - */ -function isCapturedSafeParsePattern(safeParseCall: import('ts-morph').CallExpression): boolean { - const parent = safeParseCall.getParent(); - if (!parent || !Node.isVariableDeclaration(parent)) return false; - const nameNode = parent.getNameNode(); - if (!Node.isIdentifier(nameNode)) return false; - const declList = parent.getParent(); - if (!declList || !Node.isVariableDeclarationList(declList)) return false; - const flags = declList.getDeclarationKind(); - return flags === 'const' || flags === 'let'; -} - -/** - * Rewrites a captured safeParse pattern: - * const x = Schema.safeParse(v) → const x = specTypeSchemas.T['~standard'].validate(v) - * x.success → x.issues === undefined - * x.data → x.value - * x.error → x.issues - */ -function rewriteCapturedSafeParse( - safeParseCall: import('ts-morph').CallExpression, - localName: string, - typeName: string, - sourceFile: SourceFile, - diagnostics: Diagnostic[] -): boolean { - const varDecl = safeParseCall.getParent() as import('ts-morph').VariableDeclaration; - const varName = varDecl.getName(); - - const args = safeParseCall.getArguments(); - const argText = args.length > 0 ? args[0]!.getText() : ''; - - // Rewrite the safeParse call - safeParseCall.replaceWithText(`specTypeSchemas.${typeName}['~standard'].validate(${argText})`); - ensureImport(sourceFile, 'specTypeSchemas'); - - // Find and rewrite all property accesses on the result variable (scoped to declaring block) - const replacements: { node: import('ts-morph').Node; newText: string }[] = []; - const scope = varDecl.getFirstAncestorByKind(SyntaxKind.Block) ?? sourceFile; - scope.forEachDescendant(node => { - if (!Node.isPropertyAccessExpression(node)) return; - const expr = node.getExpression(); - if (!Node.isIdentifier(expr) || expr.getText() !== varName) return; - - const propName = node.getName(); - switch (propName) { - case 'success': { - // Check for !x.success → x.issues !== undefined - const parentNode = node.getParent(); - if ( - parentNode && - Node.isPrefixUnaryExpression(parentNode) && - parentNode.getOperatorToken() === SyntaxKind.ExclamationToken - ) { - replacements.push({ node: parentNode, newText: `${varName}.issues !== undefined` }); - } else { - replacements.push({ node, newText: `(${varName}.issues === undefined)` }); - } - break; - } - case 'data': { - replacements.push({ node, newText: `${varName}.value` }); - break; - } - case 'error': { - const errorParent = node.getParent(); - if (errorParent && Node.isPropertyAccessExpression(errorParent) && errorParent.getExpression() === node) { - const subProp = errorParent.getName(); - if (subProp === 'issues') { - replacements.push({ node: errorParent, newText: `${varName}.issues` }); - } else if (subProp === 'message') { - replacements.push({ node: errorParent, newText: `${varName}.issues?.map(i => i.message).join(', ')` }); - } else { - diagnostics.push( - actionRequired( - sourceFile.getFilePath(), - errorParent, - `${varName}.error.${subProp} has no StandardSchema equivalent. Manual migration required.` - ) - ); - } - } else { - replacements.push({ node, newText: `${varName}.issues` }); - } - break; - } - } - }); - - // Apply in reverse order to avoid position shifts - const sorted = replacements.toSorted((a, b) => b.node.getStart() - a.node.getStart()); - for (const { node, newText } of sorted) { - node.replaceWithText(newText); - } - - diagnostics.push( - warning( - sourceFile.getFilePath(), - varDecl.getStartLineNumber(), - `Rewrote ${localName}.safeParse() to specTypeSchemas.${typeName}['~standard'].validate(). ` + - `Result properties remapped: .success → .issues === undefined, .data → .value, .error → .issues.` - ) - ); - - return true; -} - -/** - * Handles spec-schema usages that have no behavior-preserving v2 equivalent: the Zod-only - * methods `.parse()` and (uncaptured) `.safeParse()`. In v2 these schemas are StandardSchemaV1 - * values that are NOT named public exports, so leaving the original import in place produces an - * unresolved-import error (e.g. `PromptSchema` is not exported by `@modelcontextprotocol/server`). - * - * - Result discarded (validation for side-effect only): rewrite `XSchema.parse(v)` → - * `specTypeSchemas.T['~standard'].validate(v)` so the code compiles. NOTE: `validate()` does not - * throw, so `.parse()`'s throw-on-invalid behavior is lost — flagged via an actionRequired comment. - * - Result used: swap only the identifier to `specTypeSchemas.T` so the import resolves; the - * `.parse()`/`.safeParse()` call and its result shape still need a manual fix (flagged). - * - * Either way the original (now non-exported) schema import is dropped by the caller's - * removeUnusedImport, so no dangling import survives. - */ -function rewriteUnsupportedSchemaCall( - ref: import('ts-morph').Node, - callNode: import('ts-morph').CallExpression, - localName: string, - typeName: string, - method: 'parse' | 'safeParse', - sourceFile: SourceFile, - diagnostics: Diagnostic[] -): boolean { - const resultDiscarded = Node.isExpressionStatement(callNode.getParent()); - - if (resultDiscarded) { - const argText = callNode - .getArguments() - .map(a => a.getText()) - .join(', '); - const semantics = - method === 'parse' - ? 'validate() does NOT throw on invalid input (parse() did) — if you relied on that, add `if (result.issues) throw …`.' - : 'the result shape changed from { success, data, error } to { value, issues }.'; - diagnostics.push( - actionRequired( - sourceFile.getFilePath(), - callNode, - `Rewrote ${localName}.${method}() to specTypeSchemas.${typeName}['~standard'].validate(): ` + - `v2 spec schemas are StandardSchemaV1, not Zod. Note: ${semantics}` - ) - ); - callNode.replaceWithText(`specTypeSchemas.${typeName}['~standard'].validate(${argText})`); - ensureImport(sourceFile, 'specTypeSchemas'); - return true; - } - - diagnostics.push( - actionRequired( - sourceFile.getFilePath(), - ref, - `${localName}.${method}() is not available on v2 spec schemas (StandardSchemaV1, not Zod). ` + - `Replaced ${localName} with specTypeSchemas.${typeName}; rewrite the .${method}(...) call using ` + - `specTypeSchemas.${typeName}['~standard'].validate(...) (returns { value, issues }, does not throw).` - ) - ); - ref.replaceWithText(`specTypeSchemas.${typeName}`); - ensureImport(sourceFile, 'specTypeSchemas'); - return true; -} - -function ensureImport(sourceFile: SourceFile, symbol: string): void { - const existingImport = sourceFile.getImportDeclarations().find(imp => { - if (!isAnyMcpSpecifier(imp.getModuleSpecifierValue())) return false; - return imp.getNamedImports().some(n => n.getName() === symbol); - }); - if (existingImport) return; - - const targetPkg = sourceFile.getImportDeclarations().find(imp => { - const spec = imp.getModuleSpecifierValue(); - return spec === '@modelcontextprotocol/server' || spec === '@modelcontextprotocol/client'; - }); - const target = targetPkg?.getModuleSpecifierValue() ?? '@modelcontextprotocol/server'; - addOrMergeImport(sourceFile, target, [symbol], false, sourceFile.getImportDeclarations().length); -} diff --git a/packages/codemod/src/runner.ts b/packages/codemod/src/runner.ts index 15a5f05064..1554429fae 100644 --- a/packages/codemod/src/runner.ts +++ b/packages/codemod/src/runner.ts @@ -143,7 +143,7 @@ export function run(migration: Migration, options: RunnerOptions): RunnerResult const fileDiagnostics: Diagnostic[] = []; const originalText = sourceFile.getFullText(); - const fileUsedPackages = new Set(); + const fileClaimedPackages = new Set(); try { for (const transform of enabledTransforms) { const result = transform.apply(sourceFile, context); @@ -151,12 +151,25 @@ export function run(migration: Migration, options: RunnerOptions): RunnerResult fileDiagnostics.push(...result.diagnostics); if (result.usedPackages) { for (const pkg of result.usedPackages) { - fileUsedPackages.add(pkg); + fileClaimedPackages.add(pkg); } } } - for (const pkg of fileUsedPackages) { - allUsedPackages.add(pkg); + // A transform records a package as "used" when it routes a binding there — but importPaths does + // so the moment it rewrites an import, and later transforms (handlerRegistration, + // schemaParamRemoval) routinely rewrite the schema usage away and delete that very import. + // Honouring a claim whose import did not survive would add an unused dependency to package.json, + // so a claim counts only when the FINAL file still references the specifier. Every claim + // originates from a string literal the transform wrote (an import/export module specifier, or a + // vi.mock()/dynamic import() argument), so a surviving string-literal match is the ground truth. + const survivingSpecifiers = new Set(); + for (const literal of sourceFile.getDescendantsOfKind(SyntaxKind.StringLiteral)) { + survivingSpecifiers.add(literal.getLiteralValue()); + } + for (const pkg of fileClaimedPackages) { + if (survivingSpecifiers.has(pkg)) { + allUsedPackages.add(pkg); + } } } catch (error_) { const filePath = sourceFile.getFilePath(); @@ -164,7 +177,7 @@ export function run(migration: Migration, options: RunnerOptions): RunnerResult fileDiagnostics.push(error(filePath, 1, `Transform failed: ${error_ instanceof Error ? error_.message : String(error_)}`)); sourceFile.replaceWithText(originalText); fileChanges = 0; - fileUsedPackages.clear(); + fileClaimedPackages.clear(); } for (const d of fileDiagnostics) { diff --git a/packages/codemod/src/utils/detectFormatter.ts b/packages/codemod/src/utils/detectFormatter.ts new file mode 100644 index 0000000000..81fc66da64 --- /dev/null +++ b/packages/codemod/src/utils/detectFormatter.ts @@ -0,0 +1,126 @@ +import { existsSync, readFileSync } from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + +/** A code formatter the codemod can recommend running after a migration. */ +export interface DetectedFormatter { + /** Display name, e.g. `Prettier`. */ + name: string; + /** Executable name, e.g. `prettier`. */ + bin: string; + /** Arguments that write formatting in place; changed file paths are appended after these. */ + writeArgs: readonly string[]; +} + +const BIOME_CONFIG_FILES = ['biome.json', 'biome.jsonc']; +const PRETTIER_CONFIG_FILES = [ + '.prettierrc', + '.prettierrc.json', + '.prettierrc.json5', + '.prettierrc.yaml', + '.prettierrc.yml', + '.prettierrc.toml', + '.prettierrc.js', + '.prettierrc.cjs', + '.prettierrc.mjs', + '.prettierrc.ts', + 'prettier.config.js', + 'prettier.config.cjs', + 'prettier.config.mjs', + 'prettier.config.ts' +]; +const ESLINT_CONFIG_FILES = [ + 'eslint.config.js', + 'eslint.config.mjs', + 'eslint.config.cjs', + 'eslint.config.ts', + 'eslint.config.mts', + 'eslint.config.cts', + '.eslintrc', + '.eslintrc.js', + '.eslintrc.cjs', + '.eslintrc.json', + '.eslintrc.yml', + '.eslintrc.yaml' +]; + +// Precedence order: a configured dedicated formatter wins over ESLint's --fix. +const FORMATTERS = { + biome: { name: 'Biome', bin: 'biome', writeArgs: ['format', '--write'] }, + prettier: { name: 'Prettier', bin: 'prettier', writeArgs: ['--write'] }, + eslint: { name: 'ESLint', bin: 'eslint', writeArgs: ['--fix'] } +} as const satisfies Record; + +function hasAnyFile(dir: string, files: readonly string[]): boolean { + return files.some(file => existsSync(path.join(dir, file))); +} + +interface PackageJsonSignals { + prettier: boolean; + eslint: boolean; +} + +function readPackageJsonSignals(dir: string): PackageJsonSignals { + const pkgJsonPath = path.join(dir, 'package.json'); + if (!existsSync(pkgJsonPath)) return { prettier: false, eslint: false }; + try { + const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf8')) as Record; + const allDeps = { + ...(pkgJson.dependencies as Record | undefined), + ...(pkgJson.devDependencies as Record | undefined) + }; + return { + prettier: 'prettier' in pkgJson || 'prettier' in allDeps, + eslint: 'eslint' in allDeps + }; + } catch { + return { prettier: false, eslint: false }; + } +} + +/** + * Walks up from `startDir` looking for a configured code formatter, so the CLI can suggest the right + * "format your changed files" command after a migration. + * + * The walk is bounded so a user-level global config is never mistaken for the project's. It stops at the + * repository root (a `.git` directory) or the filesystem root, and — for a project that is not a git + * checkout (tarball, fresh scaffold, CI workspace) — never ascends into or above `$HOME`, so a + * `~/.prettierrc`, `~/biome.json`, or `~/package.json` with formatter deps is never matched. (A `.git` + * boundary alone did not hold this guarantee for non-git projects, which would otherwise walk to `$HOME`.) + * + * Detection is config-based and runs nothing. When multiple formatters are configured, precedence is + * Biome > Prettier > ESLint. + * + * @param startDir the directory to start the upward search from. + * @param homeDir the user's home directory; the walk never reads it or any ancestor. Injectable for tests; + * defaults to `os.homedir()`. + * @returns the detected formatter, or `null` if none is configured. + */ +export function detectFormatter(startDir: string, homeDir: string = os.homedir()): DetectedFormatter | null { + let dir = path.resolve(startDir); + const root = path.parse(dir).root; + const home = path.resolve(homeDir); + const found = { biome: false, prettier: false, eslint: false }; + + while (true) { + if (hasAnyFile(dir, BIOME_CONFIG_FILES)) found.biome = true; + if (hasAnyFile(dir, PRETTIER_CONFIG_FILES)) found.prettier = true; + if (hasAnyFile(dir, ESLINT_CONFIG_FILES)) found.eslint = true; + + const signals = readPackageJsonSignals(dir); + if (signals.prettier) found.prettier = true; + if (signals.eslint) found.eslint = true; + + // Stop at the repository root (a `.git` dir) or the filesystem root. For a project that is not a + // git checkout (tarball, fresh scaffold, CI workspace), also stop before ascending into `$HOME`: + // the project is a descendant of `$HOME`, so a user-level `~/.prettierrc`, `~/biome.json`, or + // `~/package.json` with formatter deps must never be read as the project's own config. + if (existsSync(path.join(dir, '.git')) || dir === root || dir === home || path.dirname(dir) === home) break; + dir = path.dirname(dir); + } + + if (found.biome) return FORMATTERS.biome; + if (found.prettier) return FORMATTERS.prettier; + if (found.eslint) return FORMATTERS.eslint; + return null; +} diff --git a/packages/codemod/src/utils/importUtils.ts b/packages/codemod/src/utils/importUtils.ts index 145c95328c..ecf3670af5 100644 --- a/packages/codemod/src/utils/importUtils.ts +++ b/packages/codemod/src/utils/importUtils.ts @@ -6,6 +6,7 @@ const SDK_PREFIX = '@modelcontextprotocol/sdk'; const V2_PACKAGES = new Set([ '@modelcontextprotocol/client', '@modelcontextprotocol/server', + '@modelcontextprotocol/core-internal', '@modelcontextprotocol/core', '@modelcontextprotocol/node', '@modelcontextprotocol/express' @@ -32,31 +33,52 @@ export function isTypeOnlyImport(imp: ImportDeclaration): boolean { return imp.isTypeOnly(); } +/** A named import to emit: either a bare name, or a `{ name, alias }` pair preserving an `as` alias. */ +export type NamedImportSpec = string | { name: string; alias?: string }; + +function toSpec(n: NamedImportSpec): { name: string; alias?: string } { + return typeof n === 'string' ? { name: n } : n; +} + +/** Local binding a spec introduces — the alias when present, otherwise the imported name. */ +function specLocalName(s: { name: string; alias?: string }): string { + return s.alias ?? s.name; +} + export function addOrMergeImport( sourceFile: SourceFile, moduleSpecifier: string, - namedImports: string[], + namedImports: NamedImportSpec[], isTypeOnly: boolean, insertIndex: number ): void { if (namedImports.length === 0) return; + const specs = namedImports.map(n => toSpec(n)); + const existing = sourceFile.getImportDeclarations().find(imp => { if (imp.getNamespaceImport()) return false; return imp.getModuleSpecifierValue() === moduleSpecifier && imp.isTypeOnly() === isTypeOnly; }); if (existing) { - const existingNames = new Set(existing.getNamedImports().map(n => n.getName())); - const newNames = namedImports.filter(n => !existingNames.has(n)); - if (newNames.length > 0) { - existing.addNamedImports(newNames); + const existingLocals = new Set(existing.getNamedImports().map(n => n.getAliasNode()?.getText() ?? n.getName())); + const newSpecs = specs.filter(s => !existingLocals.has(specLocalName(s))); + if (newSpecs.length > 0) { + existing.addNamedImports(newSpecs.map(s => (s.alias ? { name: s.name, alias: s.alias } : { name: s.name }))); } } else { + const seen = new Set(); + const deduped = specs.filter(s => { + const local = specLocalName(s); + if (seen.has(local)) return false; + seen.add(local); + return true; + }); const clampedIndex = Math.min(insertIndex, sourceFile.getImportDeclarations().length); sourceFile.insertImportDeclaration(clampedIndex, { moduleSpecifier, - namedImports: [...new Set(namedImports)], + namedImports: deduped.map(s => (s.alias ? { name: s.name, alias: s.alias } : { name: s.name })), isTypeOnly }); } diff --git a/packages/codemod/src/utils/packageJsonUpdater.ts b/packages/codemod/src/utils/packageJsonUpdater.ts index 54a7b97624..1b3eb07f8a 100644 --- a/packages/codemod/src/utils/packageJsonUpdater.ts +++ b/packages/codemod/src/utils/packageJsonUpdater.ts @@ -5,7 +5,7 @@ import type { PackageJsonChange } from '../types'; import { findPackageJson } from './projectAnalyzer'; const V1_PACKAGE = '@modelcontextprotocol/sdk'; -const PRIVATE_PACKAGES = new Set(['@modelcontextprotocol/core']); +const PRIVATE_PACKAGES = new Set(['@modelcontextprotocol/core-internal']); function normalizeToRoot(pkg: string): string { const secondSlash = pkg.indexOf('/', pkg.indexOf('/') + 1); diff --git a/packages/codemod/src/utils/projectAnalyzer.ts b/packages/codemod/src/utils/projectAnalyzer.ts index f8c1a40537..1a836021c7 100644 --- a/packages/codemod/src/utils/projectAnalyzer.ts +++ b/packages/codemod/src/utils/projectAnalyzer.ts @@ -1,11 +1,24 @@ -import { existsSync, readFileSync } from 'node:fs'; +import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'; import path from 'node:path'; import type { Diagnostic, TransformContext } from '../types'; -import { warning } from './diagnostics'; +import { info, warning } from './diagnostics'; const PROJECT_ROOT_MARKERS = ['.git', 'node_modules']; +const SCAN_EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs']); +const SCAN_SKIP_DIRS = new Set(['node_modules', 'dist', '.git', 'build', '.next', '.nuxt', 'coverage']); +const SCAN_FILE_BUDGET = 5000; + +// Matches a quoted v1 SDK client/server subpath import specifier — e.g. +// '@modelcontextprotocol/sdk/client/index.js' "@modelcontextprotocol/sdk/server/mcp.js" +// '@modelcontextprotocol/sdk/client' (extensionless / bare subpath; see the extensionless +// import matching the codemod already supports) +// Anchored to the opening quote and a trailing `/` or closing quote so that comments or prose that +// merely mention the path do not count, and `…/client` is not confused with `…/clientfoo`. +const CLIENT_IMPORT_RE = /['"`]@modelcontextprotocol\/sdk\/client(?:\/|['"`])/; +const SERVER_IMPORT_RE = /['"`]@modelcontextprotocol\/sdk\/server(?:\/|['"`])/; + export function findPackageJson(startDir: string): string | undefined { let dir = path.resolve(startDir); const root = path.parse(dir).root; @@ -20,27 +33,86 @@ export function findPackageJson(startDir: string): string | undefined { export function analyzeProject(targetDir: string): TransformContext { const pkgJsonPath = findPackageJson(targetDir); - if (!pkgJsonPath) { - return { projectType: 'unknown' }; + if (pkgJsonPath) { + try { + const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf8')); + const allDeps = { + ...pkgJson.dependencies, + ...pkgJson.devDependencies + }; + + const hasClient = '@modelcontextprotocol/client' in allDeps; + const hasServer = '@modelcontextprotocol/server' in allDeps; + + if (hasClient && hasServer) return { projectType: 'both' }; + if (hasClient) return { projectType: 'client' }; + if (hasServer) return { projectType: 'server' }; + // No v2 split deps — this is almost always a v1 project mid-migration (v1 ships as the single + // `@modelcontextprotocol/sdk` package). Fall through to inferring the type from source usage. + } catch { + // Malformed package.json — fall through to source inference. + } } - try { - const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf8')); - const allDeps = { - ...pkgJson.dependencies, - ...pkgJson.devDependencies - }; + return { projectType: inferProjectTypeFromSource(targetDir) }; +} - const hasClient = '@modelcontextprotocol/client' in allDeps; - const hasServer = '@modelcontextprotocol/server' in allDeps; +/** + * Infer client vs server vs both by scanning the source for v1 SDK subpath imports: a + * `@modelcontextprotocol/sdk/client/...` specifier means the project will need + * `@modelcontextprotocol/client`; a `.../server/...` specifier means it needs `@modelcontextprotocol/server`. + * Files that import only shared paths (`types.js`, `shared/...`) give no signal. The scan matches quoted + * specifiers (not bare substrings), so comments/prose are ignored. Bounded: skips heavy dirs, caps the + * file count, and early-exits once both signals are seen. + */ +function inferProjectTypeFromSource(targetDir: string): TransformContext['projectType'] { + let usesClient = false; + let usesServer = false; + let scanned = 0; - if (hasClient && hasServer) return { projectType: 'both' }; - if (hasClient) return { projectType: 'client' }; - if (hasServer) return { projectType: 'server' }; - return { projectType: 'unknown' }; + const visit = (dir: string): void => { + if (usesClient && usesServer) return; + let entries: import('node:fs').Dirent[]; + try { + entries = readdirSync(dir, { withFileTypes: true }); + } catch { + return; + } + for (const entry of entries) { + if (usesClient && usesServer) return; + const full = path.join(dir, entry.name); + if (entry.isDirectory()) { + if (SCAN_SKIP_DIRS.has(entry.name)) continue; + visit(full); + } else if (entry.isFile()) { + const ext = path.extname(entry.name); + if (!SCAN_EXTENSIONS.has(ext) || entry.name.endsWith('.d.ts')) continue; + if (scanned >= SCAN_FILE_BUDGET) return; + scanned++; + let content: string; + try { + content = readFileSync(full, 'utf8'); + } catch { + continue; + } + if (!usesClient && CLIENT_IMPORT_RE.test(content)) usesClient = true; + if (!usesServer && SERVER_IMPORT_RE.test(content)) usesServer = true; + } + } + }; + + let root = targetDir; + try { + if (!statSync(targetDir).isDirectory()) root = path.dirname(targetDir); } catch { - return { projectType: 'unknown' }; + return 'unknown'; } + visit(root); + + if (usesClient && usesServer) return 'both'; + if (usesClient) return 'client'; + if (usesServer) return 'server'; + return 'unknown'; } export function resolveTypesPackage( @@ -61,6 +133,22 @@ export function resolveTypesPackage( if (context.projectType === 'server') { return '@modelcontextprotocol/server'; } + if (context.projectType === 'both') { + // Both packages are present and both re-export the shared protocol types (from core), so importing + // from either compiles. This file has no client/server-specific signal — default to server and note + // it as an optional preference, not an action-required warning. + if (diagnosticSink) { + diagnosticSink.diagnostics.push( + info( + diagnosticSink.filePath, + diagnosticSink.line, + 'Shared protocol types imported from @modelcontextprotocol/server (both client and server ' + + 're-export them). Switch to @modelcontextprotocol/client if this is client-only code.' + ) + ); + } + return '@modelcontextprotocol/server'; + } if (diagnosticSink) { diagnosticSink.diagnostics.push( warning( diff --git a/packages/codemod/test/commentInsertion.test.ts b/packages/codemod/test/commentInsertion.test.ts index b8e2b2e98e..8862303633 100644 --- a/packages/codemod/test/commentInsertion.test.ts +++ b/packages/codemod/test/commentInsertion.test.ts @@ -87,11 +87,12 @@ describe('comment insertion', () => { it('inserts multiple comments in one file in correct positions', () => { const dir = createTempDir(); - // Two .parse() calls on different schemas trigger two actionRequired diagnostics + // Two custom-schema handler registrations on different lines trigger two actionRequired diagnostics const input = [ - `import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';`, - `const a = CallToolRequestSchema.parse(data1);`, - `const b = ListToolsRequestSchema.parse(data2);`, + `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';`, + `const server = new McpServer({ name: 'test', version: '1.0' });`, + `server.setRequestHandler(FooSchema, async () => ({}));`, + `server.setRequestHandler(BarSchema, async () => ({}));`, `` ].join('\n'); writeFileSync(path.join(dir, 'server.ts'), input); @@ -107,9 +108,9 @@ describe('comment insertion', () => { it('preserves indentation of the target line', () => { const dir = createTempDir(); const input = [ - `import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';`, - `function validate() {`, - ` const a = CallToolRequestSchema.parse(data);`, + `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';`, + `function register(server: McpServer) {`, + ` server.setRequestHandler(FooSchema, async () => ({}));`, `}`, `` ].join('\n'); @@ -125,8 +126,9 @@ describe('comment insertion', () => { it('does not duplicate comments on re-run (idempotency)', () => { const dir = createTempDir(); const input = [ - `import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';`, - `const a = CallToolRequestSchema.parse(data);`, + `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';`, + `const server = new McpServer({ name: 'test', version: '1.0' });`, + `server.setRequestHandler(FooSchema, async () => ({}));`, `` ].join('\n'); writeFileSync(path.join(dir, 'server.ts'), input); @@ -146,10 +148,11 @@ describe('comment insertion', () => { it('sanitizes */ in diagnostic messages', () => { const dir = createTempDir(); - // The .parse() diagnostic message doesn't contain */, but we verify the comment is well-formed + // The handler diagnostic message doesn't contain */, but we verify the comment is well-formed const input = [ - `import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';`, - `const a = CallToolRequestSchema.parse(data);`, + `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';`, + `const server = new McpServer({ name: 'test', version: '1.0' });`, + `server.setRequestHandler(FooSchema, async () => ({}));`, `` ].join('\n'); writeFileSync(path.join(dir, 'server.ts'), input); @@ -167,10 +170,10 @@ describe('comment insertion', () => { // Import rewrite adds new import lines (splitting into multiple packages), // then handler transform emits actionRequired. The comment must land at the correct post-shift line. const input = [ - `import { McpServer, CallToolRequestSchema } from '@modelcontextprotocol/sdk/server/mcp.js';`, + `import { McpServer, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/server/mcp.js';`, ``, `const server = new McpServer({ name: 'test', version: '1.0' });`, - `const a = CallToolRequestSchema.parse(data);`, + `server.setRequestHandler(FooSchema, async () => ({}));`, `` ].join('\n'); writeFileSync(path.join(dir, 'server.ts'), input); @@ -181,16 +184,18 @@ describe('comment insertion', () => { const lines = output.split('\n'); const commentIdx = lines.findIndex(l => l.includes(CODEMOD_ERROR_PREFIX)); expect(commentIdx).toBeGreaterThan(-1); - // The comment should be directly above the parse() line (which may have moved) + // The comment should be directly above the handler line (which may have moved) const nextLine = lines[commentIdx + 1]!; - expect(nextLine).toContain('.parse(data)'); + expect(nextLine).toContain('setRequestHandler'); }); it('merges same-line diagnostics into a single comment', () => { const dir = createTempDir(); + // Two custom-schema handler registrations on the SAME physical line -> two same-line diagnostics const input = [ - `import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';`, - `const a = CallToolRequestSchema.parse(data1); const b = ListToolsRequestSchema.parse(data2);`, + `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';`, + `const server = new McpServer({ name: 'test', version: '1.0' });`, + `server.setRequestHandler(FooSchema, async () => ({})); server.setRequestHandler(BarSchema, async () => ({}));`, `` ].join('\n'); writeFileSync(path.join(dir, 'server.ts'), input); @@ -207,9 +212,10 @@ describe('comment insertion', () => { it('skips comment insertion when target line is inside a template literal', () => { const dir = createTempDir(); const input = [ - "import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';", + "import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';", + "const server = new McpServer({ name: 'test', version: '1.0' });", 'const msg = `', - ' Result: ${CallToolRequestSchema.parse(data).method}', + ' Result: ${server.setRequestHandler(FooSchema, async () => ({}))}', '`;', '' ].join('\n'); @@ -230,10 +236,11 @@ describe('comment insertion', () => { const dir = createTempDir(); // TemplateMiddle: text between two ${} spans const input = [ - "import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';", + "import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';", + "const server = new McpServer({ name: 'test', version: '1.0' });", 'const msg = `${somePrefix}', - ' A: ${CallToolRequestSchema.parse(d1)}', - ' B: ${ListToolsRequestSchema.parse(d2)}', + ' A: ${server.setRequestHandler(FooSchema, async () => ({}))}', + ' B: ${server.setRequestHandler(BarSchema, async () => ({}))}', '`;', '' ].join('\n'); @@ -249,11 +256,12 @@ describe('comment insertion', () => { it('still inserts comment when diagnostic line merely contains a template literal', () => { const dir = createTempDir(); - // The .parse() and template are on the same line, but lineStart is at "const", + // The handler call and template are on the same line, but lineStart is at "server", // which is outside the template literal. const input = [ - "import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';", - 'const a = CallToolRequestSchema.parse(`template ${data}`);', + "import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';", + "const server = new McpServer({ name: 'test', version: '1.0' });", + 'server.setRequestHandler(FooSchema, async () => ({ msg: `template ${data}` }));', '' ].join('\n'); writeFileSync(path.join(dir, 'server.ts'), input); @@ -272,8 +280,9 @@ describe('comment insertion', () => { it('handles CRLF line endings without corrupting the file', () => { const dir = createTempDir(); const input = [ - `import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';`, - `const a = CallToolRequestSchema.parse(data);`, + `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';`, + `const server = new McpServer({ name: 'test', version: '1.0' });`, + `server.setRequestHandler(FooSchema, async () => ({}));`, `` ].join('\r\n'); writeFileSync(path.join(dir, 'server.ts'), input); @@ -286,6 +295,6 @@ describe('comment insertion', () => { const commentIdx = lines.findIndex(l => l.includes(CODEMOD_ERROR_PREFIX)); expect(commentIdx).toBeGreaterThan(-1); expect(lines[commentIdx]!.trim()).toMatch(/^\/\*.*\*\/$/); - expect(lines[commentIdx + 1]).toContain('.parse(data)'); + expect(lines[commentIdx + 1]).toContain('setRequestHandler'); }); }); diff --git a/packages/codemod/test/detectFormatter.test.ts b/packages/codemod/test/detectFormatter.test.ts new file mode 100644 index 0000000000..493cc3b574 --- /dev/null +++ b/packages/codemod/test/detectFormatter.test.ts @@ -0,0 +1,153 @@ +import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { describe, it, expect, afterEach } from 'vitest'; + +import { detectFormatter } from '../src/utils/detectFormatter'; + +let tempDir: string; + +function createTempDir(): string { + tempDir = mkdtempSync(path.join(tmpdir(), 'mcp-codemod-formatter-')); + return tempDir; +} + +function writePkg(dir: string, pkg: Record): void { + writeFileSync(path.join(dir, 'package.json'), JSON.stringify(pkg)); +} + +afterEach(() => { + if (tempDir) { + rmSync(tempDir, { recursive: true, force: true }); + } +}); + +describe('detectFormatter', () => { + it('returns null when no formatter is configured', () => { + const dir = createTempDir(); + writePkg(dir, { devDependencies: { typescript: '^5' } }); + + expect(detectFormatter(dir)).toBeNull(); + }); + + it('detects Prettier from a config file', () => { + const dir = createTempDir(); + writeFileSync(path.join(dir, 'prettier.config.mjs'), 'export default {};'); + + const result = detectFormatter(dir); + expect(result?.name).toBe('Prettier'); + expect(result?.bin).toBe('prettier'); + expect(result?.writeArgs).toEqual(['--write']); + }); + + it('detects Prettier from the "prettier" key in package.json', () => { + const dir = createTempDir(); + writePkg(dir, { prettier: { singleQuote: false } }); + + expect(detectFormatter(dir)?.name).toBe('Prettier'); + }); + + it('detects Prettier from devDependencies', () => { + const dir = createTempDir(); + writePkg(dir, { devDependencies: { prettier: '^3' } }); + + expect(detectFormatter(dir)?.name).toBe('Prettier'); + }); + + it('detects Biome from biome.json', () => { + const dir = createTempDir(); + writeFileSync(path.join(dir, 'biome.json'), '{}'); + + const result = detectFormatter(dir); + expect(result?.name).toBe('Biome'); + expect(result?.bin).toBe('biome'); + expect(result?.writeArgs).toEqual(['format', '--write']); + }); + + it('does not detect dprint — a lone dprint.json yields null', () => { + const dir = createTempDir(); + writeFileSync(path.join(dir, 'dprint.json'), '{}'); + + expect(detectFormatter(dir)).toBeNull(); + }); + + it('detects ESLint when only ESLint is configured', () => { + const dir = createTempDir(); + writeFileSync(path.join(dir, 'eslint.config.js'), 'export default [];'); + + const result = detectFormatter(dir); + expect(result?.name).toBe('ESLint'); + expect(result?.bin).toBe('eslint'); + expect(result?.writeArgs).toEqual(['--fix']); + }); + + it('prefers Prettier over ESLint when both are configured (the common prettier-plugin case)', () => { + const dir = createTempDir(); + writeFileSync(path.join(dir, 'eslint.config.js'), 'export default [];'); + writeFileSync(path.join(dir, 'prettier.config.mjs'), 'export default {};'); + + expect(detectFormatter(dir)?.name).toBe('Prettier'); + }); + + it('prefers Biome over Prettier when both are configured', () => { + const dir = createTempDir(); + writeFileSync(path.join(dir, 'biome.json'), '{}'); + writeFileSync(path.join(dir, 'prettier.config.mjs'), 'export default {};'); + + expect(detectFormatter(dir)?.name).toBe('Biome'); + }); + + it('walks up directory levels to find the formatter (monorepo layout)', () => { + const dir = createTempDir(); + const src = path.join(dir, 'packages', 'mcp', 'src'); + mkdirSync(src, { recursive: true }); + writeFileSync(path.join(dir, 'prettier.config.mjs'), 'export default {};'); + + expect(detectFormatter(src)?.name).toBe('Prettier'); + }); + + it('stops at the .git boundary and does not detect config above the repo root', () => { + const dir = createTempDir(); + const src = path.join(dir, 'project', 'src'); + mkdirSync(src, { recursive: true }); + mkdirSync(path.join(dir, 'project', '.git'), { recursive: true }); + // Config lives above the repo root — must not be picked up. + writeFileSync(path.join(dir, 'prettier.config.mjs'), 'export default {};'); + + expect(detectFormatter(src)).toBeNull(); + }); + + it('does not match a user-level $HOME config for a non-git project (stops at $HOME)', () => { + const home = createTempDir(); + const projectSrc = path.join(home, 'projects', 'app', 'src'); + mkdirSync(projectSrc, { recursive: true }); + // A user-level config sits at $HOME, above a non-git project — it must never be read as the + // project's config (the walk must stop before ascending into $HOME). + writeFileSync(path.join(home, 'prettier.config.mjs'), 'export default {};'); + + expect(detectFormatter(projectSrc, home)).toBeNull(); + }); + + it('reads the project root directly under $HOME but never $HOME itself', () => { + const home = createTempDir(); + const projectRoot = path.join(home, 'app'); + const src = path.join(projectRoot, 'src'); + mkdirSync(src, { recursive: true }); + // A higher-precedence formatter configured at $HOME must NOT shadow the project's own. + writeFileSync(path.join(home, 'biome.json'), '{}'); + writeFileSync(path.join(projectRoot, 'eslint.config.js'), 'export default [];'); + + // Only the project's ESLint is detected; $HOME's Biome is never read. + expect(detectFormatter(src, home)?.name).toBe('ESLint'); + }); + + it('detects a project config below $HOME (the boundary only blocks $HOME and above)', () => { + const home = createTempDir(); + const projectRoot = path.join(home, 'projects', 'app'); + const src = path.join(projectRoot, 'src'); + mkdirSync(src, { recursive: true }); + writeFileSync(path.join(projectRoot, 'biome.json'), '{}'); + + expect(detectFormatter(src, home)?.name).toBe('Biome'); + }); +}); diff --git a/packages/codemod/test/integration.test.ts b/packages/codemod/test/integration.test.ts index c6292338d8..c662c68b21 100644 --- a/packages/codemod/test/integration.test.ts +++ b/packages/codemod/test/integration.test.ts @@ -379,6 +379,70 @@ describe('integration', () => { expect(pkgJson.dependencies['express']).toBe('^4.0.0'); }); + it('package.json: does not add core when every schema import is rewritten away', () => { + // The dominant v1 pattern: a `*Schema` constant used ONLY as a setRequestHandler first arg. + // importPaths routes it to @modelcontextprotocol/core (recording the package), but + // handlerRegistration then rewrites the call to a method string and deletes the now-unused + // import. No core import survives, so package.json must NOT gain a core dependency. + const dir = createTempDir(); + writePkgJson(dir, { dependencies: { '@modelcontextprotocol/sdk': '^1.0.0' } }); + writeFileSync( + path.join(dir, 'server.ts'), + [ + `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';`, + `import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';`, + `const server = new McpServer({ name: 'test', version: '1.0' });`, + `server.setRequestHandler(CallToolRequestSchema, async () => ({ content: [] }));`, + `` + ].join('\n') + ); + + const result = run(migration, { targetDir: dir }); + + // The schema usage was rewritten and its import deleted. + const output = readFileSync(path.join(dir, 'server.ts'), 'utf8'); + expect(output).toContain("setRequestHandler('tools/call'"); + expect(output).not.toContain('core'); + + // So core must not be added; the package actually imported (server) still is. + expect(result.packageJsonChanges).toBeDefined(); + expect(result.packageJsonChanges!.added).toContain('@modelcontextprotocol/server'); + expect(result.packageJsonChanges!.added).not.toContain('@modelcontextprotocol/core'); + + const pkgJson = JSON.parse(readFileSync(path.join(dir, 'package.json'), 'utf8')); + expect(pkgJson.dependencies['@modelcontextprotocol/core']).toBeUndefined(); + expect(pkgJson.dependencies['@modelcontextprotocol/server']).toBeDefined(); + }); + + it('package.json: still adds core when a schema import survives as a value', () => { + // Guard against over-correcting: a schema used as a value (e.g. `.parse(...)`) keeps its import, + // so core remains a real dependency and must still be added. + const dir = createTempDir(); + writePkgJson(dir, { dependencies: { '@modelcontextprotocol/sdk': '^1.0.0' } }); + writeFileSync( + path.join(dir, 'lib.ts'), + [ + `import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';`, + `export function parseResult(x: unknown) {`, + ` return CallToolResultSchema.parse(x);`, + `}`, + `` + ].join('\n') + ); + + const result = run(migration, { targetDir: dir }); + + const output = readFileSync(path.join(dir, 'lib.ts'), 'utf8'); + expect(output).toContain('@modelcontextprotocol/core'); + expect(output).toContain('CallToolResultSchema.parse'); + + expect(result.packageJsonChanges).toBeDefined(); + expect(result.packageJsonChanges!.added).toContain('@modelcontextprotocol/core'); + + const pkgJson = JSON.parse(readFileSync(path.join(dir, 'package.json'), 'utf8')); + expect(pkgJson.dependencies['@modelcontextprotocol/core']).toBeDefined(); + }); + it('does not modify package.json in dry-run mode', () => { const dir = createTempDir(); writePkgJson(dir, { diff --git a/packages/codemod/test/packageJsonUpdater.test.ts b/packages/codemod/test/packageJsonUpdater.test.ts index 2c272b8642..9b1e993e2f 100644 --- a/packages/codemod/test/packageJsonUpdater.test.ts +++ b/packages/codemod/test/packageJsonUpdater.test.ts @@ -145,7 +145,7 @@ describe('updatePackageJson', () => { expect(deps['@modelcontextprotocol/server']).toBeUndefined(); }); - it('filters out @modelcontextprotocol/core (private package)', () => { + it('filters out @modelcontextprotocol/core-internal (private package)', () => { const dir = createTempDir(); writePkgJson(dir, { dependencies: { @@ -153,15 +153,15 @@ describe('updatePackageJson', () => { } }); - const result = updatePackageJson(dir, new Set(['@modelcontextprotocol/core', '@modelcontextprotocol/server']), false); + const result = updatePackageJson(dir, new Set(['@modelcontextprotocol/core-internal', '@modelcontextprotocol/server']), false); expect(result).toBeDefined(); - expect(result!.added).not.toContain('@modelcontextprotocol/core'); + expect(result!.added).not.toContain('@modelcontextprotocol/core-internal'); expect(result!.added).toContain('@modelcontextprotocol/server'); const pkg = readPkgJson(dir); const deps = pkg.dependencies as Record; - expect(deps['@modelcontextprotocol/core']).toBeUndefined(); + expect(deps['@modelcontextprotocol/core-internal']).toBeUndefined(); }); it('preserves 4-space indentation', () => { diff --git a/packages/codemod/test/projectAnalyzer.test.ts b/packages/codemod/test/projectAnalyzer.test.ts index e19aec3553..222894aef7 100644 --- a/packages/codemod/test/projectAnalyzer.test.ts +++ b/packages/codemod/test/projectAnalyzer.test.ts @@ -3,7 +3,7 @@ import { tmpdir } from 'node:os'; import path from 'node:path'; import { describe, it, expect, afterEach } from 'vitest'; -import { analyzeProject } from '../src/utils/projectAnalyzer'; +import { analyzeProject, resolveTypesPackage } from '../src/utils/projectAnalyzer'; let tempDir: string; @@ -115,7 +115,7 @@ describe('analyzeProject', () => { expect(result.projectType).toBe('server'); }); - it('returns unknown for v1 SDK package (falls through to per-file resolution)', () => { + it('returns unknown for a v1 SDK package with no source signal', () => { const dir = createTempDir(); writeFileSync( path.join(dir, 'package.json'), @@ -127,4 +127,97 @@ describe('analyzeProject', () => { const result = analyzeProject(dir); expect(result.projectType).toBe('unknown'); }); + + describe('source inference for v1 (pre-split) projects', () => { + function v1Project(files: Record): string { + const dir = createTempDir(); + writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ dependencies: { '@modelcontextprotocol/sdk': '^1.0.0' } })); + mkdirSync(path.join(dir, 'src'), { recursive: true }); + for (const [name, content] of Object.entries(files)) { + writeFileSync(path.join(dir, 'src', name), content); + } + return dir; + } + + it('infers client from sdk/client subpath usage', () => { + const dir = v1Project({ 'a.ts': `import { Client } from '@modelcontextprotocol/sdk/client/index.js';` }); + expect(analyzeProject(dir).projectType).toBe('client'); + }); + + it('infers server from sdk/server subpath usage', () => { + const dir = v1Project({ 'a.ts': `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';` }); + expect(analyzeProject(dir).projectType).toBe('server'); + }); + + it('infers both when client and server subpaths are used across files', () => { + const dir = v1Project({ + 'client.ts': `import { Client } from '@modelcontextprotocol/sdk/client/index.js';`, + 'server.ts': `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';` + }); + expect(analyzeProject(dir).projectType).toBe('both'); + }); + + it('infers from an extensionless / bare sdk subpath specifier', () => { + const dir = v1Project({ 'a.ts': `import { McpServer } from '@modelcontextprotocol/sdk/server';` }); + expect(analyzeProject(dir).projectType).toBe('server'); + }); + + it('stays unknown when only shared paths are imported', () => { + const dir = v1Project({ 'a.ts': `import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';` }); + expect(analyzeProject(dir).projectType).toBe('unknown'); + }); + + it('ignores an import path that appears only in a comment (not a quoted specifier)', () => { + // A real client import plus a comment mentioning the server subpath. A whole-file substring + // scan would flip this to "both"; the quote-anchored match keeps it "client". + const dir = v1Project({ + 'a.ts': [ + `import { Client } from '@modelcontextprotocol/sdk/client/index.js';`, + `// previously imported from @modelcontextprotocol/sdk/server/mcp.js`, + '' + ].join('\n') + }); + expect(analyzeProject(dir).projectType).toBe('client'); + }); + + it('infers from source even without a package.json', () => { + const dir = createTempDir(); + mkdirSync(path.join(dir, 'src'), { recursive: true }); + writeFileSync(path.join(dir, 'src', 'a.ts'), `import { Client } from '@modelcontextprotocol/sdk/client/index.js';`); + expect(analyzeProject(path.join(dir, 'src')).projectType).toBe('client'); + }); + + it('ignores node_modules when scanning source', () => { + const dir = v1Project({ 'a.ts': `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';` }); + mkdirSync(path.join(dir, 'node_modules', 'pkg'), { recursive: true }); + writeFileSync( + path.join(dir, 'node_modules', 'pkg', 'index.ts'), + `import { Client } from '@modelcontextprotocol/sdk/client/index.js';` + ); + // Only the server import in src counts; the client import under node_modules is skipped. + expect(analyzeProject(dir).projectType).toBe('server'); + }); + }); +}); + +describe('resolveTypesPackage', () => { + it('emits an info note (not a warning) for a both-project ambiguous file', () => { + const sink = { filePath: 'f.ts', line: 1, diagnostics: [] as import('../src/types').Diagnostic[] }; + const target = resolveTypesPackage({ projectType: 'both' }, false, false, sink); + expect(target).toBe('@modelcontextprotocol/server'); + expect(sink.diagnostics).toHaveLength(1); + expect(sink.diagnostics[0]!.level).toBe('info'); + }); + + it('emits an action-required warning for a genuinely unknown project', () => { + const sink = { filePath: 'f.ts', line: 1, diagnostics: [] as import('../src/types').Diagnostic[] }; + resolveTypesPackage({ projectType: 'unknown' }, false, false, sink); + expect(sink.diagnostics).toHaveLength(1); + expect(sink.diagnostics[0]!.level).toBe('warning'); + }); + + it('resolves by per-file signal regardless of project type', () => { + expect(resolveTypesPackage({ projectType: 'both' }, true, false)).toBe('@modelcontextprotocol/client'); + expect(resolveTypesPackage({ projectType: 'unknown' }, false, true)).toBe('@modelcontextprotocol/server'); + }); }); diff --git a/packages/codemod/test/v1-to-v2/authSchemaNames.test.ts b/packages/codemod/test/v1-to-v2/authSchemaNames.test.ts new file mode 100644 index 0000000000..2fd4631c2d --- /dev/null +++ b/packages/codemod/test/v1-to-v2/authSchemaNames.test.ts @@ -0,0 +1,36 @@ +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; + +import { describe, expect, it } from 'vitest'; + +import { AUTH_SCHEMA_NAMES, AUTH_SCHEMA_NAMES_NO_V2_PUBLIC_EXPORT } from '../../src/migrations/v1-to-v2/mappings/authSchemaNames'; + +describe('AUTH_SCHEMA_NAMES (codemod auth schema-routing allowlist)', () => { + it('routes only auth schemas that @modelcontextprotocol/core exports (drift guard)', () => { + // The import transform routes a `*Schema` symbol from sdk/shared/auth.js to core only when + // its name is in AUTH_SCHEMA_NAMES, so EVERY name here MUST be exported by core — otherwise + // the rewritten import would have no exported member. AUTH_SCHEMA_NAMES is the v1 auth-schema set, + // a SUBSET of core's auth exports: core may export more (v2-only schemas such as + // IdJagTokenExchangeResponseSchema) that v1 never had and the codemod never encounters. Read + // core's barrel directly (the `export { … } from '…/core-internal/auth'` block) so they cannot drift. + const src = readFileSync(fileURLToPath(new URL('../../../core/src/index.ts', import.meta.url)), 'utf8'); + const closeIdx = src.indexOf("} from '@modelcontextprotocol/core-internal/auth'"); + const openIdx = src.lastIndexOf('export {', closeIdx); + const block = src.slice(openIdx + 'export {'.length, closeIdx); + const coreAuthExports = new Set([...block.matchAll(/\b(\w+Schema)\b/g)].map(m => m[1])); + + const notExportedByCore = [...AUTH_SCHEMA_NAMES].filter(name => !coreAuthExports.has(name)); + expect(notExportedByCore).toEqual([]); + // The v1 auth-schema set is frozen; pin its size so an accidental add/remove is caught. + expect(AUTH_SCHEMA_NAMES.size).toBe(11); + }); + + it('keeps the no-v2-home auth schemas OUT of the routing allowlist', () => { + // SafeUrlSchema/OptionalSafeUrlSchema have no public v2 export, so they must NOT be routed to + // core (the import transform flags them instead). Guard the two sets stay disjoint. + for (const name of AUTH_SCHEMA_NAMES_NO_V2_PUBLIC_EXPORT) { + expect(AUTH_SCHEMA_NAMES.has(name)).toBe(false); + } + expect([...AUTH_SCHEMA_NAMES_NO_V2_PUBLIC_EXPORT].sort()).toEqual(['OptionalSafeUrlSchema', 'SafeUrlSchema']); + }); +}); diff --git a/packages/codemod/test/v1-to-v2/specSchemaNames.test.ts b/packages/codemod/test/v1-to-v2/specSchemaNames.test.ts new file mode 100644 index 0000000000..2a5aca1267 --- /dev/null +++ b/packages/codemod/test/v1-to-v2/specSchemaNames.test.ts @@ -0,0 +1,21 @@ +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; + +import { describe, expect, it } from 'vitest'; + +import { SPEC_SCHEMA_NAMES } from '../../src/migrations/v1-to-v2/mappings/specSchemaNames'; + +describe('SPEC_SCHEMA_NAMES (codemod schema-routing allowlist)', () => { + it("matches @modelcontextprotocol/core's exported schema set exactly (drift guard)", () => { + // The import transform routes a `*Schema` symbol from sdk/types.js to core only when the + // symbol's (rename-resolved) name is in this set. It must therefore equal core's actual + // public exports: a name missing here would be misrouted to client/server (which export no Zod + // schema values), and a name here that core does not export would produce a broken import. + // Read core's barrel directly so the two cannot silently drift. + const src = readFileSync(fileURLToPath(new URL('../../../core/src/index.ts', import.meta.url)), 'utf8'); + const block = src.slice(src.indexOf('export {') + 'export {'.length, src.indexOf('} from')); + const coreExports = [...new Set([...block.matchAll(/\b(\w+Schema)\b/g)].map(m => m[1]))].sort(); + expect([...SPEC_SCHEMA_NAMES].sort()).toEqual(coreExports); + expect(coreExports.length).toBeGreaterThanOrEqual(154); + }); +}); diff --git a/packages/codemod/test/v1-to-v2/transforms/handlerRegistration.test.ts b/packages/codemod/test/v1-to-v2/transforms/handlerRegistration.test.ts index 8fdea4d000..51b82521e3 100644 --- a/packages/codemod/test/v1-to-v2/transforms/handlerRegistration.test.ts +++ b/packages/codemod/test/v1-to-v2/transforms/handlerRegistration.test.ts @@ -219,6 +219,28 @@ describe('handler-registration transform', () => { expect(result).not.toContain('ElicitationCompleteNotificationSchema'); }); + it('replaces TaskStatusNotificationSchema with the tasks/status method string', () => { + const input = [ + `import { TaskStatusNotificationSchema } from '@modelcontextprotocol/sdk/types.js';`, + `client.setNotificationHandler(TaskStatusNotificationSchema, async () => {});`, + '' + ].join('\n'); + const result = applyTransform(input); + expect(result).toContain("setNotificationHandler('notifications/tasks/status'"); + expect(result).not.toContain('TaskStatusNotificationSchema'); + }); + + it('replaces task request schemas (GetTaskRequestSchema → tasks/get)', () => { + const input = [ + `import { GetTaskRequestSchema } from '@modelcontextprotocol/sdk/types.js';`, + `server.setRequestHandler(GetTaskRequestSchema, async () => ({}));`, + '' + ].join('\n'); + const result = applyTransform(input); + expect(result).toContain("setRequestHandler('tasks/get'"); + expect(result).not.toContain('GetTaskRequestSchema'); + }); + it('does not emit diagnostic when first arg is a string literal (v2 style)', () => { const input = [`server.setRequestHandler('tools/call', async (request) => {`, ` return { content: [] };`, `});`, ''].join('\n'); const project = new Project({ useInMemoryFileSystem: true }); diff --git a/packages/codemod/test/v1-to-v2/transforms/importPaths.test.ts b/packages/codemod/test/v1-to-v2/transforms/importPaths.test.ts index f2bf7a647f..cc33f75ba8 100644 --- a/packages/codemod/test/v1-to-v2/transforms/importPaths.test.ts +++ b/packages/codemod/test/v1-to-v2/transforms/importPaths.test.ts @@ -74,25 +74,364 @@ describe('import-paths transform', () => { expect(result.diagnostics[0]!.message).toContain('SSEServerTransport is deprecated'); }); - it('resolves sdk/types.js based on sibling client imports', () => { + it('resolves a sdk/types.js TYPE import based on sibling client imports', () => { const input = [ `import { Client } from '@modelcontextprotocol/sdk/client/index.js';`, - `import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';`, + `import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';`, '' ].join('\n'); const result = applyTransform(input, { projectType: 'both' }); expect(result).toContain(`from "@modelcontextprotocol/client"`); + expect(result).toContain('CallToolResult'); + expect(result).not.toContain('@modelcontextprotocol/core'); + }); + + it('resolves a sdk/types.js TYPE import based on sibling server imports', () => { + const input = [ + `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';`, + `import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';`, + '' + ].join('\n'); + const result = applyTransform(input, { projectType: 'both' }); + expect(result).toContain(`from "@modelcontextprotocol/server"`); + expect(result).toContain('CallToolResult'); + }); + + it('routes *Schema imports from sdk/types.js to @modelcontextprotocol/core', () => { + const input = `import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';\n`; + const result = applyTransform(input, { projectType: 'server' }); + expect(result).toContain(`from "@modelcontextprotocol/core"`); expect(result).toContain('CallToolResultSchema'); + expect(result).not.toContain('@modelcontextprotocol/sdk/types'); + }); + + it('routes schemas to core regardless of client/server sibling context', () => { + // The only sibling is a client import, but the schema must still go to core. + const input = [ + `import { Client } from '@modelcontextprotocol/sdk/client/index.js';`, + `import { ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js';`, + '' + ].join('\n'); + const result = applyTransform(input, { projectType: 'both' }); + expect(result).toContain(`from "@modelcontextprotocol/core"`); + expect(result).toContain('ListToolsResultSchema'); }); - it('resolves sdk/types.js based on sibling server imports', () => { + it('splits a mixed type + schema import: type resolves by context, schema to core', () => { const input = [ `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';`, - `import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';`, + `import { CallToolResult, CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';`, '' ].join('\n'); const result = applyTransform(input, { projectType: 'both' }); + expect(result).toContain(`from "@modelcontextprotocol/core"`); + expect(result).toContain(`from "@modelcontextprotocol/server"`); + expect(result).toContain('CallToolResult'); + expect(result).toContain('CallToolResultSchema'); + expect(result).not.toContain('@modelcontextprotocol/sdk/types'); + }); + + it('splits an aliased types.js import: schema constant to core, aliased type to server', () => { + // The presence of an alias (`Tool as SDKTool`) must not force the whole import into one package; + // each symbol still routes to its correct v2 target, with the alias preserved. + const input = [ + `import { CreateMessageRequestSchema, ClientCapabilities, Tool as SDKTool } from '@modelcontextprotocol/sdk/types.js';`, + '' + ].join('\n'); + const result = applyTransform(input, { projectType: 'server' }); + expect(result).toMatch(/import\s*\{[^}]*\bCreateMessageRequestSchema\b[^}]*\}\s*from\s*["']@modelcontextprotocol\/core["']/); + expect(result).toMatch(/import\s*\{[^}]*\bClientCapabilities\b[^}]*\}\s*from\s*["']@modelcontextprotocol\/server["']/); + expect(result).toContain('Tool as SDKTool'); + // the schema constant must NOT end up imported from @modelcontextprotocol/server + expect(result).not.toMatch(/import\s*\{[^}]*CreateMessageRequestSchema[^}]*\}\s*from\s*["']@modelcontextprotocol\/server["']/); + expect(result).not.toContain('@modelcontextprotocol/sdk/types'); + }); + + it('does not emit a "mixes symbols" diagnostic for an aliased mixed import (it splits instead)', () => { + const input = `import { CreateMessageRequestSchema, Tool as SDKTool } from '@modelcontextprotocol/sdk/types.js';\n`; + const project = new Project({ useInMemoryFileSystem: true }); + const sourceFile = project.createSourceFile('test.ts', input); + const result = importPathsTransform.apply(sourceFile, { projectType: 'server' }); + expect(result.diagnostics.some(d => d.message.includes('mixes symbols'))).toBe(false); + }); + + it('preserves a leading file-header comment when rewriting the first SDK import', () => { + const input = [ + `/**`, + ` * Web-standard transport for MCP.`, + ` */`, + `import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';`, + `import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';`, + '' + ].join('\n'); + const result = applyTransform(input, { projectType: 'server' }); + expect(result).toContain('Web-standard transport for MCP.'); + expect(result).toContain('@modelcontextprotocol/server'); + expect(result).not.toContain('@modelcontextprotocol/sdk/'); + }); + + it('does not duplicate a multi-block leading header (blank line) when rewriting the first import in place', () => { + // The first SDK import is a namespace import, so it is rewritten in place (setModuleSpecifier) and + // its leading comments survive. The header is two // blocks separated by a BLANK line. A `\n`-join + // of the comment ranges loses that blank line, so the survival check would mis-fire and re-insert + // the header — duplicating it. The captured text must match the file's bytes exactly. + const input = [ + `// Copyright ACME`, + ``, + `// Notes about the types module`, + `import * as types from '@modelcontextprotocol/sdk/types.js';`, + '' + ].join('\n'); + const result = applyTransform(input, { projectType: 'server' }); + expect(result.split('// Copyright ACME').length - 1).toBe(1); + expect(result).toContain('@modelcontextprotocol/server'); + }); + + it('does not duplicate a CRLF leading header when rewriting the first import in place', () => { + // Same in-place rewrite, but the two // header lines are separated by CRLF. A `\n`-join never + // matches the file's `\r\n`, so the survival check would mis-fire and duplicate the header. + const input = `// Copyright ACME\r\n// Licensed MIT\r\n\r\nimport * as types from '@modelcontextprotocol/sdk/types.js';\r\n`; + const result = applyTransform(input, { projectType: 'server' }); + expect(result.split('// Copyright ACME').length - 1).toBe(1); + expect(result).toContain('@modelcontextprotocol/server'); + }); + + it('routes OAuth *Schema from sdk/shared/auth.js to core; the TYPE resolves by context', () => { + // OAuthTokensSchema is a Zod schema re-exported by core (AUTH_SCHEMA_NAMES), so route it + // there — `OAuthTokensSchema.parse(...)` keeps working. OAuthTokens (the type) has no schema-name + // match and resolves by context to @modelcontextprotocol/client. + const input = [ + `import { OAuthTokensSchema, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';`, + `const t = OAuthTokensSchema.parse(raw);`, + `let x: OAuthTokens;`, + '' + ].join('\n'); + const result = applyTransform(input, { projectType: 'client' }); + expect(result).toMatch(/import\s*\{[^}]*\bOAuthTokensSchema\b[^}]*\}\s*from\s*["']@modelcontextprotocol\/core["']/); + expect(result).toMatch(/import\s*\{[^}]*\bOAuthTokens\b[^}]*\}\s*from\s*["']@modelcontextprotocol\/client["']/); + expect(result).toContain('OAuthTokensSchema.parse(raw)'); + expect(result).not.toContain('@modelcontextprotocol/sdk/shared/auth'); + }); + + it('does not emit a project-type note when every symbol routes to core (both project)', () => { + // A types.js import of nothing but `*Schema` constants routes entirely to core, so the + // context package is never used — resolveTypesPackage must not be called, and no "both"-project + // info note should be emitted. + const project = new Project({ useInMemoryFileSystem: true }); + const sourceFile = project.createSourceFile( + 'test.ts', + `import { CallToolResultSchema, ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js';\n` + ); + const result = importPathsTransform.apply(sourceFile, { projectType: 'both' }); + expect(result.diagnostics.some(d => /both client and server|determine project type/i.test(d.message))).toBe(false); + expect(sourceFile.getFullText()).toContain('@modelcontextprotocol/core'); + expect(sourceFile.getFullText()).not.toContain('@modelcontextprotocol/sdk/types'); + }); + + it('does not warn about project type when an auth-schema-only import routes entirely to core (unknown project)', () => { + const project = new Project({ useInMemoryFileSystem: true }); + const sourceFile = project.createSourceFile( + 'test.ts', + `import { OAuthTokensSchema, OAuthMetadataSchema } from '@modelcontextprotocol/sdk/shared/auth.js';\n` + ); + const result = importPathsTransform.apply(sourceFile, { projectType: 'unknown' }); + expect(result.diagnostics.some(d => /determine project type/i.test(d.message))).toBe(false); + expect(sourceFile.getFullText()).toContain('@modelcontextprotocol/core'); + }); + + it('still warns about project type when a non-schema symbol falls through to context (unknown project)', () => { + // Control: `Tool` is a type with no schema-name match, so it falls through to context resolution — + // the warning must still fire (lazy resolution must not suppress genuine fall-throughs). + const project = new Project({ useInMemoryFileSystem: true }); + const sourceFile = project.createSourceFile( + 'test.ts', + `import { CallToolResultSchema, Tool } from '@modelcontextprotocol/sdk/types.js';\n` + ); + const result = importPathsTransform.apply(sourceFile, { projectType: 'unknown' }); + expect(result.diagnostics.some(d => /determine project type/i.test(d.message))).toBe(true); + }); + + it('splits a mixed default + named schema import — schema to core, default to context', () => { + // The named `CallToolResultSchema` must route to core even though a default import is present; + // the default binding (which can't be split) moves to the context package. Pre-fix the whole import + // moved to context and the schema silently became a "no exported member" error. + const result = applyTransform(`import sdk, { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';\n`, { + projectType: 'server' + }); + expect(result).toMatch(/import\s*\{[^}]*\bCallToolResultSchema\b[^}]*\}\s*from\s*["']@modelcontextprotocol\/core["']/); + expect(result).toMatch(/import\s+sdk\s+from\s*["']@modelcontextprotocol\/server["']/); + expect(result).not.toContain('@modelcontextprotocol/sdk/types'); + }); + + it('does not rewrite schema .parse() usages (migrates as an import-path swap)', () => { + const input = [ + `import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';`, + `const r = CallToolResultSchema.parse(value);`, + '' + ].join('\n'); + const result = applyTransform(input, { projectType: 'server' }); + expect(result).toContain('CallToolResultSchema.parse(value)'); + expect(result).toContain(`from "@modelcontextprotocol/core"`); + }); + + it('routes elicitation primitive *Schema TYPE names from sdk/types.js by context, not to core', () => { + // These names END in `Schema` but are TYPES; their Zod constant is `SchemaSchema`. They + // must resolve to the context package (where the types live), never to core (which only + // exports the `*SchemaSchema` constants) — otherwise the codemod emits a broken import. + const elicitationTypeNames = [ + 'BooleanSchema', + 'StringSchema', + 'NumberSchema', + 'EnumSchema', + 'SingleSelectEnumSchema', + 'MultiSelectEnumSchema', + 'TitledSingleSelectEnumSchema', + 'UntitledSingleSelectEnumSchema', + 'TitledMultiSelectEnumSchema', + 'UntitledMultiSelectEnumSchema', + 'LegacyTitledEnumSchema' + ]; + for (const typeName of elicitationTypeNames) { + const input = `import { ${typeName} } from '@modelcontextprotocol/sdk/types.js';\n`; + const result = applyTransform(input, { projectType: 'server' }); + expect(result, typeName).toContain(`from "@modelcontextprotocol/server"`); + expect(result, typeName).not.toContain('@modelcontextprotocol/core'); + expect(result, typeName).toContain(typeName); + } + }); + + it('splits a primitive-schema TYPE from its matching schema CONSTANT (BooleanSchema vs BooleanSchemaSchema)', () => { + // They differ only by a trailing `Schema`, which the suffix heuristic could not distinguish. + // The constant goes to core; the type resolves by context. + const input = `import { BooleanSchema, BooleanSchemaSchema } from '@modelcontextprotocol/sdk/types.js';\n`; + const result = applyTransform(input, { projectType: 'server' }); + expect(result).toContain(`from "@modelcontextprotocol/core"`); + expect(result).toContain('BooleanSchemaSchema'); expect(result).toContain(`from "@modelcontextprotocol/server"`); + expect(result).toMatch(/BooleanSchema\b/); + expect(result).not.toContain('@modelcontextprotocol/sdk/types'); + }); + + it('routes a renamed spec schema (JSONRPCErrorSchema) from sdk/types.js to core', () => { + // JSONRPCErrorSchema → JSONRPCErrorResponseSchema, a core export. Membership is checked + // against the rename-resolved name; the symbolRenames transform applies the rename afterward, + // so importPaths alone leaves the name unchanged but routes it to core. + const input = `import { JSONRPCErrorSchema } from '@modelcontextprotocol/sdk/types.js';\n`; + const result = applyTransform(input, { projectType: 'server' }); + expect(result).toContain(`from "@modelcontextprotocol/core"`); + expect(result).toContain('JSONRPCErrorSchema'); + expect(result).not.toContain('@modelcontextprotocol/sdk/types'); + }); + + it('routes JSONRPCResponseSchema (result-only in v1) from sdk/types.js to core', () => { + // v1's JSONRPCResponseSchema validated only result responses; v2 reuses the name for a union. + // The rename to JSONRPCResultResponseSchema (a core export) preserves v1 behavior; importPaths + // routes it to core against the rename-resolved name (symbolRenames applies the rename after). + const input = `import { JSONRPCResponseSchema } from '@modelcontextprotocol/sdk/types.js';\n`; + const result = applyTransform(input, { projectType: 'server' }); + expect(result).toContain(`from "@modelcontextprotocol/core"`); + expect(result).not.toContain('@modelcontextprotocol/sdk/types'); + expect(result).not.toContain(`from "@modelcontextprotocol/server"`); + }); + + it('flags a SafeUrlSchema import from sdk/shared/auth.js (no public v2 equivalent)', () => { + // SafeUrlSchema/OptionalSafeUrlSchema were internal URL field-validators in v1; v2's core + // deliberately does not re-export them, so there is no v2 home — emit guidance instead of silently + // routing to a package that has no such export. + const input = `import { SafeUrlSchema, OptionalSafeUrlSchema } from '@modelcontextprotocol/sdk/shared/auth.js';\n`; + const project = new Project({ useInMemoryFileSystem: true }); + const sourceFile = project.createSourceFile('test.ts', input); + const result = importPathsTransform.apply(sourceFile, { projectType: 'server' }); + const messages = result.diagnostics.map(d => d.message); + expect(messages.some(m => m.includes('SafeUrlSchema') && m.includes('no public v2 equivalent'))).toBe(true); + expect(messages.some(m => m.includes('OptionalSafeUrlSchema') && m.includes('no public v2 equivalent'))).toBe(true); + }); + + it('flags a star re-export of sdk/types.js that drops the moved schema constants', () => { + // `export * from '…/types.js'` cannot be routed per-symbol, so the Zod *Schema constants (now in + // core) silently disappear from the re-exporting barrel. Surface that for the user. + const input = `export * from '@modelcontextprotocol/sdk/types.js';\n`; + const project = new Project({ useInMemoryFileSystem: true }); + const sourceFile = project.createSourceFile('test.ts', input); + const result = importPathsTransform.apply(sourceFile, { projectType: 'server' }); + const messages = result.diagnostics.map(d => d.message).join('\n'); + expect(messages).toContain('@modelcontextprotocol/core'); + expect(messages).toMatch(/Star re-export/i); + }); + + it('flags a star re-export of sdk/shared/auth.js (schema constants move to core)', () => { + const input = `export * from '@modelcontextprotocol/sdk/shared/auth.js';\n`; + const project = new Project({ useInMemoryFileSystem: true }); + const sourceFile = project.createSourceFile('test.ts', input); + const result = importPathsTransform.apply(sourceFile, { projectType: 'server' }); + expect(result.diagnostics.map(d => d.message).join('\n')).toContain('@modelcontextprotocol/core'); + }); + + it('emits a split diagnostic for a re-export mixing a spec schema and a *Schema type (no silent breakage)', () => { + // The `*Schema` suffix would have routed BooleanSchema to core silently (no such export); + // membership routing instead surfaces the mismatch so the user splits the re-export manually. + const input = `export { CallToolResultSchema, BooleanSchema } from '@modelcontextprotocol/sdk/types.js';\n`; + const project = new Project({ useInMemoryFileSystem: true }); + const sourceFile = project.createSourceFile('test.ts', input); + const result = importPathsTransform.apply(sourceFile, { projectType: 'server' }); + expect(result.diagnostics.some(d => d.message.includes('mixes symbols') && d.message.includes('Split'))).toBe(true); + }); + + it('flags *Schema accesses through a namespace import of sdk/types.js (cannot be split)', () => { + const input = [ + `import * as types from '@modelcontextprotocol/sdk/types.js';`, + `const r = types.CallToolResultSchema.parse(value);`, + '' + ].join('\n'); + const project = new Project({ useInMemoryFileSystem: true }); + const sourceFile = project.createSourceFile('test.ts', input); + const result = importPathsTransform.apply(sourceFile, { projectType: 'server' }); + const messages = result.diagnostics.map(d => d.message).join('\n'); + // The namespace can't be split, so the schema can't be auto-routed — but the user must be told. + expect(messages).toContain('@modelcontextprotocol/core'); + expect(messages).toContain('CallToolResultSchema'); + // The namespace import itself still moves to the context package (its types live there). + // (setModuleSpecifier preserves the original quote style, so match quote-agnostically.) + expect(sourceFile.getFullText()).toContain('@modelcontextprotocol/server'); + }); + + it('suggests the v2 (rename-resolved) name in the namespace schema-access diagnostic', () => { + // JSONRPCErrorSchema is re-exported by core as JSONRPCErrorResponseSchema; the suggested + // import must use the v2 name (the v1 name has no exported member), and mention the rename. + const input = [ + `import * as types from '@modelcontextprotocol/sdk/types.js';`, + `const r = types.JSONRPCErrorSchema.safeParse(value);`, + '' + ].join('\n'); + const project = new Project({ useInMemoryFileSystem: true }); + const sourceFile = project.createSourceFile('test.ts', input); + const result = importPathsTransform.apply(sourceFile, { projectType: 'server' }); + const msg = result.diagnostics.map(d => d.message).join('\n'); + expect(msg).toContain("import { JSONRPCErrorResponseSchema } from '@modelcontextprotocol/core'"); + expect(msg).toContain('JSONRPCErrorSchema → JSONRPCErrorResponseSchema'); + expect(msg).not.toContain('import { JSONRPCErrorSchema } from'); + }); + + it('does not flag a namespace import of sdk/types.js that only accesses types', () => { + const input = [`import * as types from '@modelcontextprotocol/sdk/types.js';`, `const t: types.CallToolResult = value;`, ''].join( + '\n' + ); + const project = new Project({ useInMemoryFileSystem: true }); + const sourceFile = project.createSourceFile('test.ts', input); + const result = importPathsTransform.apply(sourceFile, { projectType: 'server' }); + expect(result.diagnostics.map(d => d.message).join('\n')).not.toContain('@modelcontextprotocol/core'); + }); + + it('resolves extensionless sdk/types (no .js suffix) the same as sdk/types.js', () => { + const input = `import { CallToolResult } from '@modelcontextprotocol/sdk/types';\n`; + const project = new Project({ useInMemoryFileSystem: true }); + const sourceFile = project.createSourceFile('test.ts', input); + const result = importPathsTransform.apply(sourceFile, { projectType: 'server' }); + const output = sourceFile.getFullText(); + expect(output).toContain(`from "@modelcontextprotocol/server"`); + expect(output).toContain('CallToolResult'); + expect(output).not.toContain('@modelcontextprotocol/sdk'); + expect(result.diagnostics.map(d => d.message).join('\n')).not.toContain('Unknown SDK import path'); }); it('preserves type-only imports separately', () => { @@ -339,6 +678,17 @@ describe('import-paths transform', () => { expect(result.diagnostics.some(d => d.message.includes('SSEServerTransport is deprecated'))).toBe(true); }); + it('resolves extensionless sdk/types re-export (no .js suffix)', () => { + const input = `export { CallToolResult } from '@modelcontextprotocol/sdk/types';\n`; + const project = new Project({ useInMemoryFileSystem: true }); + const sourceFile = project.createSourceFile('test.ts', input); + const result = importPathsTransform.apply(sourceFile, { projectType: 'server' }); + const output = sourceFile.getFullText(); + expect(output).toContain('@modelcontextprotocol/server'); + expect(output).not.toContain('@modelcontextprotocol/sdk'); + expect(result.diagnostics.map(d => d.message).join('\n')).not.toContain('Unknown SDK export path'); + }); + it('includes server-legacy in usedPackages for SSE import', () => { const project = new Project({ useInMemoryFileSystem: true }); const sourceFile = project.createSourceFile( @@ -405,7 +755,7 @@ describe('import-paths transform', () => { expect(result.diagnostics[0]!.message).toContain('RequestHandlerExtra'); }); - it('emits warning for aliased import mixing symbols from different v2 packages', () => { + it('splits an aliased import mixing symbols from different v2 packages (no longer bails)', () => { const input = [ `import { StreamableHTTPServerTransport as T, EventStore } from '@modelcontextprotocol/sdk/server/streamableHttp.js';`, '' @@ -413,8 +763,13 @@ describe('import-paths transform', () => { const project = new Project({ useInMemoryFileSystem: true }); const sourceFile = project.createSourceFile('test.ts', input); const result = importPathsTransform.apply(sourceFile, { projectType: 'server' }); - expect(result.diagnostics.length).toBeGreaterThan(0); - expect(result.diagnostics.some(d => d.message.includes('mixes symbols') && d.message.includes('Split'))).toBe(true); + const output = sourceFile.getFullText(); + // transport (aliased + renamed) → /node; companion type → /server + expect(output).toContain('NodeStreamableHTTPServerTransport as T'); + expect(output).toContain('@modelcontextprotocol/node'); + expect(output).toMatch(/import\s*\{[^}]*\bEventStore\b[^}]*\}\s*from\s*["']@modelcontextprotocol\/server["']/); + expect(output).not.toContain('@modelcontextprotocol/sdk'); + expect(result.diagnostics.some(d => d.message.includes('mixes symbols'))).toBe(false); }); it('emits warning for re-export mixing symbols from different v2 packages', () => { diff --git a/packages/codemod/test/v1-to-v2/transforms/mockPaths.test.ts b/packages/codemod/test/v1-to-v2/transforms/mockPaths.test.ts index 3e1958b714..ed613ddc2f 100644 --- a/packages/codemod/test/v1-to-v2/transforms/mockPaths.test.ts +++ b/packages/codemod/test/v1-to-v2/transforms/mockPaths.test.ts @@ -63,6 +63,19 @@ describe('mock-paths transform', () => { expect(result).toContain(`'@modelcontextprotocol/server'`); expect(result).not.toContain('@modelcontextprotocol/sdk'); }); + + it('rewrites extensionless sdk/types path (no .js suffix)', () => { + const input = [ + `vi.doMock('@modelcontextprotocol/sdk/types', async importOriginal => {`, + ` const original = await importOriginal();`, + ` return { ...original, isInitializeRequest: mockFn };`, + `});`, + '' + ].join('\n'); + const result = applyTransform(input); + expect(result).toContain(`'@modelcontextprotocol/server'`); + expect(result).not.toContain('@modelcontextprotocol/sdk'); + }); }); describe('vi.mock', () => { @@ -325,6 +338,190 @@ describe('mock-paths transform', () => { }); }); + describe('schema constant routing (schemaSymbolTarget)', () => { + it('routes a vi.mock factory of only spec *Schema constants to core', () => { + const input = [`vi.mock('@modelcontextprotocol/sdk/types.js', () => ({`, ` CallToolResultSchema: vi.fn()`, `}));`, ''].join( + '\n' + ); + const result = applyTransform(input); + expect(result).toContain(`'@modelcontextprotocol/core'`); + expect(result).not.toContain('@modelcontextprotocol/sdk/types'); + // The schema constant lives in core, never the context (server) package. + expect(result).not.toContain(`'@modelcontextprotocol/server'`); + }); + + it('routes a vi.mock factory of only auth *Schema constants to core', () => { + const input = [ + `vi.mock('@modelcontextprotocol/sdk/shared/auth.js', () => ({`, + ` OAuthTokensSchema: vi.fn()`, + `}));`, + '' + ].join('\n'); + const result = applyTransform(input); + expect(result).toContain(`'@modelcontextprotocol/core'`); + expect(result).not.toContain('@modelcontextprotocol/sdk/shared/auth'); + }); + + it('routes a destructured dynamic import of only *Schema constants to core', () => { + const input = [`const { CallToolResultSchema } = await import('@modelcontextprotocol/sdk/types.js');`, ''].join('\n'); + const result = applyTransform(input); + expect(result).toContain(`import('@modelcontextprotocol/core')`); + expect(result).not.toContain('@modelcontextprotocol/sdk/types'); + }); + + it('renames JSONRPCResponseSchema and routes it to core in a mock factory', () => { + const input = [`vi.mock('@modelcontextprotocol/sdk/types.js', () => ({`, ` JSONRPCResponseSchema: vi.fn()`, `}));`, ''].join( + '\n' + ); + const result = applyTransform(input); + expect(result).toContain(`'@modelcontextprotocol/core'`); + expect(result).toContain('JSONRPCResultResponseSchema'); + expect(result).not.toMatch(/(? { + const input = [ + `vi.mock('@modelcontextprotocol/sdk/types.js', () => ({`, + ` CallToolResultSchema: vi.fn(),`, + ` McpError: vi.fn()`, + `}));`, + '' + ].join('\n'); + const project = new Project({ useInMemoryFileSystem: true }); + const sourceFile = project.createSourceFile('test.ts', input); + const result = mockPathsTransform.apply(sourceFile, ctx); + expect(result.diagnostics.some(d => d.message.includes('mixes symbols that belong to different v2 packages'))).toBe(true); + }); + + it('flags a destructured dynamic import mixing a *Schema constant and a type', () => { + const input = [`const { CallToolResultSchema, McpError } = await import('@modelcontextprotocol/sdk/types.js');`, ''].join('\n'); + const project = new Project({ useInMemoryFileSystem: true }); + const sourceFile = project.createSourceFile('test.ts', input); + const result = mockPathsTransform.apply(sourceFile, ctx); + expect(result.diagnostics.some(d => d.message.includes('belong to different v2 packages'))).toBe(true); + }); + + it('routes a destructured .then() param of only *Schema constants to core', () => { + const input = [ + `import('@modelcontextprotocol/sdk/types.js').then(({ CallToolResultSchema }) => CallToolResultSchema.parse(value));`, + '' + ].join('\n'); + const result = applyTransform(input); + expect(result).toContain(`import('@modelcontextprotocol/core')`); + expect(result).not.toContain('@modelcontextprotocol/sdk/types'); + }); + + it('renames a *Schema in a destructured .then() param and routes it to core', () => { + const input = [ + `import('@modelcontextprotocol/sdk/types.js').then(({ JSONRPCResponseSchema }) => JSONRPCResponseSchema.parse(value));`, + '' + ].join('\n'); + const result = applyTransform(input); + expect(result).toContain(`import('@modelcontextprotocol/core')`); + expect(result).toContain('JSONRPCResultResponseSchema'); + }); + + it('flags a destructured .then() param mixing a *Schema constant and a type', () => { + const input = [ + `import('@modelcontextprotocol/sdk/types.js').then(({ CallToolResultSchema, McpError }) => CallToolResultSchema.parse(value));`, + '' + ].join('\n'); + const project = new Project({ useInMemoryFileSystem: true }); + const sourceFile = project.createSourceFile('test.ts', input); + const result = mockPathsTransform.apply(sourceFile, ctx); + expect(result.diagnostics.some(d => d.message.includes('belong to different v2 packages'))).toBe(true); + }); + }); + + describe('lazy context resolution (no spurious project-type diagnostic)', () => { + it('does not warn about project type for a schema-only vi.mock factory (unknown project)', () => { + // The factory routes entirely to core, so the context package is never used — resolveTypesPackage + // must not emit a "could not determine project type" warning. + const input = [`vi.mock('@modelcontextprotocol/sdk/types.js', () => ({`, ` CallToolResultSchema: vi.fn()`, `}));`, ''].join( + '\n' + ); + const project = new Project({ useInMemoryFileSystem: true }); + const sourceFile = project.createSourceFile('test.ts', input); + const result = mockPathsTransform.apply(sourceFile, { projectType: 'unknown' }); + expect(result.diagnostics.some(d => /determine project type/i.test(d.message))).toBe(false); + expect(sourceFile.getFullText()).toContain('@modelcontextprotocol/core'); + }); + + it('does not emit a both-project note for a schema-only destructured dynamic import (both project)', () => { + const input = [`const { OAuthTokensSchema } = await import('@modelcontextprotocol/sdk/shared/auth.js');`, ''].join('\n'); + const project = new Project({ useInMemoryFileSystem: true }); + const sourceFile = project.createSourceFile('test.ts', input); + const result = mockPathsTransform.apply(sourceFile, { projectType: 'both' }); + expect(result.diagnostics.some(d => /both client and server|determine project type/i.test(d.message))).toBe(false); + expect(sourceFile.getFullText()).toContain('@modelcontextprotocol/core'); + }); + + it('still warns about project type for a non-schema vi.mock factory (unknown project)', () => { + // Control: isInitializeRequest is a guard (not a schema constant), so the factory falls through to + // context resolution — the warning must still fire (lazy resolution must not suppress real fall-throughs). + const input = [`vi.mock('@modelcontextprotocol/sdk/types.js', () => ({`, ` isInitializeRequest: vi.fn()`, `}));`, ''].join( + '\n' + ); + const project = new Project({ useInMemoryFileSystem: true }); + const sourceFile = project.createSourceFile('test.ts', input); + const result = mockPathsTransform.apply(sourceFile, { projectType: 'unknown' }); + expect(result.diagnostics.some(d => /determine project type/i.test(d.message))).toBe(true); + }); + }); + + describe('non-destructured / .then dynamic import schema access (schemaSymbolTarget)', () => { + it('flags schema access on a non-destructured awaited dynamic import (types.js)', () => { + const input = [ + `const mod = await import('@modelcontextprotocol/sdk/types.js');`, + `const r = mod.CallToolResultSchema.parse(value);`, + '' + ].join('\n'); + const project = new Project({ useInMemoryFileSystem: true }); + const sourceFile = project.createSourceFile('test.ts', input); + const result = mockPathsTransform.apply(sourceFile, ctx); + expect( + result.diagnostics.some(d => d.message.includes('@modelcontextprotocol/core') && d.message.includes('CallToolResultSchema')) + ).toBe(true); + }); + + it('flags schema access in a .then() chain (shared/auth.js)', () => { + const input = [`import('@modelcontextprotocol/sdk/shared/auth.js').then(m => m.OAuthTokensSchema.parse(value));`, ''].join( + '\n' + ); + const project = new Project({ useInMemoryFileSystem: true }); + const sourceFile = project.createSourceFile('test.ts', input); + const result = mockPathsTransform.apply(sourceFile, ctx); + expect( + result.diagnostics.some(d => d.message.includes('@modelcontextprotocol/core') && d.message.includes('OAuthTokensSchema')) + ).toBe(true); + }); + + it('notes the rename for a renamed schema accessed in a .then() chain', () => { + const input = [`import('@modelcontextprotocol/sdk/types.js').then(m => m.JSONRPCResponseSchema.parse(value));`, ''].join('\n'); + const project = new Project({ useInMemoryFileSystem: true }); + const sourceFile = project.createSourceFile('test.ts', input); + const result = mockPathsTransform.apply(sourceFile, ctx); + expect( + result.diagnostics.some( + d => d.message.includes('JSONRPCResponseSchema') && d.message.includes('JSONRPCResultResponseSchema') + ) + ).toBe(true); + }); + + it('does not flag a non-destructured dynamic import with no schema access', () => { + // Control: `mod` is only used for a guard (not a schema constant), so no schema-moved-to-core note. + const input = [ + `const mod = await import('@modelcontextprotocol/sdk/types.js');`, + `const ok = mod.isInitializeRequest(value);`, + '' + ].join('\n'); + const project = new Project({ useInMemoryFileSystem: true }); + const sourceFile = project.createSourceFile('test.ts', input); + const result = mockPathsTransform.apply(sourceFile, ctx); + expect(result.diagnostics.some(d => d.message.includes('moved to @modelcontextprotocol/core'))).toBe(false); + }); + }); + describe('validator subpath rewrites', () => { it('rewrites vi.mock of validator provider to the subpath', () => { const input = [ diff --git a/packages/codemod/test/v1-to-v2/transforms/schemaParamRemoval.test.ts b/packages/codemod/test/v1-to-v2/transforms/schemaParamRemoval.test.ts index 70e391ce86..55c28ee36e 100644 --- a/packages/codemod/test/v1-to-v2/transforms/schemaParamRemoval.test.ts +++ b/packages/codemod/test/v1-to-v2/transforms/schemaParamRemoval.test.ts @@ -117,4 +117,40 @@ describe('schema-param-removal transform', () => { const result = applyTransform(input); expect(result).not.toMatch(/import.*CallToolResultSchema/); }); + + it('removes a literal undefined schema slot from callTool when an options argument follows', () => { + const input = [ + `import { Client } from '@modelcontextprotocol/sdk/client/index.js';`, + `const result = await client.callTool({ name: 'add', arguments: { a: 1 } }, undefined, { onprogress: cb });`, + '' + ].join('\n'); + const result = applyTransform(input); + expect(result).toContain("client.callTool({ name: 'add', arguments: { a: 1 } }, { onprogress: cb })"); + expect(result).not.toContain(', undefined,'); + }); + + it('removes a literal undefined schema slot from request when an options argument follows', () => { + const input = [ + `import { Client } from '@modelcontextprotocol/sdk/client/index.js';`, + `const result = await client.request({ method: 'tools/call', params: {} }, undefined, { timeout: 5000 });`, + '' + ].join('\n'); + const result = applyTransform(input); + expect(result).toContain("client.request({ method: 'tools/call', params: {} }, { timeout: 5000 })"); + expect(result).not.toContain(', undefined,'); + }); + + it('does not strip undefined from request()/callTool() in a file with no MCP imports', () => { + // `request`/`callTool` are common non-MCP method names; without an MCP signal in the file the + // codemod must not touch them, or it would shift `someHttpClient.request(payload, undefined, opts)`. + const input = [`const r = await someHttpClient.request(payload, undefined, { timeout: 5000 });`, ''].join('\n'); + const result = applyTransform(input); + expect(result).toContain('someHttpClient.request(payload, undefined, { timeout: 5000 })'); + }); + + it('leaves a 2-arg callTool(params, undefined) unchanged (already valid as options in v2)', () => { + const input = [`await client.callTool({ name: 'add' }, undefined);`, ''].join('\n'); + const result = applyTransform(input); + expect(result).toContain('undefined'); + }); }); diff --git a/packages/codemod/test/v1-to-v2/transforms/specSchemaAccess.test.ts b/packages/codemod/test/v1-to-v2/transforms/specSchemaAccess.test.ts deleted file mode 100644 index 92a8384ff8..0000000000 --- a/packages/codemod/test/v1-to-v2/transforms/specSchemaAccess.test.ts +++ /dev/null @@ -1,517 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { Project } from 'ts-morph'; - -import { specSchemaAccessTransform } from '../../../src/migrations/v1-to-v2/transforms/specSchemaAccess'; -import type { TransformContext } from '../../../src/types'; - -const ctx: TransformContext = { projectType: 'server' }; - -function applyTransform(code: string) { - const project = new Project({ useInMemoryFileSystem: true }); - const sourceFile = project.createSourceFile('test.ts', code); - const result = specSchemaAccessTransform.apply(sourceFile, ctx); - return { text: sourceFile.getFullText(), result }; -} - -describe('spec-schema-access transform', () => { - describe('auto-transform: .safeParse(v).success → isSpecType.X(v)', () => { - it('rewrites XSchema.safeParse(v).success to isSpecType.X(v)', () => { - const input = [ - `import { CallToolRequestSchema } from '@modelcontextprotocol/server';`, - `const valid = CallToolRequestSchema.safeParse(data).success;`, - '' - ].join('\n'); - const { text, result } = applyTransform(input); - expect(text).toContain('isSpecType.CallToolRequest(data)'); - expect(text).not.toContain('safeParse'); - expect(result.changesCount).toBeGreaterThan(0); - }); - - it('handles safeParse().success in if-condition', () => { - const input = [ - `import { ToolSchema } from '@modelcontextprotocol/server';`, - `if (ToolSchema.safeParse(obj).success) { doSomething(); }`, - '' - ].join('\n'); - const { text } = applyTransform(input); - expect(text).toContain('isSpecType.Tool(obj)'); - expect(text).not.toContain('safeParse'); - }); - - it('adds isSpecType import when transforming safeParse().success', () => { - const input = [ - `import { CallToolResultSchema } from '@modelcontextprotocol/server';`, - `const ok = CallToolResultSchema.safeParse(x).success;`, - '' - ].join('\n'); - const { text } = applyTransform(input); - expect(text).toContain('isSpecType'); - expect(text).toMatch(/import.*isSpecType.*from/); - }); - }); - - describe('auto-transform: value position → specTypeSchemas.X', () => { - it('replaces schema passed as function arg with specTypeSchemas.X', () => { - const input = [ - `import { ListToolsRequestSchema } from '@modelcontextprotocol/server';`, - `validate(ListToolsRequestSchema);`, - '' - ].join('\n'); - const { text, result } = applyTransform(input); - expect(text).toContain('specTypeSchemas.ListToolsRequest'); - expect(result.changesCount).toBeGreaterThan(0); - expect(result.diagnostics.length).toBeGreaterThan(0); - expect(result.diagnostics[0]!.message).toContain('StandardSchemaV1'); - }); - - it('adds specTypeSchemas import', () => { - const input = [`import { ToolSchema } from '@modelcontextprotocol/server';`, `const s = ToolSchema;`, ''].join('\n'); - const { text } = applyTransform(input); - expect(text).toContain('specTypeSchemas.Tool'); - expect(text).toMatch(/import.*specTypeSchemas.*from/); - }); - }); - - describe('auto-transform: captured safeParse result', () => { - it('rewrites captured safeParse call and result property accesses', () => { - const input = [ - `import { CallToolResultSchema } from '@modelcontextprotocol/server';`, - `const parsed = CallToolResultSchema.safeParse(data);`, - `if (parsed.success) { return parsed.data; }`, - '' - ].join('\n'); - const { text, result } = applyTransform(input); - expect(text).toContain("specTypeSchemas.CallToolResult['~standard'].validate(data)"); - expect(text).toContain('parsed.issues === undefined'); - expect(text).toContain('parsed.value'); - expect(text).not.toContain('safeParse'); - expect(text).not.toContain('parsed.success'); - expect(text).not.toContain('parsed.data'); - expect(result.changesCount).toBeGreaterThan(0); - }); - - it('rewrites result properties assigned to variables (const isValid = parsed.success)', () => { - const input = [ - `import { CallToolResultSchema } from '@modelcontextprotocol/server';`, - `const parsed = CallToolResultSchema.safeParse(data);`, - `const isValid = parsed.success;`, - `const result = parsed.data;`, - '' - ].join('\n'); - const { text } = applyTransform(input); - expect(text).toContain('parsed.issues === undefined'); - expect(text).toContain('parsed.value'); - expect(text).not.toContain('parsed.success'); - expect(text).not.toContain('parsed.data'); - }); - - it('rewrites .error to .issues', () => { - const input = [ - `import { ToolSchema } from '@modelcontextprotocol/server';`, - `const result = ToolSchema.safeParse(raw);`, - `if (!result.success) { console.log(result.error); }`, - '' - ].join('\n'); - const { text } = applyTransform(input); - expect(text).toContain('result.issues'); - expect(text).not.toContain('result.error'); - }); - - it('handles ternary pattern: x.success ? x.data : fallback', () => { - const input = [ - `import { CallToolResultSchema } from '@modelcontextprotocol/server';`, - `const parsed = CallToolResultSchema.safeParse(toolResult);`, - `return parsed.success ? parsed.data : undefined;`, - '' - ].join('\n'); - const { text } = applyTransform(input); - expect(text).toContain("specTypeSchemas.CallToolResult['~standard'].validate(toolResult)"); - expect(text).toContain('(parsed.issues === undefined) ? parsed.value : undefined'); - }); - - it('adds specTypeSchemas import', () => { - const input = [ - `import { ToolSchema } from '@modelcontextprotocol/server';`, - `const r = ToolSchema.safeParse(v);`, - `r.success;`, - '' - ].join('\n'); - const { text } = applyTransform(input); - expect(text).toMatch(/import.*specTypeSchemas.*from/); - }); - - it('rewrites .error.issues to .issues (unwrap double nesting)', () => { - const input = [ - `import { CallToolResultSchema } from '@modelcontextprotocol/server';`, - `const parsed = CallToolResultSchema.safeParse(data);`, - `if (!parsed.success) { console.log(parsed.error.issues); }`, - '' - ].join('\n'); - const { text } = applyTransform(input); - expect(text).toContain('parsed.issues'); - expect(text).not.toContain('parsed.issues.issues'); - expect(text).not.toContain('parsed.error'); - }); - - it('rewrites .error.message to issues map expression', () => { - const input = [ - `import { CallToolResultSchema } from '@modelcontextprotocol/server';`, - `const parsed = CallToolResultSchema.safeParse(data);`, - `if (!parsed.success) { console.log(parsed.error.message); }`, - '' - ].join('\n'); - const { text } = applyTransform(input); - expect(text).not.toContain('parsed.error'); - expect(text).not.toContain('parsed.issues.message'); - expect(text).toContain("parsed.issues?.map(i => i.message).join(', ')"); - }); - - it('emits diagnostic for .error.format() instead of silently rewriting', () => { - const input = [ - `import { CallToolResultSchema } from '@modelcontextprotocol/server';`, - `const parsed = CallToolResultSchema.safeParse(data);`, - `if (!parsed.success) { console.log(parsed.error.format()); }`, - '' - ].join('\n'); - const { text, result } = applyTransform(input); - expect(text).toContain('parsed.error.format()'); - expect(text).not.toContain('parsed.issues()'); - expect(result.diagnostics.some(d => d.message.includes('no StandardSchema equivalent'))).toBe(true); - }); - - it('rewrites bare .error to .issues (unchanged behavior)', () => { - const input = [ - `import { ToolSchema } from '@modelcontextprotocol/server';`, - `const result = ToolSchema.safeParse(raw);`, - `if (!result.success) { console.log(result.error); }`, - '' - ].join('\n'); - const { text } = applyTransform(input); - expect(text).toContain('result.issues'); - expect(text).not.toContain('result.error'); - }); - - it('does not rewrite same-named variable in sibling function', () => { - const input = [ - `import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';`, - `function validate(d: unknown) {`, - ` const result = CallToolRequestSchema.safeParse(d);`, - ` return result.success;`, - `}`, - `async function callApi(client: any) {`, - ` const result = await client.get('/api');`, - ` return result.data;`, - `}`, - '' - ].join('\n'); - const { text } = applyTransform(input); - expect(text).toContain('result.issues === undefined'); - expect(text).toContain('return result.data'); - expect(text).not.toContain('return result.value'); - }); - - it('rewrites non-captured safeParse (bare expression) to validate()', () => { - const input = [`import { ToolSchema } from '@modelcontextprotocol/server';`, `ToolSchema.safeParse(data);`, ''].join('\n'); - const { text, result } = applyTransform(input); - expect(text).toContain("specTypeSchemas.Tool['~standard'].validate(data)"); - expect(text).not.toMatch(/import\s*\{[^}]*ToolSchema[^}]*\}/); - expect(result.changesCount).toBeGreaterThan(0); - expect(result.diagnostics.length).toBe(1); - }); - }); - - describe('guardrails: non-MCP schemas are NOT touched', () => { - it('does not rewrite safeParse on user-defined schema with same name from local import', () => { - const input = [ - `import { CallToolResultSchema } from './mySchemas';`, - `const parsed = CallToolResultSchema.safeParse(data);`, - `if (parsed.success) { return parsed.data; }`, - '' - ].join('\n'); - const { text, result } = applyTransform(input); - expect(text).toContain('CallToolResultSchema.safeParse'); - expect(text).toContain('parsed.success'); - expect(text).toContain('parsed.data'); - expect(result.changesCount).toBe(0); - expect(result.diagnostics.length).toBe(0); - }); - - it('does not rewrite safeParse on user zod schema not from MCP', () => { - const input = [ - `import { z } from 'zod';`, - `const MySchema = z.object({ name: z.string() });`, - `const parsed = MySchema.safeParse(data);`, - `if (parsed.success) { return parsed.data; }`, - '' - ].join('\n'); - const { text, result } = applyTransform(input); - expect(text).toContain('MySchema.safeParse'); - expect(text).toContain('parsed.success'); - expect(text).toContain('parsed.data'); - expect(result.changesCount).toBe(0); - expect(result.diagnostics.length).toBe(0); - }); - - it('does not rewrite safeParse on non-spec schema name from MCP import', () => { - const input = [ - `import { SomeRandomSchema } from '@modelcontextprotocol/server';`, - `const parsed = SomeRandomSchema.safeParse(data);`, - `if (parsed.success) { return parsed.data; }`, - '' - ].join('\n'); - const { text, result } = applyTransform(input); - expect(text).toContain('SomeRandomSchema.safeParse'); - expect(text).toContain('parsed.success'); - expect(result.changesCount).toBe(0); - expect(result.diagnostics.length).toBe(0); - }); - - it('does not rewrite safeParse on npm package schema with matching name', () => { - const input = [ - `import { CallToolResultSchema } from 'some-other-package';`, - `const parsed = CallToolResultSchema.safeParse(data);`, - `if (parsed.success) { return parsed.data; }`, - '' - ].join('\n'); - const { text, result } = applyTransform(input); - expect(text).toContain('CallToolResultSchema.safeParse'); - expect(text).toContain('parsed.success'); - expect(result.changesCount).toBe(0); - expect(result.diagnostics.length).toBe(0); - }); - }); - - describe('auto-transform: generic property access → specTypeSchemas.X', () => { - it('replaces schema identifier in .parseAsync() call', () => { - const input = [ - `import { OAuthTokensSchema } from '@modelcontextprotocol/server';`, - `const tokens = await OAuthTokensSchema.parseAsync(data);`, - '' - ].join('\n'); - const { text, result } = applyTransform(input); - expect(text).toContain('specTypeSchemas.OAuthTokens.parseAsync(data)'); - expect(text).not.toMatch(/import\s*\{[^}]*OAuthTokensSchema[^}]*\}/); - expect(result.changesCount).toBeGreaterThan(0); - expect(result.diagnostics.length).toBeGreaterThan(0); - }); - - it('replaces schema identifier in .or() call', () => { - const input = [ - `import { ServerNotificationSchema } from '@modelcontextprotocol/server';`, - `const union = ServerNotificationSchema.or(otherSchema);`, - '' - ].join('\n'); - const { text, result } = applyTransform(input); - expect(text).toContain('specTypeSchemas.ServerNotification.or(otherSchema)'); - expect(text).not.toMatch(/import\s*\{[^}]*ServerNotificationSchema[^}]*\}/); - expect(result.changesCount).toBeGreaterThan(0); - }); - - it('replaces schema identifier in .extend() call', () => { - const input = [ - `import { ToolSchema } from '@modelcontextprotocol/server';`, - `const extended = ToolSchema.extend({ extra: z.string() });`, - '' - ].join('\n'); - const { text, result } = applyTransform(input); - expect(text).toContain('specTypeSchemas.Tool.extend'); - expect(result.changesCount).toBeGreaterThan(0); - }); - - it('adds specTypeSchemas import for generic property access', () => { - const input = [ - `import { OAuthTokensSchema } from '@modelcontextprotocol/server';`, - `const tokens = await OAuthTokensSchema.parseAsync(data);`, - '' - ].join('\n'); - const { text } = applyTransform(input); - expect(text).toMatch(/import.*specTypeSchemas.*from/); - }); - }); - - describe('.parse(v)', () => { - it('rewrites discarded parse() to the validate() primitive', () => { - const input = [`import { ToolSchema } from '@modelcontextprotocol/server';`, `ToolSchema.parse(raw);`, ''].join('\n'); - const { text, result } = applyTransform(input); - expect(text).toContain("specTypeSchemas.Tool['~standard'].validate(raw)"); - expect(text).not.toMatch(/import\s*\{[^}]*ToolSchema[^}]*\}/); - expect(result.changesCount).toBeGreaterThan(0); - }); - - it('swaps the identifier (import stays resolvable) when the parse() result is used', () => { - const input = [`import { ToolSchema } from '@modelcontextprotocol/server';`, `const tool = ToolSchema.parse(raw);`, ''].join( - '\n' - ); - const { text, result } = applyTransform(input); - expect(text).toContain('specTypeSchemas.Tool.parse(raw)'); - expect(text).not.toMatch(/import\s*\{[^}]*ToolSchema[^}]*\}/); - expect(result.changesCount).toBeGreaterThan(0); - expect(result.diagnostics[0]!.message).toContain('specTypeSchemas.Tool'); - }); - }); - - describe('diagnostic: z.infer', () => { - it('emits diagnostic for typeof in type position', () => { - const input = [ - `import { CallToolResultSchema } from '@modelcontextprotocol/client';`, - `type Result = typeof CallToolResultSchema;`, - '' - ].join('\n'); - const { result } = applyTransform(input); - expect(result.diagnostics.length).toBe(1); - expect(result.diagnostics[0]!.message).toContain('CallToolResult'); - }); - }); - - describe('no-op cases', () => { - it('does nothing for non-MCP imports', () => { - const input = [`import { CallToolRequestSchema } from './local';`, `CallToolRequestSchema.safeParse(data);`, ''].join('\n'); - const { text, result } = applyTransform(input); - expect(text).toContain('CallToolRequestSchema.safeParse'); - expect(result.changesCount).toBe(0); - expect(result.diagnostics.length).toBe(0); - }); - - it('does nothing for non-spec schema names', () => { - const input = [`import { SomeRandomSchema } from '@modelcontextprotocol/server';`, `SomeRandomSchema.parse(data);`, ''].join( - '\n' - ); - const { text, result } = applyTransform(input); - expect(text).toContain('SomeRandomSchema.parse'); - expect(result.changesCount).toBe(0); - expect(result.diagnostics.length).toBe(0); - }); - - it('does nothing when no remaining references', () => { - const input = [`import { CallToolRequestSchema } from '@modelcontextprotocol/server';`, ''].join('\n'); - const { result } = applyTransform(input); - expect(result.changesCount).toBe(0); - expect(result.diagnostics.length).toBe(0); - }); - }); - - describe('import cleanup after transform', () => { - it('removes original schema import after all refs are auto-transformed', () => { - const input = [ - `import { CallToolRequestSchema } from '@modelcontextprotocol/server';`, - `const valid = CallToolRequestSchema.safeParse(data).success;`, - '' - ].join('\n'); - const { text } = applyTransform(input); - expect(text).toContain('isSpecType.CallToolRequest(data)'); - expect(text).not.toMatch(/import\s*\{[^}]*CallToolRequestSchema[^}]*\}/); - }); - - it('removes the schema import even when a ref falls back to a parse()/safeParse() rewrite', () => { - const input = [ - `import { CallToolRequestSchema } from '@modelcontextprotocol/server';`, - `const valid = CallToolRequestSchema.safeParse(data).success;`, - `const parsed = CallToolRequestSchema.parse(data);`, - '' - ].join('\n'); - const { text } = applyTransform(input); - expect(text).toContain('isSpecType.CallToolRequest(data)'); - expect(text).toContain('specTypeSchemas.CallToolRequest.parse(data)'); - expect(text).not.toMatch(/import\s*\{[^}]*CallToolRequestSchema[^}]*\}/); - }); - - it('removes schema specifier from import that also has other symbols', () => { - const input = [ - `import { CallToolRequestSchema, McpError } from '@modelcontextprotocol/server';`, - `const valid = CallToolRequestSchema.safeParse(data).success;`, - `throw new McpError(1, 'fail');`, - '' - ].join('\n'); - const { text } = applyTransform(input); - expect(text).not.toMatch(/import\s*\{[^}]*CallToolRequestSchema[^}]*\}/); - expect(text).toContain('McpError'); - expect(text).toContain(`@modelcontextprotocol/server`); - }); - }); - - describe('parent-kind guards', () => { - it('emits diagnostic for re-exported schema (ExportSpecifier)', () => { - const input = [ - `import { CallToolRequestSchema } from '@modelcontextprotocol/server';`, - `export { CallToolRequestSchema };`, - '' - ].join('\n'); - const { text, result } = applyTransform(input); - expect(text).toContain('export { CallToolRequestSchema }'); - expect(result.diagnostics.some(d => d.message.includes('Re-export'))).toBe(true); - expect(result.changesCount).toBe(0); - }); - - it('expands shorthand property assignment and removes import', () => { - const input = [`import { ToolSchema } from '@modelcontextprotocol/server';`, `const schemas = { ToolSchema };`, ''].join('\n'); - const { text, result } = applyTransform(input); - expect(text).toContain("'ToolSchema': specTypeSchemas.Tool"); - expect(text).not.toMatch(/import\s*\{[^}]*ToolSchema[^}]*\}/); - expect(result.changesCount).toBeGreaterThan(0); - }); - - it('skips PropertyAssignment name-node (non-shorthand)', () => { - const input = [ - `import { ToolSchema } from '@modelcontextprotocol/server';`, - `const schemas = { ToolSchema: myValidator };`, - '' - ].join('\n'); - const { text, result } = applyTransform(input); - expect(text).toContain('ToolSchema: myValidator'); - expect(result.changesCount).toBe(0); - }); - - it('skips BindingElement property-name', () => { - const input = [`import { ToolSchema } from '@modelcontextprotocol/server';`, `const { ToolSchema: local } = obj;`, ''].join( - '\n' - ); - const { text, result } = applyTransform(input); - expect(text).toContain('ToolSchema: local'); - expect(result.changesCount).toBe(0); - }); - - it('skips PropertyAccessExpression name-node (obj.ToolSchema)', () => { - const input = [`import { ToolSchema } from '@modelcontextprotocol/server';`, `const x = registry.ToolSchema;`, ''].join('\n'); - const { text, result } = applyTransform(input); - expect(text).toContain('registry.ToolSchema'); - expect(text).not.toContain('specTypeSchemas'); - expect(result.changesCount).toBe(0); - }); - - it('does not emit z.infer diagnostic for runtime typeof (TypeOfExpression)', () => { - const input = [`import { ToolSchema } from '@modelcontextprotocol/server';`, `const kind = typeof ToolSchema;`, ''].join('\n'); - const { result } = applyTransform(input); - expect(result.diagnostics.every(d => !d.message.includes('z.infer'))).toBe(true); - }); - }); - - describe('namespace imports', () => { - it('does not crash when file has namespace import from same package', () => { - const input = [ - `import * as types from '@modelcontextprotocol/server';`, - `import { ToolSchema } from '@modelcontextprotocol/server';`, - `const s = ToolSchema;`, - '' - ].join('\n'); - const { text, result } = applyTransform(input); - expect(text).toContain('specTypeSchemas.Tool'); - expect(result.changesCount).toBeGreaterThan(0); - }); - }); - - describe('aliased imports', () => { - it('handles aliased import and auto-transforms captured safeParse', () => { - const input = [ - `import { CallToolRequestSchema as CTRS } from '@modelcontextprotocol/server';`, - `const result = CTRS.safeParse(data);`, - `result.success;`, - '' - ].join('\n'); - const { text, result } = applyTransform(input); - expect(text).toContain("specTypeSchemas.CallToolRequest['~standard'].validate(data)"); - expect(text).not.toContain('CTRS.safeParse'); - expect(result.changesCount).toBeGreaterThan(0); - expect(result.diagnostics[0]!.message).toContain('specTypeSchemas.CallToolRequest'); - }); - }); -}); diff --git a/packages/codemod/test/v1-to-v2/transforms/symbolRenames.test.ts b/packages/codemod/test/v1-to-v2/transforms/symbolRenames.test.ts index 9a227d2f4d..b9cb6137cb 100644 --- a/packages/codemod/test/v1-to-v2/transforms/symbolRenames.test.ts +++ b/packages/codemod/test/v1-to-v2/transforms/symbolRenames.test.ts @@ -44,6 +44,34 @@ describe('symbol-renames transform', () => { expect(result).toContain('isJSONRPCResultResponse'); }); + it('renames JSONRPCResponseSchema to JSONRPCResultResponseSchema (result-only in v1)', () => { + // v1's JSONRPCResponseSchema validated only result responses; v2 reuses the name for a union that + // also accepts errors. Rename to the result-only schema to preserve v1 parse/safeParse behavior. + const input = [ + `import { JSONRPCResponseSchema } from '@modelcontextprotocol/sdk/types.js';`, + `const r = JSONRPCResponseSchema.parse(value);`, + '' + ].join('\n'); + const result = applyTransform(input); + expect(result).toContain('JSONRPCResultResponseSchema.parse(value)'); + expect(result).not.toMatch(/(? { + // v1's JSONRPCResponse type was the result-only response; v2 reuses the name for a + // result|error union (Infer). Leaving the type unrenamed would + // silently widen a migrated v1 type import — mirror the schema/guard renames. + const input = [ + `import type { JSONRPCResponse } from '@modelcontextprotocol/server';`, + `function handle(r: JSONRPCResponse) { return r; }`, + '' + ].join('\n'); + const result = applyTransform(input); + expect(result).toContain('import type { JSONRPCResultResponse }'); + expect(result).toContain('r: JSONRPCResultResponse'); + expect(result).not.toMatch(/(? { const input = [ `import { ResourceReference } from '@modelcontextprotocol/sdk/types.js';`, diff --git a/packages/core-internal/CHANGELOG.md b/packages/core-internal/CHANGELOG.md new file mode 100644 index 0000000000..b1212d74db --- /dev/null +++ b/packages/core-internal/CHANGELOG.md @@ -0,0 +1,180 @@ +# @modelcontextprotocol/core-internal + +## 2.0.0-alpha.2 + +### Major Changes + +- [#2128](https://github.com/modelcontextprotocol/typescript-sdk/pull/2128) [`c8d7401`](https://github.com/modelcontextprotocol/typescript-sdk/commit/c8d7401b46f34b6c49b7cfb7b321714d0d4048f6) Thanks [@felixweinberger](https://github.com/felixweinberger)! - SEP-2663: remove + 2025-11 experimental tasks (TaskManager, experimental.tasks.\* accessors). Tasks are now Extensions Track. + +### Minor Changes + +- [#2049](https://github.com/modelcontextprotocol/typescript-sdk/pull/2049) [`4f226c1`](https://github.com/modelcontextprotocol/typescript-sdk/commit/4f226c1e35200616d62f1d7e46a2daa33d91172a) Thanks [@KKonstantinov](https://github.com/KKonstantinov)! - Add `SdkHttpError` subclass + with typed `.status` / `.statusText` accessors for HTTP transport failures. `StreamableHTTPClientTransport` now throws `SdkHttpError` (which extends `SdkError`) for non-OK HTTP responses; `SSEClientTransport` throws `SdkHttpError` for 401-after-reauth (circuit breaker). + +- [#1974](https://github.com/modelcontextprotocol/typescript-sdk/pull/1974) [`db83829`](https://github.com/modelcontextprotocol/typescript-sdk/commit/db83829c5bd5d6659c5e7b96638b11953b0e262d) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Add custom (non-spec) + method support: a 3-arg `setRequestHandler(method, schemas, handler)` / `setNotificationHandler(method, schemas, handler)` form for vendor-prefixed methods, and a `request(req, resultSchema)` overload (also on `ctx.mcpReq.send`) for typed custom-method results. Spec-method + calls are unchanged. + + Response result-schema validation failure now rejects with `SdkError(InvalidResult)` instead of a raw `ZodError`. Adds `SdkErrorCode.InvalidResult`. + +- [#2248](https://github.com/modelcontextprotocol/typescript-sdk/pull/2248) [`db28156`](https://github.com/modelcontextprotocol/typescript-sdk/commit/db28156a23032290b3ce3bae00a17544c4807b8f) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Restore the 2025-11-25 + task wire types that were removed together with the task feature: the task schemas and inferred types, task members of the request/result/notification unions, the `task` request-params augmentation, the `tasks` capability key, the `isTaskAugmentedRequestParams` guard, and + `RELATED_TASK_META_KEY`. The task feature itself remains removed — servers do not advertise the `tasks` capability and inbound `tasks/*` requests receive `-32601` — but the wire surface stays so SDKs interoperate cleanly with peers on the 2025-11-25 revision. + +- [#2088](https://github.com/modelcontextprotocol/typescript-sdk/pull/2088) [`16d13ab`](https://github.com/modelcontextprotocol/typescript-sdk/commit/16d13abf78b5dba5de73dfa284325b13d4219bb2) Thanks [@mattzcarey](https://github.com/mattzcarey)! - Bundle automatic JSON Schema + validator defaults in `@modelcontextprotocol/client` and `@modelcontextprotocol/server` runtime shims. + + Client and server pick the right validator automatically based on the runtime: the Node shim uses AJV, the browser/workerd shim uses `@cfworker/json-schema`. Both backends are bundled into the shim chunks that select them, so the default code path needs no extra installs — + `import { McpServer } from '@modelcontextprotocol/server'` does not pull `ajv` or `@cfworker/json-schema` into the root entry chunk. + + The named validator classes remain part of the public surface for consumers who want to customize the built-in backend (pre-register schemas by `$id`, register custom AJV formats, switch dialects, change `@cfworker/json-schema` draft). They are exposed through explicit + subpaths so they do not bloat the root index chunk: + - `import { AjvJsonSchemaValidator } from '@modelcontextprotocol/{client,server}/validators/ajv'` + - `import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/{client,server}/validators/cf-worker'` + + Importing from one of these subpaths means the corresponding peer dep (`ajv` + `ajv-formats`, or `@cfworker/json-schema`) must be in your `package.json`. The shim keeps its own vendored copy for the default path, so a project can use the subpath in some files and rely on the + default in others. + + The `jsonSchemaValidator` interface remains the public extension point for replacing validation entirely with a custom implementation. + +### Patch Changes + +- [#1901](https://github.com/modelcontextprotocol/typescript-sdk/pull/1901) [`e15a8ef`](https://github.com/modelcontextprotocol/typescript-sdk/commit/e15a8ef3be19520d8159ae9f5b464ba3ac80a5ab) Thanks [@felixweinberger](https://github.com/felixweinberger)! - + `registerTool`/`registerPrompt` accept a raw Zod shape (`{ field: z.string() }`) for `inputSchema`/`outputSchema`/`argsSchema` in addition to a wrapped Standard Schema. Raw shapes are auto-wrapped with `z.object()`. The raw-shape overloads are `@deprecated`; prefer wrapping + with `z.object()`. + + Also widens the `completable()` constraint from `StandardSchemaWithJSON` to `StandardSchemaV1` so v1's `completable(z.string(), fn)` continues to work. + +- [#2268](https://github.com/modelcontextprotocol/typescript-sdk/pull/2268) [`49c0a71`](https://github.com/modelcontextprotocol/typescript-sdk/commit/49c0a711c8bf2d385f9e03b4f28ba0ff0d0db0bd) Thanks [@mattzcarey](https://github.com/mattzcarey)! - Mark the roots, sampling, and + logging runtime APIs as `@deprecated` per SEP-2577 (deprecated as of protocol version 2026-07-28; functional for at least twelve months). Annotates `Server.createMessage`/`listRoots`/`sendLoggingMessage`, `McpServer.sendLoggingMessage`, + `Client.setLoggingLevel`/`sendRootsListChanged`, the `ServerContext.mcpReq.log`/`requestSampling` helpers, and the `roots`/`sampling`/`logging` capability schema fields. JSDoc/docs only — no behavior change. + +- [#2270](https://github.com/modelcontextprotocol/typescript-sdk/pull/2270) [`78fbe27`](https://github.com/modelcontextprotocol/typescript-sdk/commit/78fbe2736d72be4841072b359b3a2b8c6f97cd5c) Thanks [@mattzcarey](https://github.com/mattzcarey)! - Add reserved trace context + `_meta` key constants (`TRACEPARENT_META_KEY`, `TRACESTATE_META_KEY`, `BAGGAGE_META_KEY`) per SEP-414, plus docs and a passthrough regression test. The spec reserves the unprefixed `_meta` keys `traceparent`, `tracestate`, and `baggage` (W3C Trace Context / W3C Baggage formats) + for distributed tracing; the SDK passes them through untouched. + +- [#2252](https://github.com/modelcontextprotocol/typescript-sdk/pull/2252) [`8d55531`](https://github.com/modelcontextprotocol/typescript-sdk/commit/8d55531dabd5aa2de8864d691520cd6c6fe77541) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Add per-revision spec + reference types (2025-11-25 and 2026-07-28) with split comparison tests, and the 2026-07-28 wire contract surface: request-meta key constants, `RequestMetaEnvelopeSchema`, `server/discover` shapes, the typed `-32004` error, the `-32003` code constant, and a `resultType` + passthrough on the base result. Types and constants only — no behavior changes. + +- [#2275](https://github.com/modelcontextprotocol/typescript-sdk/pull/2275) [`1b53a41`](https://github.com/modelcontextprotocol/typescript-sdk/commit/1b53a415ea2c33aa11ac413fc9c2d68ccffde784) Thanks [@KKonstantinov](https://github.com/KKonstantinov)! - Add a configurable + `maxBufferSize` (default 10 MB) to the stdio transports. When a single message would push the read buffer past the limit, the transport now emits an `onerror` and closes instead of growing the buffer unbounded. Configure via `new StdioClientTransport({ ..., maxBufferSize })` or + `new StdioServerTransport(stdin, stdout, { maxBufferSize })`. The default is exported from `@modelcontextprotocol/core-internal` as `STDIO_DEFAULT_MAX_BUFFER_SIZE`. + +- [#1976](https://github.com/modelcontextprotocol/typescript-sdk/pull/1976) [`55b1f06`](https://github.com/modelcontextprotocol/typescript-sdk/commit/55b1f06cd4569e334f3435b7971f0446f1ef9be9) Thanks [@felixweinberger](https://github.com/felixweinberger)! - refactor: subclasses + override `_wrapHandler` hook instead of redeclaring `setRequestHandler`. + +- [#1768](https://github.com/modelcontextprotocol/typescript-sdk/pull/1768) [`866c08d`](https://github.com/modelcontextprotocol/typescript-sdk/commit/866c08d3640c5213f80c3b4220e24c42acfc2db8) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Allow additional JSON + Schema properties in elicitInput's requestedSchema type by adding .catchall(z.unknown()), matching the pattern used by inputSchema. This fixes type incompatibility when using Zod v4's .toJSONSchema() output which includes extra properties like $schema and additionalProperties. + +- [#1895](https://github.com/modelcontextprotocol/typescript-sdk/pull/1895) [`b256546`](https://github.com/modelcontextprotocol/typescript-sdk/commit/b256546750277faeb7c886792aae5ed26e6904d5) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Fix runtime crash on + `tools/list` when a tool's `inputSchema` comes from zod 4.0–4.1. The SDK requires `~standard.jsonSchema` (StandardJSONSchemaV1, added in zod 4.2.0); previously a missing `jsonSchema` crashed at `undefined[io]`. `standardSchemaToJsonSchema` now detects zod 4 schemas lacking + `jsonSchema` and falls back to the SDK-bundled `z.toJSONSchema()`, emitting a one-time console warning. zod 3 schemas (which the bundled zod 4 converter cannot introspect) and non-zod schema libraries without `jsonSchema` get a clear error pointing to `fromJsonSchema()`. The + workspace zod catalog is also bumped to `^4.2.0`. + +## 2.0.0-alpha.1 + +### Minor Changes + +- [#1673](https://github.com/modelcontextprotocol/typescript-sdk/pull/1673) [`462c3fc`](https://github.com/modelcontextprotocol/typescript-sdk/commit/462c3fc47dffac908d2ba27784d47ff010fa065e) Thanks [@KKonstantinov](https://github.com/KKonstantinov)! - refactor: extract task + orchestration from Protocol into TaskManager + + **Breaking changes:** + - `taskStore`, `taskMessageQueue`, `defaultTaskPollInterval`, and `maxTaskQueueSize` moved from `ProtocolOptions` to `capabilities.tasks` on `ClientOptions`/`ServerOptions` + +- [#1389](https://github.com/modelcontextprotocol/typescript-sdk/pull/1389) [`108f2f3`](https://github.com/modelcontextprotocol/typescript-sdk/commit/108f2f3ab6a1267587c7c4f900b6eca3cc2dae51) Thanks [@DePasqualeOrg](https://github.com/DePasqualeOrg)! - Fix error handling for + unknown tools and resources per MCP spec. + + **Tools:** Unknown or disabled tool calls now return JSON-RPC protocol errors with code `-32602` (InvalidParams) instead of `CallToolResult` with `isError: true`. Callers who checked `result.isError` for unknown tools should catch rejected promises instead. + + **Resources:** Unknown resource reads now return error code `-32002` (ResourceNotFound) instead of `-32602` (InvalidParams). + + Added `ProtocolErrorCode.ResourceNotFound`. + +- [#1689](https://github.com/modelcontextprotocol/typescript-sdk/pull/1689) [`0784be1`](https://github.com/modelcontextprotocol/typescript-sdk/commit/0784be1a67fb3cc2aba0182d88151264f4ea73c8) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Support Standard Schema + for tool and prompt schemas + + Tool and prompt registration now accepts any schema library that implements the [Standard Schema spec](https://standardschema.dev/): Zod v4, Valibot, ArkType, and others. `RegisteredTool.inputSchema`, `RegisteredTool.outputSchema`, and `RegisteredPrompt.argsSchema` now use + `StandardSchemaWithJSON` (requires both `~standard.validate` and `~standard.jsonSchema`) instead of the Zod-specific `AnySchema` type. + + **Zod v4 schemas continue to work unchanged** — Zod v4 implements the required interfaces natively. + + ```typescript + import { type } from 'arktype'; + + server.registerTool( + 'greet', + { + inputSchema: type({ name: 'string' }) + }, + async ({ name }) => ({ content: [{ type: 'text', text: `Hello, ${name}!` }] }) + ); + ``` + + For raw JSON Schema (e.g. TypeBox output), use the new `fromJsonSchema` adapter: + + ```typescript + import { fromJsonSchema, AjvJsonSchemaValidator } from '@modelcontextprotocol/core-internal'; + + server.registerTool( + 'greet', + { + inputSchema: fromJsonSchema({ type: 'object', properties: { name: { type: 'string' } } }, new AjvJsonSchemaValidator()) + }, + handler + ); + ``` + + **Breaking changes:** + - `experimental.tasks.getTaskResult()` no longer accepts a `resultSchema` parameter. Returns `GetTaskPayloadResult` (a loose `Result`); cast to the expected type at the call site. + - Removed unused exports from `@modelcontextprotocol/core-internal`: `SchemaInput`, `schemaToJson`, `parseSchemaAsync`, `getSchemaShape`, `getSchemaDescription`, `isOptionalSchema`, `unwrapOptionalSchema`. Use the new `standardSchemaToJsonSchema` and `validateStandardSchema` + instead. + - `completable()` remains Zod-specific (it relies on Zod's `.shape` introspection). + +### Patch Changes + +- [#1735](https://github.com/modelcontextprotocol/typescript-sdk/pull/1735) [`a2e5037`](https://github.com/modelcontextprotocol/typescript-sdk/commit/a2e503733f6f3eea3a79a80bdc1b3cdd743f8bb3) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Abort in-flight request + handlers when the connection closes. Previously, request handlers would continue running after the transport disconnected, wasting resources and preventing proper cleanup. Also fixes `InMemoryTransport.close()` firing `onclose` twice on the initiating side. + +- [#1574](https://github.com/modelcontextprotocol/typescript-sdk/pull/1574) [`379392d`](https://github.com/modelcontextprotocol/typescript-sdk/commit/379392d04460ee2cbeecae374901fae21e525031) Thanks [@olaservo](https://github.com/olaservo)! - Add missing `size` field to + `ResourceSchema` to match the MCP specification + +- [#1363](https://github.com/modelcontextprotocol/typescript-sdk/pull/1363) [`0a75810`](https://github.com/modelcontextprotocol/typescript-sdk/commit/0a75810b26e24bae6b9cfb41e12ac770aeaa1da4) Thanks [@DevJanderson](https://github.com/DevJanderson)! - Fix ReDoS vulnerability in + UriTemplate regex patterns (CVE-2026-0621) + +- [#1761](https://github.com/modelcontextprotocol/typescript-sdk/pull/1761) [`01954e6`](https://github.com/modelcontextprotocol/typescript-sdk/commit/01954e621afe525cc3c1bbe8d781e44734cf81c2) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Convert remaining + capability-assertion throws to `SdkError(SdkErrorCode.CapabilityNotSupported, ...)`. Follow-up to #1454 which missed `Client.assertCapability()`, the task capability helpers in `experimental/tasks/helpers.ts`, and the sampling/elicitation capability checks in + `experimental/tasks/server.ts`. + +- [#1790](https://github.com/modelcontextprotocol/typescript-sdk/pull/1790) [`89fb094`](https://github.com/modelcontextprotocol/typescript-sdk/commit/89fb0947b487b37f9bfcc2a2486dcd33d3922f8e) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Consolidate per-request + cleanup in `_requestWithSchema` into a single `.finally()` block. This fixes an abort signal listener leak (listeners accumulated when a caller reused one `AbortSignal` across requests) and two cases where `_responseHandlers` entries leaked on send-failure paths. + +- [#1486](https://github.com/modelcontextprotocol/typescript-sdk/pull/1486) [`65bbcea`](https://github.com/modelcontextprotocol/typescript-sdk/commit/65bbceab773277f056a9d3e385e7e7d8cef54f9b) Thanks [@localden](https://github.com/localden)! - Fix InMemoryTaskStore to enforce + session isolation. Previously, sessionId was accepted but ignored on all TaskStore methods, allowing any session to enumerate, read, and mutate tasks created by other sessions. The store now persists sessionId at creation time and enforces ownership on all reads and writes. + +- [#1766](https://github.com/modelcontextprotocol/typescript-sdk/pull/1766) [`48aba0d`](https://github.com/modelcontextprotocol/typescript-sdk/commit/48aba0d3c3b2ee04c442934095b663d19e07a3b3) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Add explicit + `| undefined` to optional properties on the `Transport` interface and `TransportSendOptions` (`onclose`, `onerror`, `onmessage`, `sessionId`, `setProtocolVersion`, `setSupportedProtocolVersions`, `onresumptiontoken`). + + This fixes TS2420 errors for consumers using `exactOptionalPropertyTypes: true` without `skipLibCheck`, where the emitted `.d.ts` for implementing classes included `| undefined` but the interface did not. + + Workaround for older SDK versions: enable `skipLibCheck: true` in your tsconfig. + +- [#1419](https://github.com/modelcontextprotocol/typescript-sdk/pull/1419) [`dcf708d`](https://github.com/modelcontextprotocol/typescript-sdk/commit/dcf708d892b7ca5f137c74109d42cdeb05e2ee3a) Thanks [@KKonstantinov](https://github.com/KKonstantinov)! - remove deprecated .tool, + .prompt, .resource method signatures + +- [#1534](https://github.com/modelcontextprotocol/typescript-sdk/pull/1534) [`69a0626`](https://github.com/modelcontextprotocol/typescript-sdk/commit/69a062693f61e024d7a366db0c3e3ba74ff59d8e) Thanks [@josefaidt](https://github.com/josefaidt)! - remove npm references, use pnpm + +- [#1534](https://github.com/modelcontextprotocol/typescript-sdk/pull/1534) [`69a0626`](https://github.com/modelcontextprotocol/typescript-sdk/commit/69a062693f61e024d7a366db0c3e3ba74ff59d8e) Thanks [@josefaidt](https://github.com/josefaidt)! - clean up package manager usage, all + pnpm + +- [#1796](https://github.com/modelcontextprotocol/typescript-sdk/pull/1796) [`d6a02c8`](https://github.com/modelcontextprotocol/typescript-sdk/commit/d6a02c85c0514658c27615398a3003aadce80fb0) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Ensure + `standardSchemaToJsonSchema` emits `type: "object"` at the root, fixing discriminated-union tool/prompt schemas that previously produced `{oneOf: [...]}` without the MCP-required top-level type. Also throws a clear error when given an explicitly non-object schema (e.g. + `z.string()`). Fixes #1643. + +- [#1419](https://github.com/modelcontextprotocol/typescript-sdk/pull/1419) [`dcf708d`](https://github.com/modelcontextprotocol/typescript-sdk/commit/dcf708d892b7ca5f137c74109d42cdeb05e2ee3a) Thanks [@KKonstantinov](https://github.com/KKonstantinov)! - deprecated .tool, .prompt, + .resource method removal + +- [#1762](https://github.com/modelcontextprotocol/typescript-sdk/pull/1762) [`64897f7`](https://github.com/modelcontextprotocol/typescript-sdk/commit/64897f78ce78f736b027dfecd1b4326c8c6678c7) Thanks [@felixweinberger](https://github.com/felixweinberger)! - + `ReadBuffer.readMessage()` now silently skips non-JSON lines instead of throwing `SyntaxError`. This prevents noisy `onerror` callbacks when hot-reload tools (tsx, nodemon) write debug output like "Gracefully restarting..." to stdout. Lines that parse as JSON but fail JSONRPC + schema validation still throw. diff --git a/packages/core-internal/eslint.config.mjs b/packages/core-internal/eslint.config.mjs new file mode 100644 index 0000000000..951c9f3a91 --- /dev/null +++ b/packages/core-internal/eslint.config.mjs @@ -0,0 +1,5 @@ +// @ts-check + +import baseConfig from '@modelcontextprotocol/eslint-config'; + +export default baseConfig; diff --git a/packages/core-internal/package.json b/packages/core-internal/package.json new file mode 100644 index 0000000000..6fbfbfec90 --- /dev/null +++ b/packages/core-internal/package.json @@ -0,0 +1,100 @@ +{ + "name": "@modelcontextprotocol/core-internal", + "private": true, + "version": "2.0.0-alpha.2", + "description": "Model Context Protocol implementation for TypeScript - Core package", + "license": "MIT", + "author": "Anthropic, PBC (https://anthropic.com)", + "homepage": "https://modelcontextprotocol.io", + "bugs": "https://github.com/modelcontextprotocol/typescript-sdk/issues", + "type": "module", + "repository": { + "type": "git", + "url": "git+https://github.com/modelcontextprotocol/typescript-sdk.git" + }, + "engines": { + "node": ">=20" + }, + "keywords": [ + "modelcontextprotocol", + "mcp", + "core" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs" + }, + "./types": { + "types": "./src/exports/types/index.ts", + "import": "./src/exports/types/index.ts" + }, + "./public": { + "types": "./src/exports/public/index.ts", + "import": "./src/exports/public/index.ts" + }, + "./validators/ajv": { + "types": "./src/validators/ajvProvider.ts", + "import": "./src/validators/ajvProvider.ts" + }, + "./validators/cfWorker": { + "types": "./src/validators/cfWorkerProvider.ts", + "import": "./src/validators/cfWorkerProvider.ts" + } + }, + "scripts": { + "typecheck": "tsgo -p tsconfig.json --noEmit", + "lint": "eslint src/ && prettier --ignore-path ../../.prettierignore --check .", + "lint:fix": "eslint src/ --fix && prettier --ignore-path ../../.prettierignore --write .", + "check": "pnpm run typecheck && pnpm run lint", + "test": "vitest run", + "test:watch": "vitest" + }, + "dependencies": { + "json-schema-typed": "catalog:runtimeShared", + "zod": "catalog:runtimeShared" + }, + "peerDependencies": { + "@cfworker/json-schema": "catalog:runtimeShared", + "ajv": "catalog:runtimeShared", + "ajv-formats": "catalog:runtimeShared", + "zod": "catalog:runtimeShared" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "ajv": { + "optional": true + }, + "ajv-formats": { + "optional": true + }, + "zod": { + "optional": false + } + }, + "devDependencies": { + "@modelcontextprotocol/tsconfig": "workspace:^", + "@modelcontextprotocol/vitest-config": "workspace:^", + "@modelcontextprotocol/eslint-config": "workspace:^", + "@cfworker/json-schema": "catalog:runtimeShared", + "ajv": "catalog:runtimeShared", + "ajv-formats": "catalog:runtimeShared", + "@eslint/js": "catalog:devTools", + "@types/content-type": "catalog:devTools", + "@types/cors": "catalog:devTools", + "@types/cross-spawn": "catalog:devTools", + "@types/eventsource": "catalog:devTools", + "@types/express": "catalog:devTools", + "@types/express-serve-static-core": "catalog:devTools", + "@typescript/native-preview": "catalog:devTools", + "eslint": "catalog:devTools", + "eslint-config-prettier": "catalog:devTools", + "eslint-plugin-n": "catalog:devTools", + "prettier": "catalog:devTools", + "typescript": "catalog:devTools", + "typescript-eslint": "catalog:devTools", + "vitest": "catalog:devTools" + } +} diff --git a/packages/core/src/auth/errors.ts b/packages/core-internal/src/auth/errors.ts similarity index 100% rename from packages/core/src/auth/errors.ts rename to packages/core-internal/src/auth/errors.ts diff --git a/packages/core/src/errors/sdkErrors.examples.ts b/packages/core-internal/src/errors/sdkErrors.examples.ts similarity index 100% rename from packages/core/src/errors/sdkErrors.examples.ts rename to packages/core-internal/src/errors/sdkErrors.examples.ts diff --git a/packages/core/src/errors/sdkErrors.ts b/packages/core-internal/src/errors/sdkErrors.ts similarity index 100% rename from packages/core/src/errors/sdkErrors.ts rename to packages/core-internal/src/errors/sdkErrors.ts diff --git a/packages/core/src/exports/public/index.ts b/packages/core-internal/src/exports/public/index.ts similarity index 97% rename from packages/core/src/exports/public/index.ts rename to packages/core-internal/src/exports/public/index.ts index 0ea4b2683a..88d4942a74 100644 --- a/packages/core/src/exports/public/index.ts +++ b/packages/core-internal/src/exports/public/index.ts @@ -1,11 +1,11 @@ /** - * Curated public API exports for @modelcontextprotocol/core. + * Curated public API exports for @modelcontextprotocol/core-internal. * * This module defines the stable, public-facing API surface. Client and server * packages re-export from here so that end users only see supported symbols. * * Internal utilities (Protocol class, stdio parsing, schema helpers, etc.) - * remain available via the internal barrel (@modelcontextprotocol/core) for + * remain available via the internal barrel (@modelcontextprotocol/core-internal) for * use by client/server packages. */ @@ -19,6 +19,7 @@ export { SdkError, SdkErrorCode, SdkHttpError } from '../../errors/sdkErrors'; // Auth TypeScript types (NOT Zod schemas like OAuthMetadataSchema) export type { AuthorizationServerMetadata, + IdJagTokenExchangeResponse, OAuthClientInformation, OAuthClientInformationFull, OAuthClientInformationMixed, diff --git a/packages/core/src/exports/types/index.ts b/packages/core-internal/src/exports/types/index.ts similarity index 100% rename from packages/core/src/exports/types/index.ts rename to packages/core-internal/src/exports/types/index.ts diff --git a/packages/core-internal/src/index.ts b/packages/core-internal/src/index.ts new file mode 100644 index 0000000000..fb89b0383d --- /dev/null +++ b/packages/core-internal/src/index.ts @@ -0,0 +1,22 @@ +export * from './auth/errors'; +export * from './errors/sdkErrors'; +export * from './shared/auth'; +export * from './shared/authUtils'; +export * from './shared/metadataUtils'; +export * from './shared/protocol'; +export * from './shared/stdio'; +export * from './shared/toolNameValidation'; +export * from './shared/transport'; +export * from './shared/uriTemplate'; +export * from './types/index'; +export * from './util/inMemory'; +export * from './util/schema'; +export * from './util/standardSchema'; +export * from './util/zodCompat'; + +// Validator providers are type-only here — import the runtime classes from the explicit +// `@modelcontextprotocol/{client,server}/validators/{ajv,cf-worker}` subpaths to customise. +export type { AjvJsonSchemaValidator } from './validators/ajvProvider'; +export type { CfWorkerJsonSchemaValidator, CfWorkerSchemaDraft } from './validators/cfWorkerProvider'; +export * from './validators/fromJsonSchema'; +export type { JsonSchemaType, JsonSchemaValidator, jsonSchemaValidator, JsonSchemaValidatorResult } from './validators/types'; diff --git a/packages/core/src/shared/auth.ts b/packages/core-internal/src/shared/auth.ts similarity index 100% rename from packages/core/src/shared/auth.ts rename to packages/core-internal/src/shared/auth.ts diff --git a/packages/core/src/shared/authUtils.ts b/packages/core-internal/src/shared/authUtils.ts similarity index 100% rename from packages/core/src/shared/authUtils.ts rename to packages/core-internal/src/shared/authUtils.ts diff --git a/packages/core/src/shared/metadataUtils.ts b/packages/core-internal/src/shared/metadataUtils.ts similarity index 100% rename from packages/core/src/shared/metadataUtils.ts rename to packages/core-internal/src/shared/metadataUtils.ts diff --git a/packages/core/src/shared/protocol.examples.ts b/packages/core-internal/src/shared/protocol.examples.ts similarity index 100% rename from packages/core/src/shared/protocol.examples.ts rename to packages/core-internal/src/shared/protocol.examples.ts diff --git a/packages/core/src/shared/protocol.ts b/packages/core-internal/src/shared/protocol.ts similarity index 100% rename from packages/core/src/shared/protocol.ts rename to packages/core-internal/src/shared/protocol.ts diff --git a/packages/core/src/shared/stdio.ts b/packages/core-internal/src/shared/stdio.ts similarity index 100% rename from packages/core/src/shared/stdio.ts rename to packages/core-internal/src/shared/stdio.ts diff --git a/packages/core/src/shared/toolNameValidation.ts b/packages/core-internal/src/shared/toolNameValidation.ts similarity index 100% rename from packages/core/src/shared/toolNameValidation.ts rename to packages/core-internal/src/shared/toolNameValidation.ts diff --git a/packages/core/src/shared/transport.ts b/packages/core-internal/src/shared/transport.ts similarity index 100% rename from packages/core/src/shared/transport.ts rename to packages/core-internal/src/shared/transport.ts diff --git a/packages/core/src/shared/uriTemplate.ts b/packages/core-internal/src/shared/uriTemplate.ts similarity index 100% rename from packages/core/src/shared/uriTemplate.ts rename to packages/core-internal/src/shared/uriTemplate.ts diff --git a/packages/core/src/types/constants.ts b/packages/core-internal/src/types/constants.ts similarity index 100% rename from packages/core/src/types/constants.ts rename to packages/core-internal/src/types/constants.ts diff --git a/packages/core/src/types/enums.ts b/packages/core-internal/src/types/enums.ts similarity index 100% rename from packages/core/src/types/enums.ts rename to packages/core-internal/src/types/enums.ts diff --git a/packages/core/src/types/errors.ts b/packages/core-internal/src/types/errors.ts similarity index 100% rename from packages/core/src/types/errors.ts rename to packages/core-internal/src/types/errors.ts diff --git a/packages/core/src/types/guards.ts b/packages/core-internal/src/types/guards.ts similarity index 100% rename from packages/core/src/types/guards.ts rename to packages/core-internal/src/types/guards.ts diff --git a/packages/core/src/types/index.ts b/packages/core-internal/src/types/index.ts similarity index 71% rename from packages/core/src/types/index.ts rename to packages/core-internal/src/types/index.ts index bbb00b1e73..46dd118f11 100644 --- a/packages/core/src/types/index.ts +++ b/packages/core-internal/src/types/index.ts @@ -1,5 +1,5 @@ // Internal barrel — re-exports everything for use within the SDK packages. -// The public API is defined in @modelcontextprotocol/core/public (see exports/public/index.ts). +// The public API is defined in @modelcontextprotocol/core-internal/public (see exports/public/index.ts). export * from './constants'; export * from './enums'; export * from './errors'; diff --git a/packages/core/src/types/schemas.ts b/packages/core-internal/src/types/schemas.ts similarity index 100% rename from packages/core/src/types/schemas.ts rename to packages/core-internal/src/types/schemas.ts diff --git a/packages/core/src/types/spec.types.2025-11-25.ts b/packages/core-internal/src/types/spec.types.2025-11-25.ts similarity index 100% rename from packages/core/src/types/spec.types.2025-11-25.ts rename to packages/core-internal/src/types/spec.types.2025-11-25.ts diff --git a/packages/core/src/types/spec.types.2026-07-28.ts b/packages/core-internal/src/types/spec.types.2026-07-28.ts similarity index 100% rename from packages/core/src/types/spec.types.2026-07-28.ts rename to packages/core-internal/src/types/spec.types.2026-07-28.ts diff --git a/packages/core/src/types/specTypeSchema.examples.ts b/packages/core-internal/src/types/specTypeSchema.examples.ts similarity index 100% rename from packages/core/src/types/specTypeSchema.examples.ts rename to packages/core-internal/src/types/specTypeSchema.examples.ts diff --git a/packages/core/src/types/specTypeSchema.ts b/packages/core-internal/src/types/specTypeSchema.ts similarity index 99% rename from packages/core/src/types/specTypeSchema.ts rename to packages/core-internal/src/types/specTypeSchema.ts index 927e3790d6..95aa718d5c 100644 --- a/packages/core/src/types/specTypeSchema.ts +++ b/packages/core-internal/src/types/specTypeSchema.ts @@ -1,6 +1,7 @@ import type * as z from 'zod/v4'; import { + IdJagTokenExchangeResponseSchema, OAuthClientInformationFullSchema, OAuthClientInformationSchema, OAuthClientMetadataSchema, @@ -188,6 +189,7 @@ const SPEC_SCHEMA_KEYS = [ ] as const satisfies readonly (keyof typeof schemas)[]; const authSchemas = { + IdJagTokenExchangeResponseSchema, OAuthClientInformationFullSchema, OAuthClientInformationSchema, OAuthClientMetadataSchema, diff --git a/packages/core/src/types/types.ts b/packages/core-internal/src/types/types.ts similarity index 100% rename from packages/core/src/types/types.ts rename to packages/core-internal/src/types/types.ts diff --git a/packages/core/src/util/inMemory.ts b/packages/core-internal/src/util/inMemory.ts similarity index 100% rename from packages/core/src/util/inMemory.ts rename to packages/core-internal/src/util/inMemory.ts diff --git a/packages/core/src/util/schema.ts b/packages/core-internal/src/util/schema.ts similarity index 100% rename from packages/core/src/util/schema.ts rename to packages/core-internal/src/util/schema.ts diff --git a/packages/core/src/util/standardSchema.ts b/packages/core-internal/src/util/standardSchema.ts similarity index 100% rename from packages/core/src/util/standardSchema.ts rename to packages/core-internal/src/util/standardSchema.ts diff --git a/packages/core/src/util/zodCompat.ts b/packages/core-internal/src/util/zodCompat.ts similarity index 100% rename from packages/core/src/util/zodCompat.ts rename to packages/core-internal/src/util/zodCompat.ts diff --git a/packages/core/src/validators/ajvProvider.examples.ts b/packages/core-internal/src/validators/ajvProvider.examples.ts similarity index 100% rename from packages/core/src/validators/ajvProvider.examples.ts rename to packages/core-internal/src/validators/ajvProvider.examples.ts diff --git a/packages/core/src/validators/ajvProvider.ts b/packages/core-internal/src/validators/ajvProvider.ts similarity index 100% rename from packages/core/src/validators/ajvProvider.ts rename to packages/core-internal/src/validators/ajvProvider.ts diff --git a/packages/core/src/validators/cfWorkerProvider.examples.ts b/packages/core-internal/src/validators/cfWorkerProvider.examples.ts similarity index 100% rename from packages/core/src/validators/cfWorkerProvider.examples.ts rename to packages/core-internal/src/validators/cfWorkerProvider.examples.ts diff --git a/packages/core/src/validators/cfWorkerProvider.ts b/packages/core-internal/src/validators/cfWorkerProvider.ts similarity index 100% rename from packages/core/src/validators/cfWorkerProvider.ts rename to packages/core-internal/src/validators/cfWorkerProvider.ts diff --git a/packages/core/src/validators/fromJsonSchema.examples.ts b/packages/core-internal/src/validators/fromJsonSchema.examples.ts similarity index 100% rename from packages/core/src/validators/fromJsonSchema.examples.ts rename to packages/core-internal/src/validators/fromJsonSchema.examples.ts diff --git a/packages/core/src/validators/fromJsonSchema.ts b/packages/core-internal/src/validators/fromJsonSchema.ts similarity index 100% rename from packages/core/src/validators/fromJsonSchema.ts rename to packages/core-internal/src/validators/fromJsonSchema.ts diff --git a/packages/core/src/validators/types.examples.ts b/packages/core-internal/src/validators/types.examples.ts similarity index 100% rename from packages/core/src/validators/types.examples.ts rename to packages/core-internal/src/validators/types.examples.ts diff --git a/packages/core/src/validators/types.ts b/packages/core-internal/src/validators/types.ts similarity index 100% rename from packages/core/src/validators/types.ts rename to packages/core-internal/src/validators/types.ts diff --git a/packages/core/test/errors/sdkHttpError.test.ts b/packages/core-internal/test/errors/sdkHttpError.test.ts similarity index 100% rename from packages/core/test/errors/sdkHttpError.test.ts rename to packages/core-internal/test/errors/sdkHttpError.test.ts diff --git a/packages/core/test/inMemory.test.ts b/packages/core-internal/test/inMemory.test.ts similarity index 100% rename from packages/core/test/inMemory.test.ts rename to packages/core-internal/test/inMemory.test.ts diff --git a/packages/core/test/shared/auth.test.ts b/packages/core-internal/test/shared/auth.test.ts similarity index 100% rename from packages/core/test/shared/auth.test.ts rename to packages/core-internal/test/shared/auth.test.ts diff --git a/packages/core/test/shared/authUtils.test.ts b/packages/core-internal/test/shared/authUtils.test.ts similarity index 100% rename from packages/core/test/shared/authUtils.test.ts rename to packages/core-internal/test/shared/authUtils.test.ts diff --git a/packages/core/test/shared/customMethods.test.ts b/packages/core-internal/test/shared/customMethods.test.ts similarity index 100% rename from packages/core/test/shared/customMethods.test.ts rename to packages/core-internal/test/shared/customMethods.test.ts diff --git a/packages/core/test/shared/protocol.test.ts b/packages/core-internal/test/shared/protocol.test.ts similarity index 100% rename from packages/core/test/shared/protocol.test.ts rename to packages/core-internal/test/shared/protocol.test.ts diff --git a/packages/core/test/shared/protocolTransportHandling.test.ts b/packages/core-internal/test/shared/protocolTransportHandling.test.ts similarity index 100% rename from packages/core/test/shared/protocolTransportHandling.test.ts rename to packages/core-internal/test/shared/protocolTransportHandling.test.ts diff --git a/packages/core/test/shared/stdio.test.ts b/packages/core-internal/test/shared/stdio.test.ts similarity index 100% rename from packages/core/test/shared/stdio.test.ts rename to packages/core-internal/test/shared/stdio.test.ts diff --git a/packages/core/test/shared/toolNameValidation.test.ts b/packages/core-internal/test/shared/toolNameValidation.test.ts similarity index 100% rename from packages/core/test/shared/toolNameValidation.test.ts rename to packages/core-internal/test/shared/toolNameValidation.test.ts diff --git a/packages/core/test/shared/traceContextMeta.test.ts b/packages/core-internal/test/shared/traceContextMeta.test.ts similarity index 100% rename from packages/core/test/shared/traceContextMeta.test.ts rename to packages/core-internal/test/shared/traceContextMeta.test.ts diff --git a/packages/core/test/shared/transport.test.ts b/packages/core-internal/test/shared/transport.test.ts similarity index 100% rename from packages/core/test/shared/transport.test.ts rename to packages/core-internal/test/shared/transport.test.ts diff --git a/packages/core/test/shared/uriTemplate.test.ts b/packages/core-internal/test/shared/uriTemplate.test.ts similarity index 100% rename from packages/core/test/shared/uriTemplate.test.ts rename to packages/core-internal/test/shared/uriTemplate.test.ts diff --git a/packages/core/test/shared/wrapHandler.test.ts b/packages/core-internal/test/shared/wrapHandler.test.ts similarity index 100% rename from packages/core/test/shared/wrapHandler.test.ts rename to packages/core-internal/test/shared/wrapHandler.test.ts diff --git a/packages/core/test/spec.types.2025-11-25.test.ts b/packages/core-internal/test/spec.types.2025-11-25.test.ts similarity index 100% rename from packages/core/test/spec.types.2025-11-25.test.ts rename to packages/core-internal/test/spec.types.2025-11-25.test.ts diff --git a/packages/core/test/spec.types.2026-07-28.test.ts b/packages/core-internal/test/spec.types.2026-07-28.test.ts similarity index 100% rename from packages/core/test/spec.types.2026-07-28.test.ts rename to packages/core-internal/test/spec.types.2026-07-28.test.ts diff --git a/packages/core/test/types.capabilities.test.ts b/packages/core-internal/test/types.capabilities.test.ts similarity index 100% rename from packages/core/test/types.capabilities.test.ts rename to packages/core-internal/test/types.capabilities.test.ts diff --git a/packages/core/test/types.test.ts b/packages/core-internal/test/types.test.ts similarity index 100% rename from packages/core/test/types.test.ts rename to packages/core-internal/test/types.test.ts diff --git a/packages/core/test/types/errors.test.ts b/packages/core-internal/test/types/errors.test.ts similarity index 100% rename from packages/core/test/types/errors.test.ts rename to packages/core-internal/test/types/errors.test.ts diff --git a/packages/core/test/types/guards.test.ts b/packages/core-internal/test/types/guards.test.ts similarity index 100% rename from packages/core/test/types/guards.test.ts rename to packages/core-internal/test/types/guards.test.ts diff --git a/packages/core/test/types/specTypeSchema.test.ts b/packages/core-internal/test/types/specTypeSchema.test.ts similarity index 96% rename from packages/core/test/types/specTypeSchema.test.ts rename to packages/core-internal/test/types/specTypeSchema.test.ts index 5605b2d48a..e04e9f1c45 100644 --- a/packages/core/test/types/specTypeSchema.test.ts +++ b/packages/core-internal/test/types/specTypeSchema.test.ts @@ -166,10 +166,11 @@ describe('SPEC_SCHEMA_KEYS allowlist', () => { .filter(k => !INTERNAL_HELPER_SCHEMAS.includes(k)) .map(k => k.slice(0, -'Schema'.length)) .sort(); - // Auth schemas are sourced from shared/auth.ts, not schemas.ts, so filter them out of the - // observed side before comparing. + // Auth schemas are sourced from shared/auth.ts, not schemas.ts. Keep only the protocol entries + // (whose `*Schema` const lives in schemas.ts) so the comparison stays against schemas.ts — + // robust to new auth schemas (e.g. IdJagTokenExchangeResponse) without a name-prefix heuristic. const actual = Object.keys(isSpecType) - .filter(k => !k.startsWith('OAuth') && !k.startsWith('OpenId')) + .filter(k => `${k}Schema` in schemas) .sort(); expect(actual).toEqual(expected); }); diff --git a/packages/core/test/util/standardSchema.test.ts b/packages/core-internal/test/util/standardSchema.test.ts similarity index 100% rename from packages/core/test/util/standardSchema.test.ts rename to packages/core-internal/test/util/standardSchema.test.ts diff --git a/packages/core/test/util/standardSchema.zodFallback.test.ts b/packages/core-internal/test/util/standardSchema.zodFallback.test.ts similarity index 100% rename from packages/core/test/util/standardSchema.zodFallback.test.ts rename to packages/core-internal/test/util/standardSchema.zodFallback.test.ts diff --git a/packages/core/test/util/zodCompat.test.ts b/packages/core-internal/test/util/zodCompat.test.ts similarity index 100% rename from packages/core/test/util/zodCompat.test.ts rename to packages/core-internal/test/util/zodCompat.test.ts diff --git a/packages/core/test/validators/validators.test.ts b/packages/core-internal/test/validators/validators.test.ts similarity index 100% rename from packages/core/test/validators/validators.test.ts rename to packages/core-internal/test/validators/validators.test.ts diff --git a/packages/core-internal/tsconfig.json b/packages/core-internal/tsconfig.json new file mode 100644 index 0000000000..a6838303e4 --- /dev/null +++ b/packages/core-internal/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@modelcontextprotocol/tsconfig", + "include": ["./"], + "exclude": ["node_modules", "dist"], + "compilerOptions": { + "paths": { + "*": ["./*"], + "@modelcontextprotocol/eslint-config": ["./node_modules/@modelcontextprotocol/eslint-config/tsconfig.json"], + "@modelcontextprotocol/vitest-config": ["./node_modules/@modelcontextprotocol/vitest-config/tsconfig.json"] + } + } +} diff --git a/packages/core-internal/vitest.config.js b/packages/core-internal/vitest.config.js new file mode 100644 index 0000000000..496fca3200 --- /dev/null +++ b/packages/core-internal/vitest.config.js @@ -0,0 +1,3 @@ +import baseConfig from '@modelcontextprotocol/vitest-config'; + +export default baseConfig; diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index e25cd09faa..6c0e610989 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -4,103 +4,7 @@ ### Minor Changes -- [#1673](https://github.com/modelcontextprotocol/typescript-sdk/pull/1673) [`462c3fc`](https://github.com/modelcontextprotocol/typescript-sdk/commit/462c3fc47dffac908d2ba27784d47ff010fa065e) Thanks [@KKonstantinov](https://github.com/KKonstantinov)! - refactor: extract task - orchestration from Protocol into TaskManager - - **Breaking changes:** - - `taskStore`, `taskMessageQueue`, `defaultTaskPollInterval`, and `maxTaskQueueSize` moved from `ProtocolOptions` to `capabilities.tasks` on `ClientOptions`/`ServerOptions` - -- [#1389](https://github.com/modelcontextprotocol/typescript-sdk/pull/1389) [`108f2f3`](https://github.com/modelcontextprotocol/typescript-sdk/commit/108f2f3ab6a1267587c7c4f900b6eca3cc2dae51) Thanks [@DePasqualeOrg](https://github.com/DePasqualeOrg)! - Fix error handling for - unknown tools and resources per MCP spec. - - **Tools:** Unknown or disabled tool calls now return JSON-RPC protocol errors with code `-32602` (InvalidParams) instead of `CallToolResult` with `isError: true`. Callers who checked `result.isError` for unknown tools should catch rejected promises instead. - - **Resources:** Unknown resource reads now return error code `-32002` (ResourceNotFound) instead of `-32602` (InvalidParams). - - Added `ProtocolErrorCode.ResourceNotFound`. - -- [#1689](https://github.com/modelcontextprotocol/typescript-sdk/pull/1689) [`0784be1`](https://github.com/modelcontextprotocol/typescript-sdk/commit/0784be1a67fb3cc2aba0182d88151264f4ea73c8) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Support Standard Schema - for tool and prompt schemas - - Tool and prompt registration now accepts any schema library that implements the [Standard Schema spec](https://standardschema.dev/): Zod v4, Valibot, ArkType, and others. `RegisteredTool.inputSchema`, `RegisteredTool.outputSchema`, and `RegisteredPrompt.argsSchema` now use - `StandardSchemaWithJSON` (requires both `~standard.validate` and `~standard.jsonSchema`) instead of the Zod-specific `AnySchema` type. - - **Zod v4 schemas continue to work unchanged** — Zod v4 implements the required interfaces natively. - - ```typescript - import { type } from 'arktype'; - - server.registerTool( - 'greet', - { - inputSchema: type({ name: 'string' }) - }, - async ({ name }) => ({ content: [{ type: 'text', text: `Hello, ${name}!` }] }) - ); - ``` - - For raw JSON Schema (e.g. TypeBox output), use the new `fromJsonSchema` adapter: - - ```typescript - import { fromJsonSchema, AjvJsonSchemaValidator } from '@modelcontextprotocol/core'; - - server.registerTool( - 'greet', - { - inputSchema: fromJsonSchema({ type: 'object', properties: { name: { type: 'string' } } }, new AjvJsonSchemaValidator()) - }, - handler - ); - ``` - - **Breaking changes:** - - `experimental.tasks.getTaskResult()` no longer accepts a `resultSchema` parameter. Returns `GetTaskPayloadResult` (a loose `Result`); cast to the expected type at the call site. - - Removed unused exports from `@modelcontextprotocol/core`: `SchemaInput`, `schemaToJson`, `parseSchemaAsync`, `getSchemaShape`, `getSchemaDescription`, `isOptionalSchema`, `unwrapOptionalSchema`. Use the new `standardSchemaToJsonSchema` and `validateStandardSchema` instead. - - `completable()` remains Zod-specific (it relies on Zod's `.shape` introspection). - -### Patch Changes - -- [#1735](https://github.com/modelcontextprotocol/typescript-sdk/pull/1735) [`a2e5037`](https://github.com/modelcontextprotocol/typescript-sdk/commit/a2e503733f6f3eea3a79a80bdc1b3cdd743f8bb3) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Abort in-flight request - handlers when the connection closes. Previously, request handlers would continue running after the transport disconnected, wasting resources and preventing proper cleanup. Also fixes `InMemoryTransport.close()` firing `onclose` twice on the initiating side. - -- [#1574](https://github.com/modelcontextprotocol/typescript-sdk/pull/1574) [`379392d`](https://github.com/modelcontextprotocol/typescript-sdk/commit/379392d04460ee2cbeecae374901fae21e525031) Thanks [@olaservo](https://github.com/olaservo)! - Add missing `size` field to - `ResourceSchema` to match the MCP specification - -- [#1363](https://github.com/modelcontextprotocol/typescript-sdk/pull/1363) [`0a75810`](https://github.com/modelcontextprotocol/typescript-sdk/commit/0a75810b26e24bae6b9cfb41e12ac770aeaa1da4) Thanks [@DevJanderson](https://github.com/DevJanderson)! - Fix ReDoS vulnerability in - UriTemplate regex patterns (CVE-2026-0621) - -- [#1761](https://github.com/modelcontextprotocol/typescript-sdk/pull/1761) [`01954e6`](https://github.com/modelcontextprotocol/typescript-sdk/commit/01954e621afe525cc3c1bbe8d781e44734cf81c2) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Convert remaining - capability-assertion throws to `SdkError(SdkErrorCode.CapabilityNotSupported, ...)`. Follow-up to #1454 which missed `Client.assertCapability()`, the task capability helpers in `experimental/tasks/helpers.ts`, and the sampling/elicitation capability checks in - `experimental/tasks/server.ts`. - -- [#1790](https://github.com/modelcontextprotocol/typescript-sdk/pull/1790) [`89fb094`](https://github.com/modelcontextprotocol/typescript-sdk/commit/89fb0947b487b37f9bfcc2a2486dcd33d3922f8e) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Consolidate per-request - cleanup in `_requestWithSchema` into a single `.finally()` block. This fixes an abort signal listener leak (listeners accumulated when a caller reused one `AbortSignal` across requests) and two cases where `_responseHandlers` entries leaked on send-failure paths. - -- [#1486](https://github.com/modelcontextprotocol/typescript-sdk/pull/1486) [`65bbcea`](https://github.com/modelcontextprotocol/typescript-sdk/commit/65bbceab773277f056a9d3e385e7e7d8cef54f9b) Thanks [@localden](https://github.com/localden)! - Fix InMemoryTaskStore to enforce - session isolation. Previously, sessionId was accepted but ignored on all TaskStore methods, allowing any session to enumerate, read, and mutate tasks created by other sessions. The store now persists sessionId at creation time and enforces ownership on all reads and writes. - -- [#1766](https://github.com/modelcontextprotocol/typescript-sdk/pull/1766) [`48aba0d`](https://github.com/modelcontextprotocol/typescript-sdk/commit/48aba0d3c3b2ee04c442934095b663d19e07a3b3) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Add explicit - `| undefined` to optional properties on the `Transport` interface and `TransportSendOptions` (`onclose`, `onerror`, `onmessage`, `sessionId`, `setProtocolVersion`, `setSupportedProtocolVersions`, `onresumptiontoken`). - - This fixes TS2420 errors for consumers using `exactOptionalPropertyTypes: true` without `skipLibCheck`, where the emitted `.d.ts` for implementing classes included `| undefined` but the interface did not. - - Workaround for older SDK versions: enable `skipLibCheck: true` in your tsconfig. - -- [#1419](https://github.com/modelcontextprotocol/typescript-sdk/pull/1419) [`dcf708d`](https://github.com/modelcontextprotocol/typescript-sdk/commit/dcf708d892b7ca5f137c74109d42cdeb05e2ee3a) Thanks [@KKonstantinov](https://github.com/KKonstantinov)! - remove deprecated .tool, - .prompt, .resource method signatures - -- [#1534](https://github.com/modelcontextprotocol/typescript-sdk/pull/1534) [`69a0626`](https://github.com/modelcontextprotocol/typescript-sdk/commit/69a062693f61e024d7a366db0c3e3ba74ff59d8e) Thanks [@josefaidt](https://github.com/josefaidt)! - remove npm references, use pnpm - -- [#1534](https://github.com/modelcontextprotocol/typescript-sdk/pull/1534) [`69a0626`](https://github.com/modelcontextprotocol/typescript-sdk/commit/69a062693f61e024d7a366db0c3e3ba74ff59d8e) Thanks [@josefaidt](https://github.com/josefaidt)! - clean up package manager usage, all - pnpm - -- [#1796](https://github.com/modelcontextprotocol/typescript-sdk/pull/1796) [`d6a02c8`](https://github.com/modelcontextprotocol/typescript-sdk/commit/d6a02c85c0514658c27615398a3003aadce80fb0) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Ensure - `standardSchemaToJsonSchema` emits `type: "object"` at the root, fixing discriminated-union tool/prompt schemas that previously produced `{oneOf: [...]}` without the MCP-required top-level type. Also throws a clear error when given an explicitly non-object schema (e.g. - `z.string()`). Fixes #1643. - -- [#1419](https://github.com/modelcontextprotocol/typescript-sdk/pull/1419) [`dcf708d`](https://github.com/modelcontextprotocol/typescript-sdk/commit/dcf708d892b7ca5f137c74109d42cdeb05e2ee3a) Thanks [@KKonstantinov](https://github.com/KKonstantinov)! - deprecated .tool, .prompt, - .resource method removal - -- [#1762](https://github.com/modelcontextprotocol/typescript-sdk/pull/1762) [`64897f7`](https://github.com/modelcontextprotocol/typescript-sdk/commit/64897f78ce78f736b027dfecd1b4326c8c6678c7) Thanks [@felixweinberger](https://github.com/felixweinberger)! - - `ReadBuffer.readMessage()` now silently skips non-JSON lines instead of throwing `SyntaxError`. This prevents noisy `onerror` callbacks when hot-reload tools (tsx, nodemon) write debug output like "Gracefully restarting..." to stdout. Lines that parse as JSON but fail JSONRPC - schema validation still throw. +- [#2354](https://github.com/modelcontextprotocol/typescript-sdk/pull/2354) [`0fb8406`](https://github.com/modelcontextprotocol/typescript-sdk/commit/0fb8406d83a3578a12a605e1b43c352d565071b1) Thanks [@KKonstantinov](https://github.com/KKonstantinov)! - Add + `@modelcontextprotocol/core`: the public home for the MCP specification and OAuth/OpenID Zod schemas. It bundles the SDK's internal schema definitions and re-exports only the `*Schema` values, so consumers can validate protocol payloads (`Schema.parse(value)` / + `.safeParse(value)`) without depending on a package's internal barrel. Alongside the spec schemas it also re-exports the auth schemas v1 exposed from `@modelcontextprotocol/sdk/shared/auth.js` (e.g. `OAuthTokensSchema`, `OAuthMetadataSchema`, + `IdJagTokenExchangeResponseSchema`). Spec types, error classes, enums, and guards continue to live on `@modelcontextprotocol/server` and `@modelcontextprotocol/client`. diff --git a/packages/core/README.md b/packages/core/README.md new file mode 100644 index 0000000000..5da63a8f2a --- /dev/null +++ b/packages/core/README.md @@ -0,0 +1,40 @@ +# @modelcontextprotocol/core + +Canonical public home for the [Model Context Protocol](https://modelcontextprotocol.io) specification and OAuth/OpenID **Zod schemas**. + +These are the exact schema constants the SDK validates protocol and OAuth/OpenID payloads against internally. The `@modelcontextprotocol/server` and `@modelcontextprotocol/client` packages keep a Zod-free public surface, so this package exists as the supported place to import the +raw schemas when you need to validate or parse MCP messages yourself. + +## Install + +```sh +npm install @modelcontextprotocol/core +``` + +## Usage + +```ts +import { CallToolResultSchema } from '@modelcontextprotocol/core'; + +// Throws on invalid input; returns the typed result on success. +const result = CallToolResultSchema.parse(payload); + +// Or non-throwing: +const parsed = CallToolResultSchema.safeParse(payload); +if (parsed.success) { + // parsed.data is a fully typed CallToolResult +} +``` + +## Scope + +This package exports **only** Zod schema constants (`*Schema`), in two groups: + +- the MCP **spec** schemas — `CallToolResultSchema`, `ListToolsResultSchema`, …; and +- the **OAuth/OpenID** auth schemas — `OAuthTokensSchema`, `OAuthMetadataSchema`, `IdJagTokenExchangeResponseSchema`, … (the schemas v1 exposed from `@modelcontextprotocol/sdk/shared/auth.js`). + +The corresponding TypeScript types, error classes, enums, and type guards are part of the public API of [`@modelcontextprotocol/server`](https://www.npmjs.com/package/@modelcontextprotocol/server) and +[`@modelcontextprotocol/client`](https://www.npmjs.com/package/@modelcontextprotocol/client). + +> **Migrating from v1?** In v1 these schemas were imported from `@modelcontextprotocol/sdk/types.js` (spec schemas) and `@modelcontextprotocol/sdk/shared/auth.js` (OAuth/OpenID schemas). Point those `*Schema` imports at `@modelcontextprotocol/core` and your existing `.parse()` / +> `.safeParse()` calls keep working unchanged. diff --git a/packages/core/eslint.config.mjs b/packages/core/eslint.config.mjs index 951c9f3a91..dd9e88588a 100644 --- a/packages/core/eslint.config.mjs +++ b/packages/core/eslint.config.mjs @@ -2,4 +2,11 @@ import baseConfig from '@modelcontextprotocol/eslint-config'; -export default baseConfig; +export default [ + ...baseConfig, + { + settings: { + 'import/internal-regex': '^@modelcontextprotocol/core-internal' + } + } +]; diff --git a/packages/core/package.json b/packages/core/package.json index 360f37ea1c..fabb970fc4 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,8 +1,7 @@ { "name": "@modelcontextprotocol/core", - "private": true, "version": "2.0.0-alpha.1", - "description": "Model Context Protocol implementation for TypeScript - Core package", + "description": "Model Context Protocol for TypeScript — public Zod schemas (spec + OAuth/OpenID)", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", "homepage": "https://modelcontextprotocol.io", @@ -18,32 +17,24 @@ "keywords": [ "modelcontextprotocol", "mcp", - "core" + "schemas", + "zod" ], "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.mjs" - }, - "./types": { - "types": "./src/exports/types/index.ts", - "import": "./src/exports/types/index.ts" - }, - "./public": { - "types": "./src/exports/public/index.ts", - "import": "./src/exports/public/index.ts" - }, - "./validators/ajv": { - "types": "./src/validators/ajvProvider.ts", - "import": "./src/validators/ajvProvider.ts" - }, - "./validators/cfWorker": { - "types": "./src/validators/cfWorkerProvider.ts", - "import": "./src/validators/cfWorkerProvider.ts" + "import": "./dist/index.js" } }, + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], "scripts": { "typecheck": "tsgo -p tsconfig.json --noEmit", + "build": "tsdown", + "build:watch": "tsdown --watch", + "prepack": "pnpm run build", "lint": "eslint src/ && prettier --ignore-path ../../.prettierignore --check .", "lint:fix": "eslint src/ --fix && prettier --ignore-path ../../.prettierignore --write .", "check": "pnpm run typecheck && pnpm run lint", @@ -51,48 +42,20 @@ "test:watch": "vitest" }, "dependencies": { - "json-schema-typed": "catalog:runtimeShared", - "zod": "catalog:runtimeShared" - }, - "peerDependencies": { - "@cfworker/json-schema": "catalog:runtimeShared", - "ajv": "catalog:runtimeShared", - "ajv-formats": "catalog:runtimeShared", "zod": "catalog:runtimeShared" }, - "peerDependenciesMeta": { - "@cfworker/json-schema": { - "optional": true - }, - "ajv": { - "optional": true - }, - "ajv-formats": { - "optional": true - }, - "zod": { - "optional": false - } - }, "devDependencies": { + "@eslint/js": "catalog:devTools", + "@modelcontextprotocol/core-internal": "workspace:^", + "@modelcontextprotocol/eslint-config": "workspace:^", "@modelcontextprotocol/tsconfig": "workspace:^", "@modelcontextprotocol/vitest-config": "workspace:^", - "@modelcontextprotocol/eslint-config": "workspace:^", - "@cfworker/json-schema": "catalog:runtimeShared", - "ajv": "catalog:runtimeShared", - "ajv-formats": "catalog:runtimeShared", - "@eslint/js": "catalog:devTools", - "@types/content-type": "catalog:devTools", - "@types/cors": "catalog:devTools", - "@types/cross-spawn": "catalog:devTools", - "@types/eventsource": "catalog:devTools", - "@types/express": "catalog:devTools", - "@types/express-serve-static-core": "catalog:devTools", "@typescript/native-preview": "catalog:devTools", "eslint": "catalog:devTools", "eslint-config-prettier": "catalog:devTools", "eslint-plugin-n": "catalog:devTools", "prettier": "catalog:devTools", + "tsdown": "catalog:devTools", "typescript": "catalog:devTools", "typescript-eslint": "catalog:devTools", "vitest": "catalog:devTools" diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 940ab08187..8ef16b453f 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,22 +1,198 @@ -export * from './auth/errors'; -export * from './errors/sdkErrors'; -export * from './shared/auth'; -export * from './shared/authUtils'; -export * from './shared/metadataUtils'; -export * from './shared/protocol'; -export * from './shared/stdio'; -export * from './shared/toolNameValidation'; -export * from './shared/transport'; -export * from './shared/uriTemplate'; -export * from './types/index'; -export * from './util/inMemory'; -export * from './util/schema'; -export * from './util/standardSchema'; -export * from './util/zodCompat'; +// @modelcontextprotocol/core +// +// Canonical public home for the Model Context Protocol specification + OAuth/OpenID Zod schemas. +// +// These are the exact schema constants the SDK validates against internally (defined in the +// private @modelcontextprotocol/core-internal package). This package bundles core-internal and re-exports ONLY the +// `*Schema` Zod values, so consumers can validate protocol/OAuth payloads directly — e.g. +// `CallToolResultSchema.parse(value)` / `.safeParse(value)` — without depending on core-internal's +// barrel. +// +// Scope: Zod schemas ONLY. The corresponding spec TypeScript types, error classes, enums, and +// type guards are part of the public API of @modelcontextprotocol/server and /client. +// +// Two groups, kept separate to mirror core-internal's own spec-vs-auth split, each bundled from a build-only +// subpath alias of core-internal (tsconfig.json + tsdown.config.ts): +// - SPEC schemas, from @modelcontextprotocol/core-internal/schemas (core-internal/src/types/schemas.ts): every +// `export const *Schema` EXCEPT internal helpers with no public spec type (e.g. +// BaseRequestParamsSchema). Mirrors core-internal's SPEC_SCHEMA_KEYS allowlist. +// - OAUTH/OPENID schemas, from @modelcontextprotocol/core-internal/auth (core-internal/src/shared/auth.ts). +// The coreSchemas test asserts both groups stay in sync with their core-internal source modules. +export { + AnnotationsSchema, + AudioContentSchema, + BaseMetadataSchema, + BlobResourceContentsSchema, + BooleanSchemaSchema, + CallToolRequestParamsSchema, + CallToolRequestSchema, + CallToolResultSchema, + CancelledNotificationParamsSchema, + CancelledNotificationSchema, + CancelTaskRequestSchema, + CancelTaskResultSchema, + ClientCapabilitiesSchema, + ClientNotificationSchema, + ClientRequestSchema, + ClientResultSchema, + CompatibilityCallToolResultSchema, + CompleteRequestParamsSchema, + CompleteRequestSchema, + CompleteResultSchema, + ContentBlockSchema, + CreateMessageRequestParamsSchema, + CreateMessageRequestSchema, + CreateMessageResultSchema, + CreateMessageResultWithToolsSchema, + CreateTaskResultSchema, + CursorSchema, + DiscoverRequestSchema, + DiscoverResultSchema, + ElicitationCompleteNotificationParamsSchema, + ElicitationCompleteNotificationSchema, + ElicitRequestFormParamsSchema, + ElicitRequestParamsSchema, + ElicitRequestSchema, + ElicitRequestURLParamsSchema, + ElicitResultSchema, + EmbeddedResourceSchema, + EmptyResultSchema, + EnumSchemaSchema, + GetPromptRequestParamsSchema, + GetPromptRequestSchema, + GetPromptResultSchema, + GetTaskPayloadRequestSchema, + GetTaskPayloadResultSchema, + GetTaskRequestSchema, + GetTaskResultSchema, + IconSchema, + IconsSchema, + ImageContentSchema, + ImplementationSchema, + InitializedNotificationSchema, + InitializeRequestParamsSchema, + InitializeRequestSchema, + InitializeResultSchema, + JSONArraySchema, + JSONObjectSchema, + JSONRPCErrorResponseSchema, + JSONRPCMessageSchema, + JSONRPCNotificationSchema, + JSONRPCRequestSchema, + JSONRPCResponseSchema, + JSONRPCResultResponseSchema, + JSONValueSchema, + LegacyTitledEnumSchemaSchema, + ListPromptsRequestSchema, + ListPromptsResultSchema, + ListResourcesRequestSchema, + ListResourcesResultSchema, + ListResourceTemplatesRequestSchema, + ListResourceTemplatesResultSchema, + ListRootsRequestSchema, + ListRootsResultSchema, + ListTasksRequestSchema, + ListTasksResultSchema, + ListToolsRequestSchema, + ListToolsResultSchema, + LoggingLevelSchema, + LoggingMessageNotificationParamsSchema, + LoggingMessageNotificationSchema, + ModelHintSchema, + ModelPreferencesSchema, + MultiSelectEnumSchemaSchema, + NotificationSchema, + NumberSchemaSchema, + PaginatedRequestParamsSchema, + PaginatedRequestSchema, + PaginatedResultSchema, + PingRequestSchema, + PrimitiveSchemaDefinitionSchema, + ProgressNotificationParamsSchema, + ProgressNotificationSchema, + ProgressSchema, + ProgressTokenSchema, + PromptArgumentSchema, + PromptListChangedNotificationSchema, + PromptMessageSchema, + PromptReferenceSchema, + PromptSchema, + ReadResourceRequestParamsSchema, + ReadResourceRequestSchema, + ReadResourceResultSchema, + RelatedTaskMetadataSchema, + RequestIdSchema, + RequestMetaEnvelopeSchema, + RequestMetaSchema, + RequestSchema, + ResourceContentsSchema, + ResourceLinkSchema, + ResourceListChangedNotificationSchema, + ResourceRequestParamsSchema, + ResourceSchema, + ResourceTemplateReferenceSchema, + ResourceTemplateSchema, + ResourceUpdatedNotificationParamsSchema, + ResourceUpdatedNotificationSchema, + ResultSchema, + RoleSchema, + RootSchema, + RootsListChangedNotificationSchema, + SamplingContentSchema, + SamplingMessageContentBlockSchema, + SamplingMessageSchema, + ServerCapabilitiesSchema, + ServerNotificationSchema, + ServerRequestSchema, + ServerResultSchema, + SetLevelRequestParamsSchema, + SetLevelRequestSchema, + SingleSelectEnumSchemaSchema, + StringSchemaSchema, + SubscribeRequestParamsSchema, + SubscribeRequestSchema, + TaskAugmentedRequestParamsSchema, + TaskCreationParamsSchema, + TaskMetadataSchema, + TaskSchema, + TaskStatusNotificationParamsSchema, + TaskStatusNotificationSchema, + TaskStatusSchema, + TextContentSchema, + TextResourceContentsSchema, + TitledMultiSelectEnumSchemaSchema, + TitledSingleSelectEnumSchemaSchema, + ToolAnnotationsSchema, + ToolChoiceSchema, + ToolExecutionSchema, + ToolListChangedNotificationSchema, + ToolResultContentSchema, + ToolSchema, + ToolUseContentSchema, + UnsubscribeRequestParamsSchema, + UnsubscribeRequestSchema, + UntitledMultiSelectEnumSchemaSchema, + UntitledSingleSelectEnumSchemaSchema +} from '@modelcontextprotocol/core-internal/schemas'; -// Validator providers are type-only here — import the runtime classes from the explicit -// `@modelcontextprotocol/{core,client,server}/validators/{ajv,cf-worker}` subpaths to customise. -export type { AjvJsonSchemaValidator } from './validators/ajvProvider'; -export type { CfWorkerJsonSchemaValidator, CfWorkerSchemaDraft } from './validators/cfWorkerProvider'; -export * from './validators/fromJsonSchema'; -export type { JsonSchemaType, JsonSchemaValidator, jsonSchemaValidator, JsonSchemaValidatorResult } from './validators/types'; +// Auth schemas (OAuth / OpenID / IdJag) — kept as a SEPARATE group from the MCP spec schemas above, +// mirroring core-internal's own spec-vs-auth split (these live in core-internal/src/shared/auth.ts, not types/schemas.ts, +// and are registered as `authSchemas` in core-internal's specTypeSchema.ts). This group is EXACTLY core-internal's +// `authSchemas` set — every auth schema that has a public spec type (so `isSpecType.OAuthTokens`, +// `isSpecType.IdJagTokenExchangeResponse`, etc. exist). The typeless internal URL field-validators +// (SafeUrlSchema, OptionalSafeUrlSchema) are not auth schemas and stay out. The coreSchemas test +// asserts this group stays in sync with core-internal's `authSchemas`. +export { + IdJagTokenExchangeResponseSchema, + OAuthClientInformationFullSchema, + OAuthClientInformationSchema, + OAuthClientMetadataSchema, + OAuthClientRegistrationErrorSchema, + OAuthErrorResponseSchema, + OAuthMetadataSchema, + OAuthProtectedResourceMetadataSchema, + OAuthTokenRevocationRequestSchema, + OAuthTokensSchema, + OpenIdProviderDiscoveryMetadataSchema, + OpenIdProviderMetadataSchema +} from '@modelcontextprotocol/core-internal/auth'; diff --git a/packages/core/test/coreSchemas.test.ts b/packages/core/test/coreSchemas.test.ts new file mode 100644 index 0000000000..85a0e08a09 --- /dev/null +++ b/packages/core/test/coreSchemas.test.ts @@ -0,0 +1,61 @@ +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; + +import { describe, expect, it } from 'vitest'; + +import * as core from '../src/index'; +import { CursorSchema, InitializeRequestSchema, OAuthTokensSchema } from '../src/index'; + +function readCore(relativePath: string): string { + return readFileSync(fileURLToPath(new URL(relativePath, import.meta.url)), 'utf8'); +} + +function exportedSchemaConsts(src: string, re: RegExp): string[] { + return [...src.matchAll(re)].map(m => m[1]).filter((name): name is string => name !== undefined && /^[A-Z]/.test(name)); +} + +describe('@modelcontextprotocol/core', () => { + it('re-exports spec + OAuth schemas as working Zod objects', () => { + // Round-trips valid/invalid values — proves the re-exports are real Zod schemas (not type-only + // aliases) and that `.parse`/`.safeParse` work, for both the spec and the OAuth group. + expect(CursorSchema.parse('abc')).toBe('abc'); + expect(InitializeRequestSchema.safeParse({}).success).toBe(false); + expect(OAuthTokensSchema.safeParse({}).success).toBe(false); + expect(OAuthTokensSchema.safeParse({ access_token: 'tok', token_type: 'Bearer' }).success).toBe(true); + }); + + it('re-exports exactly core’s spec + OAuth schemas — no internal helpers (drift guard)', () => { + // core's public surface is two SEPARATE groups, mirroring core-internal's own spec-vs-auth split: + // 1. spec `*Schema` constants from core-internal/src/types/schemas.ts (minus internal helpers with no + // public spec type — they must NOT leak), mirroring core-internal's SPEC_SCHEMA_KEYS allowlist; and + // 2. the auth `*Schema` constants registered in core-internal's `authSchemas` object (specTypeSchema.ts) + // — i.e. the auth schemas that have a public spec type. Reading that object directly (not a + // name prefix) is the source of truth, so a new auth schema added to core-internal is required here + // automatically; typeless internal helpers (SafeUrlSchema, OptionalSafeUrlSchema) stay out + // because they are not in `authSchemas`. + // Read the core-internal sources directly so the groups cannot silently drift. + const SPEC_INTERNAL_HELPERS = [ + 'BaseRequestParamsSchema', + 'ClientTasksCapabilitySchema', + 'ListChangedOptionsBaseSchema', + 'NotificationsParamsSchema', + 'ServerTasksCapabilitySchema' + ]; + const specSchemas = exportedSchemaConsts( + readCore('../../core-internal/src/types/schemas.ts'), + /^export const (\w+Schema)\b/gm + ).filter(name => !SPEC_INTERNAL_HELPERS.includes(name)); + const specTypeSrc = readCore('../../core-internal/src/types/specTypeSchema.ts'); + const authStart = specTypeSrc.indexOf('const authSchemas = {'); + const authObj = specTypeSrc.slice(authStart, specTypeSrc.indexOf('} as const', authStart)); + const authSchemas = exportedSchemaConsts(authObj, /\b(\w+Schema)\b/g); + + const expected = [...specSchemas, ...authSchemas].sort(); + const exported = Object.keys(core).sort(); + // Exact match, both directions: a new core spec/auth schema missing here fails (we forgot to + // re-export it), and any internal helper / non-spec symbol that leaks here also fails. + expect(exported).toEqual(expected); + expect(specSchemas.length).toBeGreaterThanOrEqual(154); + expect(authSchemas.length).toBe(12); + }); +}); diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index a6838303e4..e150389b59 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -5,8 +5,8 @@ "compilerOptions": { "paths": { "*": ["./*"], - "@modelcontextprotocol/eslint-config": ["./node_modules/@modelcontextprotocol/eslint-config/tsconfig.json"], - "@modelcontextprotocol/vitest-config": ["./node_modules/@modelcontextprotocol/vitest-config/tsconfig.json"] + "@modelcontextprotocol/core-internal/schemas": ["./node_modules/@modelcontextprotocol/core-internal/src/types/schemas.ts"], + "@modelcontextprotocol/core-internal/auth": ["./node_modules/@modelcontextprotocol/core-internal/src/shared/auth.ts"] } } } diff --git a/packages/core/tsdown.config.ts b/packages/core/tsdown.config.ts new file mode 100644 index 0000000000..464ae98615 --- /dev/null +++ b/packages/core/tsdown.config.ts @@ -0,0 +1,32 @@ +import { defineConfig } from 'tsdown'; + +// core re-exports ONLY the spec + OAuth Zod schemas from @modelcontextprotocol/core-internal (private, +// unpublished). Two BUILD-ONLY subpath aliases (not real core exports) point at core-internal's two schema +// modules, kept as separate sources: +// @modelcontextprotocol/core-internal/schemas → core-internal/src/types/schemas.ts (MCP spec schemas) +// @modelcontextprotocol/core-internal/auth → core-internal/src/shared/auth.ts (OAuth/OpenID schemas) +// Aliasing to these modules rather than core-internal's barrel keeps the bundled graph to just the schemas + +// the constants they use — never Protocol, transports, stdio, or the ajv/cfWorker validators. Both +// modules import only `zod/v4`, so the graph stays runtime-neutral; `platform: 'neutral'` makes a +// node-only dependency leaking in fail the build here instead of silently shipping. +export default defineConfig({ + failOnWarn: 'ci-only', + entry: ['src/index.ts'], + format: ['esm'], + outDir: 'dist', + clean: true, + sourcemap: true, + target: 'esnext', + platform: 'neutral', + dts: { + resolver: 'tsc', + compilerOptions: { + baseUrl: '.', + paths: { + '@modelcontextprotocol/core-internal/schemas': ['../core-internal/src/types/schemas.ts'], + '@modelcontextprotocol/core-internal/auth': ['../core-internal/src/shared/auth.ts'] + } + } + }, + noExternal: ['@modelcontextprotocol/core-internal/schemas', '@modelcontextprotocol/core-internal/auth'] +}); diff --git a/packages/core/typedoc.json b/packages/core/typedoc.json new file mode 100644 index 0000000000..08e5572417 --- /dev/null +++ b/packages/core/typedoc.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "entryPoints": ["src/index.ts"] +} diff --git a/packages/middleware/express/CHANGELOG.md b/packages/middleware/express/CHANGELOG.md index fb0fe43371..f3a3cd93bd 100644 --- a/packages/middleware/express/CHANGELOG.md +++ b/packages/middleware/express/CHANGELOG.md @@ -1,5 +1,31 @@ # @modelcontextprotocol/express +## 2.0.0-alpha.3 + +### Minor Changes + +- [#1907](https://github.com/modelcontextprotocol/typescript-sdk/pull/1907) [`7cccc2a`](https://github.com/modelcontextprotocol/typescript-sdk/commit/7cccc2aca81f4cd961d2a0ef53e879f68a01df73) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Add OAuth + Resource-Server glue to the Express adapter: `requireBearerAuth` middleware (token verification + RFC 6750 `WWW-Authenticate` challenges), `mcpAuthMetadataRouter` (serves RFC 9728 Protected Resource Metadata and mirrors RFC 8414 AS metadata at the resource origin), the + `getOAuthProtectedResourceMetadataUrl` helper, and the `OAuthTokenVerifier` interface. These restore the v1 `src/server/auth` Resource-Server pieces as first-class v2 API so MCP servers can plug into an external Authorization Server with a few lines of Express wiring. + +### Patch Changes + +- [#1898](https://github.com/modelcontextprotocol/typescript-sdk/pull/1898) [`2a7611d`](https://github.com/modelcontextprotocol/typescript-sdk/commit/2a7611d46b0d4f4e7bd4147c7a3ad3da00e57e52) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Add top-level `types` + field (and `typesVersions` on client/server for their subpath exports) so consumers on legacy `moduleResolution: "node"` can resolve type declarations. The `exports` map remains the source of truth for `nodenext`/`bundler` resolution. The `typesVersions` map includes entries + for subpaths added by sibling PRs in this series (`zod-schemas`, `stdio`); those entries are no-ops until the corresponding `dist/*.d.mts` files exist. + +- Updated dependencies [[`e8c7180`](https://github.com/modelcontextprotocol/typescript-sdk/commit/e8c7180109b417eef5cc22b7e9d821ab1c119a69), [`434b2f1`](https://github.com/modelcontextprotocol/typescript-sdk/commit/434b2f11ecec452f3dca0199f68afccd8b119dd4), + [`db83829`](https://github.com/modelcontextprotocol/typescript-sdk/commit/db83829c5bd5d6659c5e7b96638b11953b0e262d), [`e84c3e9`](https://github.com/modelcontextprotocol/typescript-sdk/commit/e84c3e9ad040eb09299b1f99dd8bdd14251ae790), + [`42cb6b2`](https://github.com/modelcontextprotocol/typescript-sdk/commit/42cb6b2b728347d8b58a0d1940b7e63366a29ab9), [`c59dc3a`](https://github.com/modelcontextprotocol/typescript-sdk/commit/c59dc3aa1a633d27fbbe873f1a430483cf7440f8), + [`df4b6cc`](https://github.com/modelcontextprotocol/typescript-sdk/commit/df4b6cc88d6f24fc857519cf506a7a039f532637), [`2c0c481`](https://github.com/modelcontextprotocol/typescript-sdk/commit/2c0c481cb9dbfd15c8613f765c940a5f5bace94d), + [`0fb8406`](https://github.com/modelcontextprotocol/typescript-sdk/commit/0fb8406d83a3578a12a605e1b43c352d565071b1), [`2a7611d`](https://github.com/modelcontextprotocol/typescript-sdk/commit/2a7611d46b0d4f4e7bd4147c7a3ad3da00e57e52), + [`e15a8ef`](https://github.com/modelcontextprotocol/typescript-sdk/commit/e15a8ef3be19520d8159ae9f5b464ba3ac80a5ab), [`db28156`](https://github.com/modelcontextprotocol/typescript-sdk/commit/db28156a23032290b3ce3bae00a17544c4807b8f), + [`49c0a71`](https://github.com/modelcontextprotocol/typescript-sdk/commit/49c0a711c8bf2d385f9e03b4f28ba0ff0d0db0bd), [`c8d7401`](https://github.com/modelcontextprotocol/typescript-sdk/commit/c8d7401b46f34b6c49b7cfb7b321714d0d4048f6), + [`96db044`](https://github.com/modelcontextprotocol/typescript-sdk/commit/96db044fe965f0b7d5109e6d68598eaddce961c9), [`1b53a41`](https://github.com/modelcontextprotocol/typescript-sdk/commit/1b53a415ea2c33aa11ac413fc9c2d68ccffde784), + [`9fc9070`](https://github.com/modelcontextprotocol/typescript-sdk/commit/9fc9070b7b8e18227127aaee9869f8809a87fdb1), [`16d13ab`](https://github.com/modelcontextprotocol/typescript-sdk/commit/16d13abf78b5dba5de73dfa284325b13d4219bb2), + [`55b1f06`](https://github.com/modelcontextprotocol/typescript-sdk/commit/55b1f06cd4569e334f3435b7971f0446f1ef9be9), [`b256546`](https://github.com/modelcontextprotocol/typescript-sdk/commit/b256546750277faeb7c886792aae5ed26e6904d5)]: + - @modelcontextprotocol/server@2.0.0-alpha.3 + ## 2.0.0-alpha.2 ### Patch Changes diff --git a/packages/middleware/express/eslint.config.mjs b/packages/middleware/express/eslint.config.mjs index 03d5331344..2284c163a1 100644 --- a/packages/middleware/express/eslint.config.mjs +++ b/packages/middleware/express/eslint.config.mjs @@ -6,7 +6,7 @@ export default [ ...baseConfig, { settings: { - 'import/internal-regex': '^@modelcontextprotocol/(server|core)' + 'import/internal-regex': '^@modelcontextprotocol/server' } } ]; diff --git a/packages/middleware/express/package.json b/packages/middleware/express/package.json index b0b695344e..8b675f789b 100644 --- a/packages/middleware/express/package.json +++ b/packages/middleware/express/package.json @@ -1,7 +1,7 @@ { "name": "@modelcontextprotocol/express", "private": false, - "version": "2.0.0-alpha.2", + "version": "2.0.0-alpha.3", "description": "Express adapters for the Model Context Protocol TypeScript server SDK - Express middleware", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", diff --git a/packages/middleware/express/tsconfig.json b/packages/middleware/express/tsconfig.json index 0292cb0c22..11330e7a4a 100644 --- a/packages/middleware/express/tsconfig.json +++ b/packages/middleware/express/tsconfig.json @@ -7,11 +7,11 @@ "*": ["./*"], "@modelcontextprotocol/server": ["./node_modules/@modelcontextprotocol/server/src/index.ts"], "@modelcontextprotocol/server/_shims": ["./node_modules/@modelcontextprotocol/server/src/shimsNode.ts"], - "@modelcontextprotocol/core": [ - "./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/src/index.ts" + "@modelcontextprotocol/core-internal": [ + "./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core-internal/src/index.ts" ], - "@modelcontextprotocol/core/public": [ - "./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/src/exports/public/index.ts" + "@modelcontextprotocol/core-internal/public": [ + "./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core-internal/src/exports/public/index.ts" ] } } diff --git a/packages/middleware/fastify/CHANGELOG.md b/packages/middleware/fastify/CHANGELOG.md index 94d0a35749..1661e5b9d3 100644 --- a/packages/middleware/fastify/CHANGELOG.md +++ b/packages/middleware/fastify/CHANGELOG.md @@ -1,5 +1,25 @@ # @modelcontextprotocol/fastify +## 2.0.0-alpha.3 + +### Patch Changes + +- [#1898](https://github.com/modelcontextprotocol/typescript-sdk/pull/1898) [`2a7611d`](https://github.com/modelcontextprotocol/typescript-sdk/commit/2a7611d46b0d4f4e7bd4147c7a3ad3da00e57e52) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Add top-level `types` + field (and `typesVersions` on client/server for their subpath exports) so consumers on legacy `moduleResolution: "node"` can resolve type declarations. The `exports` map remains the source of truth for `nodenext`/`bundler` resolution. The `typesVersions` map includes entries + for subpaths added by sibling PRs in this series (`zod-schemas`, `stdio`); those entries are no-ops until the corresponding `dist/*.d.mts` files exist. + +- Updated dependencies [[`e8c7180`](https://github.com/modelcontextprotocol/typescript-sdk/commit/e8c7180109b417eef5cc22b7e9d821ab1c119a69), [`434b2f1`](https://github.com/modelcontextprotocol/typescript-sdk/commit/434b2f11ecec452f3dca0199f68afccd8b119dd4), + [`db83829`](https://github.com/modelcontextprotocol/typescript-sdk/commit/db83829c5bd5d6659c5e7b96638b11953b0e262d), [`e84c3e9`](https://github.com/modelcontextprotocol/typescript-sdk/commit/e84c3e9ad040eb09299b1f99dd8bdd14251ae790), + [`42cb6b2`](https://github.com/modelcontextprotocol/typescript-sdk/commit/42cb6b2b728347d8b58a0d1940b7e63366a29ab9), [`c59dc3a`](https://github.com/modelcontextprotocol/typescript-sdk/commit/c59dc3aa1a633d27fbbe873f1a430483cf7440f8), + [`df4b6cc`](https://github.com/modelcontextprotocol/typescript-sdk/commit/df4b6cc88d6f24fc857519cf506a7a039f532637), [`2c0c481`](https://github.com/modelcontextprotocol/typescript-sdk/commit/2c0c481cb9dbfd15c8613f765c940a5f5bace94d), + [`0fb8406`](https://github.com/modelcontextprotocol/typescript-sdk/commit/0fb8406d83a3578a12a605e1b43c352d565071b1), [`2a7611d`](https://github.com/modelcontextprotocol/typescript-sdk/commit/2a7611d46b0d4f4e7bd4147c7a3ad3da00e57e52), + [`e15a8ef`](https://github.com/modelcontextprotocol/typescript-sdk/commit/e15a8ef3be19520d8159ae9f5b464ba3ac80a5ab), [`db28156`](https://github.com/modelcontextprotocol/typescript-sdk/commit/db28156a23032290b3ce3bae00a17544c4807b8f), + [`49c0a71`](https://github.com/modelcontextprotocol/typescript-sdk/commit/49c0a711c8bf2d385f9e03b4f28ba0ff0d0db0bd), [`c8d7401`](https://github.com/modelcontextprotocol/typescript-sdk/commit/c8d7401b46f34b6c49b7cfb7b321714d0d4048f6), + [`96db044`](https://github.com/modelcontextprotocol/typescript-sdk/commit/96db044fe965f0b7d5109e6d68598eaddce961c9), [`1b53a41`](https://github.com/modelcontextprotocol/typescript-sdk/commit/1b53a415ea2c33aa11ac413fc9c2d68ccffde784), + [`9fc9070`](https://github.com/modelcontextprotocol/typescript-sdk/commit/9fc9070b7b8e18227127aaee9869f8809a87fdb1), [`16d13ab`](https://github.com/modelcontextprotocol/typescript-sdk/commit/16d13abf78b5dba5de73dfa284325b13d4219bb2), + [`55b1f06`](https://github.com/modelcontextprotocol/typescript-sdk/commit/55b1f06cd4569e334f3435b7971f0446f1ef9be9), [`b256546`](https://github.com/modelcontextprotocol/typescript-sdk/commit/b256546750277faeb7c886792aae5ed26e6904d5)]: + - @modelcontextprotocol/server@2.0.0-alpha.3 + ## 2.0.0-alpha.2 ### Patch Changes diff --git a/packages/middleware/fastify/eslint.config.mjs b/packages/middleware/fastify/eslint.config.mjs index 03d5331344..2284c163a1 100644 --- a/packages/middleware/fastify/eslint.config.mjs +++ b/packages/middleware/fastify/eslint.config.mjs @@ -6,7 +6,7 @@ export default [ ...baseConfig, { settings: { - 'import/internal-regex': '^@modelcontextprotocol/(server|core)' + 'import/internal-regex': '^@modelcontextprotocol/server' } } ]; diff --git a/packages/middleware/fastify/package.json b/packages/middleware/fastify/package.json index de6df8f3bc..7afe78cc30 100644 --- a/packages/middleware/fastify/package.json +++ b/packages/middleware/fastify/package.json @@ -1,7 +1,7 @@ { "name": "@modelcontextprotocol/fastify", "private": false, - "version": "2.0.0-alpha.2", + "version": "2.0.0-alpha.3", "description": "Fastify adapters for the Model Context Protocol TypeScript server SDK - Fastify middleware", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", diff --git a/packages/middleware/fastify/tsconfig.json b/packages/middleware/fastify/tsconfig.json index c92435851b..62f257e788 100644 --- a/packages/middleware/fastify/tsconfig.json +++ b/packages/middleware/fastify/tsconfig.json @@ -7,8 +7,8 @@ "*": ["./*"], "@modelcontextprotocol/server": ["./node_modules/@modelcontextprotocol/server/src/index.ts"], "@modelcontextprotocol/server/_shims": ["./node_modules/@modelcontextprotocol/server/src/shimsNode.ts"], - "@modelcontextprotocol/core": [ - "./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/src/index.ts" + "@modelcontextprotocol/core-internal": [ + "./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core-internal/src/index.ts" ] } } diff --git a/packages/middleware/hono/CHANGELOG.md b/packages/middleware/hono/CHANGELOG.md index 7a9b246747..a621810ffb 100644 --- a/packages/middleware/hono/CHANGELOG.md +++ b/packages/middleware/hono/CHANGELOG.md @@ -1,5 +1,25 @@ # @modelcontextprotocol/hono +## 2.0.0-alpha.3 + +### Patch Changes + +- [#1898](https://github.com/modelcontextprotocol/typescript-sdk/pull/1898) [`2a7611d`](https://github.com/modelcontextprotocol/typescript-sdk/commit/2a7611d46b0d4f4e7bd4147c7a3ad3da00e57e52) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Add top-level `types` + field (and `typesVersions` on client/server for their subpath exports) so consumers on legacy `moduleResolution: "node"` can resolve type declarations. The `exports` map remains the source of truth for `nodenext`/`bundler` resolution. The `typesVersions` map includes entries + for subpaths added by sibling PRs in this series (`zod-schemas`, `stdio`); those entries are no-ops until the corresponding `dist/*.d.mts` files exist. + +- Updated dependencies [[`e8c7180`](https://github.com/modelcontextprotocol/typescript-sdk/commit/e8c7180109b417eef5cc22b7e9d821ab1c119a69), [`434b2f1`](https://github.com/modelcontextprotocol/typescript-sdk/commit/434b2f11ecec452f3dca0199f68afccd8b119dd4), + [`db83829`](https://github.com/modelcontextprotocol/typescript-sdk/commit/db83829c5bd5d6659c5e7b96638b11953b0e262d), [`e84c3e9`](https://github.com/modelcontextprotocol/typescript-sdk/commit/e84c3e9ad040eb09299b1f99dd8bdd14251ae790), + [`42cb6b2`](https://github.com/modelcontextprotocol/typescript-sdk/commit/42cb6b2b728347d8b58a0d1940b7e63366a29ab9), [`c59dc3a`](https://github.com/modelcontextprotocol/typescript-sdk/commit/c59dc3aa1a633d27fbbe873f1a430483cf7440f8), + [`df4b6cc`](https://github.com/modelcontextprotocol/typescript-sdk/commit/df4b6cc88d6f24fc857519cf506a7a039f532637), [`2c0c481`](https://github.com/modelcontextprotocol/typescript-sdk/commit/2c0c481cb9dbfd15c8613f765c940a5f5bace94d), + [`0fb8406`](https://github.com/modelcontextprotocol/typescript-sdk/commit/0fb8406d83a3578a12a605e1b43c352d565071b1), [`2a7611d`](https://github.com/modelcontextprotocol/typescript-sdk/commit/2a7611d46b0d4f4e7bd4147c7a3ad3da00e57e52), + [`e15a8ef`](https://github.com/modelcontextprotocol/typescript-sdk/commit/e15a8ef3be19520d8159ae9f5b464ba3ac80a5ab), [`db28156`](https://github.com/modelcontextprotocol/typescript-sdk/commit/db28156a23032290b3ce3bae00a17544c4807b8f), + [`49c0a71`](https://github.com/modelcontextprotocol/typescript-sdk/commit/49c0a711c8bf2d385f9e03b4f28ba0ff0d0db0bd), [`c8d7401`](https://github.com/modelcontextprotocol/typescript-sdk/commit/c8d7401b46f34b6c49b7cfb7b321714d0d4048f6), + [`96db044`](https://github.com/modelcontextprotocol/typescript-sdk/commit/96db044fe965f0b7d5109e6d68598eaddce961c9), [`1b53a41`](https://github.com/modelcontextprotocol/typescript-sdk/commit/1b53a415ea2c33aa11ac413fc9c2d68ccffde784), + [`9fc9070`](https://github.com/modelcontextprotocol/typescript-sdk/commit/9fc9070b7b8e18227127aaee9869f8809a87fdb1), [`16d13ab`](https://github.com/modelcontextprotocol/typescript-sdk/commit/16d13abf78b5dba5de73dfa284325b13d4219bb2), + [`55b1f06`](https://github.com/modelcontextprotocol/typescript-sdk/commit/55b1f06cd4569e334f3435b7971f0446f1ef9be9), [`b256546`](https://github.com/modelcontextprotocol/typescript-sdk/commit/b256546750277faeb7c886792aae5ed26e6904d5)]: + - @modelcontextprotocol/server@2.0.0-alpha.3 + ## 2.0.0-alpha.2 ### Patch Changes diff --git a/packages/middleware/hono/eslint.config.mjs b/packages/middleware/hono/eslint.config.mjs index 03d5331344..2284c163a1 100644 --- a/packages/middleware/hono/eslint.config.mjs +++ b/packages/middleware/hono/eslint.config.mjs @@ -6,7 +6,7 @@ export default [ ...baseConfig, { settings: { - 'import/internal-regex': '^@modelcontextprotocol/(server|core)' + 'import/internal-regex': '^@modelcontextprotocol/server' } } ]; diff --git a/packages/middleware/hono/package.json b/packages/middleware/hono/package.json index f067aedf91..79b2d02de1 100644 --- a/packages/middleware/hono/package.json +++ b/packages/middleware/hono/package.json @@ -1,7 +1,7 @@ { "name": "@modelcontextprotocol/hono", "private": false, - "version": "2.0.0-alpha.2", + "version": "2.0.0-alpha.3", "description": "Hono adapters for the Model Context Protocol TypeScript server SDK - Hono middleware", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", diff --git a/packages/middleware/hono/tsconfig.json b/packages/middleware/hono/tsconfig.json index 0292cb0c22..11330e7a4a 100644 --- a/packages/middleware/hono/tsconfig.json +++ b/packages/middleware/hono/tsconfig.json @@ -7,11 +7,11 @@ "*": ["./*"], "@modelcontextprotocol/server": ["./node_modules/@modelcontextprotocol/server/src/index.ts"], "@modelcontextprotocol/server/_shims": ["./node_modules/@modelcontextprotocol/server/src/shimsNode.ts"], - "@modelcontextprotocol/core": [ - "./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/src/index.ts" + "@modelcontextprotocol/core-internal": [ + "./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core-internal/src/index.ts" ], - "@modelcontextprotocol/core/public": [ - "./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/src/exports/public/index.ts" + "@modelcontextprotocol/core-internal/public": [ + "./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core-internal/src/exports/public/index.ts" ] } } diff --git a/packages/middleware/node/CHANGELOG.md b/packages/middleware/node/CHANGELOG.md index 242614d4a3..b40fabe1c0 100644 --- a/packages/middleware/node/CHANGELOG.md +++ b/packages/middleware/node/CHANGELOG.md @@ -1,5 +1,29 @@ # @modelcontextprotocol/node +## 2.0.0-alpha.3 + +### Patch Changes + +- [#1896](https://github.com/modelcontextprotocol/typescript-sdk/pull/1896) [`5433f40`](https://github.com/modelcontextprotocol/typescript-sdk/commit/5433f405972ab943c4d68b0eacf6d79baf132824) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Mark `hono` peer + dependency as optional. `@modelcontextprotocol/node` only uses `getRequestListener` from `@hono/node-server` (Node HTTP ↔ Web Standard conversion), which does not require the `hono` framework at runtime. Consumers no longer need to install `hono` to use + `NodeStreamableHTTPServerTransport`. Note: `@hono/node-server` itself still declares `hono` as a hard peer, so package managers may emit a warning; this is upstream and harmless for `getRequestListener`-only usage. + +- [#1898](https://github.com/modelcontextprotocol/typescript-sdk/pull/1898) [`2a7611d`](https://github.com/modelcontextprotocol/typescript-sdk/commit/2a7611d46b0d4f4e7bd4147c7a3ad3da00e57e52) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Add top-level `types` + field (and `typesVersions` on client/server for their subpath exports) so consumers on legacy `moduleResolution: "node"` can resolve type declarations. The `exports` map remains the source of truth for `nodenext`/`bundler` resolution. The `typesVersions` map includes entries + for subpaths added by sibling PRs in this series (`zod-schemas`, `stdio`); those entries are no-ops until the corresponding `dist/*.d.mts` files exist. + +- Updated dependencies [[`e8c7180`](https://github.com/modelcontextprotocol/typescript-sdk/commit/e8c7180109b417eef5cc22b7e9d821ab1c119a69), [`434b2f1`](https://github.com/modelcontextprotocol/typescript-sdk/commit/434b2f11ecec452f3dca0199f68afccd8b119dd4), + [`db83829`](https://github.com/modelcontextprotocol/typescript-sdk/commit/db83829c5bd5d6659c5e7b96638b11953b0e262d), [`e84c3e9`](https://github.com/modelcontextprotocol/typescript-sdk/commit/e84c3e9ad040eb09299b1f99dd8bdd14251ae790), + [`42cb6b2`](https://github.com/modelcontextprotocol/typescript-sdk/commit/42cb6b2b728347d8b58a0d1940b7e63366a29ab9), [`c59dc3a`](https://github.com/modelcontextprotocol/typescript-sdk/commit/c59dc3aa1a633d27fbbe873f1a430483cf7440f8), + [`df4b6cc`](https://github.com/modelcontextprotocol/typescript-sdk/commit/df4b6cc88d6f24fc857519cf506a7a039f532637), [`2c0c481`](https://github.com/modelcontextprotocol/typescript-sdk/commit/2c0c481cb9dbfd15c8613f765c940a5f5bace94d), + [`0fb8406`](https://github.com/modelcontextprotocol/typescript-sdk/commit/0fb8406d83a3578a12a605e1b43c352d565071b1), [`2a7611d`](https://github.com/modelcontextprotocol/typescript-sdk/commit/2a7611d46b0d4f4e7bd4147c7a3ad3da00e57e52), + [`e15a8ef`](https://github.com/modelcontextprotocol/typescript-sdk/commit/e15a8ef3be19520d8159ae9f5b464ba3ac80a5ab), [`db28156`](https://github.com/modelcontextprotocol/typescript-sdk/commit/db28156a23032290b3ce3bae00a17544c4807b8f), + [`49c0a71`](https://github.com/modelcontextprotocol/typescript-sdk/commit/49c0a711c8bf2d385f9e03b4f28ba0ff0d0db0bd), [`c8d7401`](https://github.com/modelcontextprotocol/typescript-sdk/commit/c8d7401b46f34b6c49b7cfb7b321714d0d4048f6), + [`96db044`](https://github.com/modelcontextprotocol/typescript-sdk/commit/96db044fe965f0b7d5109e6d68598eaddce961c9), [`1b53a41`](https://github.com/modelcontextprotocol/typescript-sdk/commit/1b53a415ea2c33aa11ac413fc9c2d68ccffde784), + [`9fc9070`](https://github.com/modelcontextprotocol/typescript-sdk/commit/9fc9070b7b8e18227127aaee9869f8809a87fdb1), [`16d13ab`](https://github.com/modelcontextprotocol/typescript-sdk/commit/16d13abf78b5dba5de73dfa284325b13d4219bb2), + [`55b1f06`](https://github.com/modelcontextprotocol/typescript-sdk/commit/55b1f06cd4569e334f3435b7971f0446f1ef9be9), [`b256546`](https://github.com/modelcontextprotocol/typescript-sdk/commit/b256546750277faeb7c886792aae5ed26e6904d5)]: + - @modelcontextprotocol/server@2.0.0-alpha.3 + ## 2.0.0-alpha.2 ### Patch Changes diff --git a/packages/middleware/node/eslint.config.mjs b/packages/middleware/node/eslint.config.mjs index 4f034f2235..dd9e88588a 100644 --- a/packages/middleware/node/eslint.config.mjs +++ b/packages/middleware/node/eslint.config.mjs @@ -6,7 +6,7 @@ export default [ ...baseConfig, { settings: { - 'import/internal-regex': '^@modelcontextprotocol/core' + 'import/internal-regex': '^@modelcontextprotocol/core-internal' } } ]; diff --git a/packages/middleware/node/package.json b/packages/middleware/node/package.json index 9d6ed525d9..955f02eabe 100644 --- a/packages/middleware/node/package.json +++ b/packages/middleware/node/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/node", - "version": "2.0.0-alpha.2", + "version": "2.0.0-alpha.3", "description": "Model Context Protocol implementation for TypeScript - Node.js middleware", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -62,7 +62,7 @@ }, "devDependencies": { "@modelcontextprotocol/server": "workspace:^", - "@modelcontextprotocol/core": "workspace:^", + "@modelcontextprotocol/core-internal": "workspace:^", "@modelcontextprotocol/eslint-config": "workspace:^", "@modelcontextprotocol/test-helpers": "workspace:^", "@modelcontextprotocol/tsconfig": "workspace:^", diff --git a/packages/middleware/node/test/streamableHttp.test.ts b/packages/middleware/node/test/streamableHttp.test.ts index e56166a279..140717c6bb 100644 --- a/packages/middleware/node/test/streamableHttp.test.ts +++ b/packages/middleware/node/test/streamableHttp.test.ts @@ -11,7 +11,7 @@ import type { JSONRPCMessage, JSONRPCResultResponse, RequestId -} from '@modelcontextprotocol/core'; +} from '@modelcontextprotocol/core-internal'; import type { EventId, EventStore, StreamId } from '@modelcontextprotocol/server'; import { McpServer } from '@modelcontextprotocol/server'; import { listenOnRandomPort } from '@modelcontextprotocol/test-helpers'; diff --git a/packages/middleware/node/tsconfig.json b/packages/middleware/node/tsconfig.json index 0985895356..7cd442d0d4 100644 --- a/packages/middleware/node/tsconfig.json +++ b/packages/middleware/node/tsconfig.json @@ -7,8 +7,10 @@ "*": ["./*"], "@modelcontextprotocol/server": ["./node_modules/@modelcontextprotocol/server/src/index.ts"], "@modelcontextprotocol/server/_shims": ["./node_modules/@modelcontextprotocol/server/src/shimsNode.ts"], - "@modelcontextprotocol/core": ["./node_modules/@modelcontextprotocol/core/src/index.ts"], - "@modelcontextprotocol/core/public": ["./node_modules/@modelcontextprotocol/core/src/exports/public/index.ts"], + "@modelcontextprotocol/core-internal": ["./node_modules/@modelcontextprotocol/core-internal/src/index.ts"], + "@modelcontextprotocol/core-internal/public": [ + "./node_modules/@modelcontextprotocol/core-internal/src/exports/public/index.ts" + ], "@modelcontextprotocol/test-helpers": ["./node_modules/@modelcontextprotocol/test-helpers/src/index.ts"] } } diff --git a/packages/server-legacy/CHANGELOG.md b/packages/server-legacy/CHANGELOG.md new file mode 100644 index 0000000000..8a2be446a4 --- /dev/null +++ b/packages/server-legacy/CHANGELOG.md @@ -0,0 +1,8 @@ +# @modelcontextprotocol/server-legacy + +## 2.0.0-alpha.3 + +### Minor Changes + +- [#2206](https://github.com/modelcontextprotocol/typescript-sdk/pull/2206) [`e03bca9`](https://github.com/modelcontextprotocol/typescript-sdk/commit/e03bca90c1f925f80843dc27fb4eb2421408a0c1) Thanks [@KKonstantinov](https://github.com/KKonstantinov)! - Add + @modelcontextprotocol/server-legacy package with frozen v1 SSE transport and OAuth Authorization Server helpers for migration from v1 to v2. diff --git a/packages/server-legacy/eslint.config.mjs b/packages/server-legacy/eslint.config.mjs index 4f034f2235..dd9e88588a 100644 --- a/packages/server-legacy/eslint.config.mjs +++ b/packages/server-legacy/eslint.config.mjs @@ -6,7 +6,7 @@ export default [ ...baseConfig, { settings: { - 'import/internal-regex': '^@modelcontextprotocol/core' + 'import/internal-regex': '^@modelcontextprotocol/core-internal' } } ]; diff --git a/packages/server-legacy/package.json b/packages/server-legacy/package.json index c320fe349d..6bf42d5155 100644 --- a/packages/server-legacy/package.json +++ b/packages/server-legacy/package.json @@ -1,7 +1,7 @@ { "name": "@modelcontextprotocol/server-legacy", "private": false, - "version": "2.0.0-alpha.2", + "version": "2.0.0-alpha.3", "description": "Frozen v1 SSE transport and OAuth Authorization Server helpers for the Model Context Protocol TypeScript SDK. Deprecated; use StreamableHTTP and a dedicated OAuth server in production.", "deprecated": "This package is a frozen copy of v1's SSE transport and OAuth Authorization Server helpers for migration purposes only. Use StreamableHTTP from @modelcontextprotocol/server and a dedicated OAuth server in production. Will not receive new features.", "license": "MIT", @@ -80,7 +80,7 @@ } }, "devDependencies": { - "@modelcontextprotocol/core": "workspace:^", + "@modelcontextprotocol/core-internal": "workspace:^", "@modelcontextprotocol/tsconfig": "workspace:^", "@modelcontextprotocol/eslint-config": "workspace:^", "@modelcontextprotocol/vitest-config": "workspace:^", diff --git a/packages/server-legacy/src/auth/clients.ts b/packages/server-legacy/src/auth/clients.ts index f6aca1be92..0c9412ca18 100644 --- a/packages/server-legacy/src/auth/clients.ts +++ b/packages/server-legacy/src/auth/clients.ts @@ -1,4 +1,4 @@ -import type { OAuthClientInformationFull } from '@modelcontextprotocol/core'; +import type { OAuthClientInformationFull } from '@modelcontextprotocol/core-internal'; /** * Stores information about registered OAuth clients for this server. diff --git a/packages/server-legacy/src/auth/errors.ts b/packages/server-legacy/src/auth/errors.ts index eac277779c..236ed22d1b 100644 --- a/packages/server-legacy/src/auth/errors.ts +++ b/packages/server-legacy/src/auth/errors.ts @@ -1,4 +1,4 @@ -import type { OAuthErrorResponse } from '@modelcontextprotocol/core'; +import type { OAuthErrorResponse } from '@modelcontextprotocol/core-internal'; /** * Base class for all OAuth errors diff --git a/packages/server-legacy/src/auth/handlers/metadata.ts b/packages/server-legacy/src/auth/handlers/metadata.ts index 23230e3d3f..e75b493320 100644 --- a/packages/server-legacy/src/auth/handlers/metadata.ts +++ b/packages/server-legacy/src/auth/handlers/metadata.ts @@ -1,4 +1,4 @@ -import type { OAuthMetadata, OAuthProtectedResourceMetadata } from '@modelcontextprotocol/core'; +import type { OAuthMetadata, OAuthProtectedResourceMetadata } from '@modelcontextprotocol/core-internal'; import cors from 'cors'; import type { RequestHandler } from 'express'; import express from 'express'; diff --git a/packages/server-legacy/src/auth/handlers/register.ts b/packages/server-legacy/src/auth/handlers/register.ts index 1cc19e96e9..d180ff35f7 100644 --- a/packages/server-legacy/src/auth/handlers/register.ts +++ b/packages/server-legacy/src/auth/handlers/register.ts @@ -1,7 +1,7 @@ import crypto from 'node:crypto'; -import type { OAuthClientInformationFull } from '@modelcontextprotocol/core'; -import { OAuthClientMetadataSchema } from '@modelcontextprotocol/core'; +import type { OAuthClientInformationFull } from '@modelcontextprotocol/core-internal'; +import { OAuthClientMetadataSchema } from '@modelcontextprotocol/core-internal'; import cors from 'cors'; import type { RequestHandler } from 'express'; import express from 'express'; diff --git a/packages/server-legacy/src/auth/handlers/revoke.ts b/packages/server-legacy/src/auth/handlers/revoke.ts index 0d39a80ece..218d89beba 100644 --- a/packages/server-legacy/src/auth/handlers/revoke.ts +++ b/packages/server-legacy/src/auth/handlers/revoke.ts @@ -1,4 +1,4 @@ -import { OAuthTokenRevocationRequestSchema } from '@modelcontextprotocol/core'; +import { OAuthTokenRevocationRequestSchema } from '@modelcontextprotocol/core-internal'; import cors from 'cors'; import type { RequestHandler } from 'express'; import express from 'express'; diff --git a/packages/server-legacy/src/auth/middleware/clientAuth.ts b/packages/server-legacy/src/auth/middleware/clientAuth.ts index 611d797d1a..ad9d931253 100644 --- a/packages/server-legacy/src/auth/middleware/clientAuth.ts +++ b/packages/server-legacy/src/auth/middleware/clientAuth.ts @@ -1,4 +1,4 @@ -import type { OAuthClientInformationFull } from '@modelcontextprotocol/core'; +import type { OAuthClientInformationFull } from '@modelcontextprotocol/core-internal'; import type { RequestHandler } from 'express'; import * as z from 'zod/v4'; diff --git a/packages/server-legacy/src/auth/provider.ts b/packages/server-legacy/src/auth/provider.ts index 0884997272..47e8836a3d 100644 --- a/packages/server-legacy/src/auth/provider.ts +++ b/packages/server-legacy/src/auth/provider.ts @@ -1,4 +1,4 @@ -import type { OAuthClientInformationFull, OAuthTokenRevocationRequest, OAuthTokens } from '@modelcontextprotocol/core'; +import type { OAuthClientInformationFull, OAuthTokenRevocationRequest, OAuthTokens } from '@modelcontextprotocol/core-internal'; import type { Response } from 'express'; import type { OAuthRegisteredClientsStore } from './clients'; diff --git a/packages/server-legacy/src/auth/providers/proxyProvider.ts b/packages/server-legacy/src/auth/providers/proxyProvider.ts index 622b576b0e..db85c34da6 100644 --- a/packages/server-legacy/src/auth/providers/proxyProvider.ts +++ b/packages/server-legacy/src/auth/providers/proxyProvider.ts @@ -1,5 +1,5 @@ -import type { FetchLike, OAuthClientInformationFull, OAuthTokenRevocationRequest, OAuthTokens } from '@modelcontextprotocol/core'; -import { OAuthClientInformationFullSchema, OAuthTokensSchema } from '@modelcontextprotocol/core'; +import type { FetchLike, OAuthClientInformationFull, OAuthTokenRevocationRequest, OAuthTokens } from '@modelcontextprotocol/core-internal'; +import { OAuthClientInformationFullSchema, OAuthTokensSchema } from '@modelcontextprotocol/core-internal'; import type { Response } from 'express'; import type { OAuthRegisteredClientsStore } from '../clients'; diff --git a/packages/server-legacy/src/auth/router.ts b/packages/server-legacy/src/auth/router.ts index 71cd3d54ec..6ffa1e36fc 100644 --- a/packages/server-legacy/src/auth/router.ts +++ b/packages/server-legacy/src/auth/router.ts @@ -1,4 +1,4 @@ -import type { OAuthMetadata, OAuthProtectedResourceMetadata } from '@modelcontextprotocol/core'; +import type { OAuthMetadata, OAuthProtectedResourceMetadata } from '@modelcontextprotocol/core-internal'; import type { RequestHandler } from 'express'; import express from 'express'; diff --git a/packages/server-legacy/src/auth/types.ts b/packages/server-legacy/src/auth/types.ts index 3ccfcdc344..1168c8be9b 100644 --- a/packages/server-legacy/src/auth/types.ts +++ b/packages/server-legacy/src/auth/types.ts @@ -1 +1 @@ -export type { AuthInfo } from '@modelcontextprotocol/core'; +export type { AuthInfo } from '@modelcontextprotocol/core-internal'; diff --git a/packages/server-legacy/src/sse/sse.ts b/packages/server-legacy/src/sse/sse.ts index 7c9a4eab6f..6add66f9f9 100644 --- a/packages/server-legacy/src/sse/sse.ts +++ b/packages/server-legacy/src/sse/sse.ts @@ -2,8 +2,8 @@ import { randomUUID } from 'node:crypto'; import type { IncomingMessage, ServerResponse } from 'node:http'; import { TLSSocket } from 'node:tls'; -import type { AuthInfo, JSONRPCMessage, MessageExtraInfo, Transport, TransportSendOptions } from '@modelcontextprotocol/core'; -import { JSONRPCMessageSchema } from '@modelcontextprotocol/core'; +import type { AuthInfo, JSONRPCMessage, MessageExtraInfo, Transport, TransportSendOptions } from '@modelcontextprotocol/core-internal'; +import { JSONRPCMessageSchema } from '@modelcontextprotocol/core-internal'; import contentType from 'content-type'; import getRawBody from 'raw-body'; diff --git a/packages/server-legacy/test/auth/handlers/authorize.test.ts b/packages/server-legacy/test/auth/handlers/authorize.test.ts index bd11269349..dc0e9bbf0f 100644 --- a/packages/server-legacy/test/auth/handlers/authorize.test.ts +++ b/packages/server-legacy/test/auth/handlers/authorize.test.ts @@ -1,7 +1,7 @@ import { authorizationHandler, AuthorizationHandlerOptions, redirectUriMatches } from '../../../src/auth/handlers/authorize'; import { OAuthServerProvider, AuthorizationParams } from '../../../src/auth/provider'; import { OAuthRegisteredClientsStore } from '../../../src/auth/clients'; -import { OAuthClientInformationFull, OAuthTokens } from '@modelcontextprotocol/core'; +import { OAuthClientInformationFull, OAuthTokens } from '@modelcontextprotocol/core-internal'; import express, { Response } from 'express'; import supertest from 'supertest'; import { AuthInfo } from '../../../src/auth/types'; diff --git a/packages/server-legacy/test/auth/handlers/metadata.test.ts b/packages/server-legacy/test/auth/handlers/metadata.test.ts index bf77b3badb..46f26b98d7 100644 --- a/packages/server-legacy/test/auth/handlers/metadata.test.ts +++ b/packages/server-legacy/test/auth/handlers/metadata.test.ts @@ -1,5 +1,5 @@ import { metadataHandler } from '../../../src/auth/handlers/metadata'; -import { OAuthMetadata } from '@modelcontextprotocol/core'; +import { OAuthMetadata } from '@modelcontextprotocol/core-internal'; import express from 'express'; import supertest from 'supertest'; diff --git a/packages/server-legacy/test/auth/handlers/register.test.ts b/packages/server-legacy/test/auth/handlers/register.test.ts index bcdafdc264..295515a89a 100644 --- a/packages/server-legacy/test/auth/handlers/register.test.ts +++ b/packages/server-legacy/test/auth/handlers/register.test.ts @@ -1,6 +1,6 @@ import { clientRegistrationHandler, ClientRegistrationHandlerOptions } from '../../../src/auth/handlers/register'; import { OAuthRegisteredClientsStore } from '../../../src/auth/clients'; -import { OAuthClientInformationFull, OAuthClientMetadata } from '@modelcontextprotocol/core'; +import { OAuthClientInformationFull, OAuthClientMetadata } from '@modelcontextprotocol/core-internal'; import express from 'express'; import supertest from 'supertest'; import { MockInstance } from 'vitest'; diff --git a/packages/server-legacy/test/auth/handlers/revoke.test.ts b/packages/server-legacy/test/auth/handlers/revoke.test.ts index ba3343fc74..eb4caf52f7 100644 --- a/packages/server-legacy/test/auth/handlers/revoke.test.ts +++ b/packages/server-legacy/test/auth/handlers/revoke.test.ts @@ -1,7 +1,7 @@ import { revocationHandler, RevocationHandlerOptions } from '../../../src/auth/handlers/revoke'; import { OAuthServerProvider, AuthorizationParams } from '../../../src/auth/provider'; import { OAuthRegisteredClientsStore } from '../../../src/auth/clients'; -import { OAuthClientInformationFull, OAuthTokenRevocationRequest, OAuthTokens } from '@modelcontextprotocol/core'; +import { OAuthClientInformationFull, OAuthTokenRevocationRequest, OAuthTokens } from '@modelcontextprotocol/core-internal'; import express, { Response } from 'express'; import supertest from 'supertest'; import { AuthInfo } from '../../../src/auth/types'; diff --git a/packages/server-legacy/test/auth/handlers/token.test.ts b/packages/server-legacy/test/auth/handlers/token.test.ts index 88ab66e684..ade2aca5ec 100644 --- a/packages/server-legacy/test/auth/handlers/token.test.ts +++ b/packages/server-legacy/test/auth/handlers/token.test.ts @@ -1,7 +1,7 @@ import { tokenHandler, TokenHandlerOptions } from '../../../src/auth/handlers/token'; import { OAuthServerProvider, AuthorizationParams } from '../../../src/auth/provider'; import { OAuthRegisteredClientsStore } from '../../../src/auth/clients'; -import { OAuthClientInformationFull, OAuthTokenRevocationRequest, OAuthTokens } from '@modelcontextprotocol/core'; +import { OAuthClientInformationFull, OAuthTokenRevocationRequest, OAuthTokens } from '@modelcontextprotocol/core-internal'; import express, { Response } from 'express'; import supertest from 'supertest'; import * as pkceChallenge from 'pkce-challenge'; diff --git a/packages/server-legacy/test/auth/middleware/clientAuth.test.ts b/packages/server-legacy/test/auth/middleware/clientAuth.test.ts index 82aaa23281..df21c5d205 100644 --- a/packages/server-legacy/test/auth/middleware/clientAuth.test.ts +++ b/packages/server-legacy/test/auth/middleware/clientAuth.test.ts @@ -1,6 +1,6 @@ import { authenticateClient, ClientAuthenticationMiddlewareOptions } from '../../../src/auth/middleware/clientAuth'; import { OAuthRegisteredClientsStore } from '../../../src/auth/clients'; -import { OAuthClientInformationFull } from '@modelcontextprotocol/core'; +import { OAuthClientInformationFull } from '@modelcontextprotocol/core-internal'; import express from 'express'; import supertest from 'supertest'; diff --git a/packages/server-legacy/test/auth/providers/proxyProvider.test.ts b/packages/server-legacy/test/auth/providers/proxyProvider.test.ts index ed2b2b8651..75bb3c51f3 100644 --- a/packages/server-legacy/test/auth/providers/proxyProvider.test.ts +++ b/packages/server-legacy/test/auth/providers/proxyProvider.test.ts @@ -1,7 +1,7 @@ import { Response } from 'express'; import { ProxyOAuthServerProvider, ProxyOptions } from '../../../src/auth/providers/proxyProvider'; import { AuthInfo } from '../../../src/auth/types'; -import { OAuthClientInformationFull, OAuthTokens } from '@modelcontextprotocol/core'; +import { OAuthClientInformationFull, OAuthTokens } from '@modelcontextprotocol/core-internal'; import { ServerError } from '../../../src/auth/errors'; import { InvalidTokenError } from '../../../src/auth/errors'; import { InsufficientScopeError } from '../../../src/auth/errors'; diff --git a/packages/server-legacy/test/auth/router.test.ts b/packages/server-legacy/test/auth/router.test.ts index 1e21f2cf5e..321ebfaf3f 100644 --- a/packages/server-legacy/test/auth/router.test.ts +++ b/packages/server-legacy/test/auth/router.test.ts @@ -1,7 +1,7 @@ import { mcpAuthRouter, AuthRouterOptions, mcpAuthMetadataRouter, AuthMetadataOptions } from '../../src/auth/router'; import { OAuthServerProvider, AuthorizationParams } from '../../src/auth/provider'; import { OAuthRegisteredClientsStore } from '../../src/auth/clients'; -import { OAuthClientInformationFull, OAuthMetadata, OAuthTokenRevocationRequest, OAuthTokens } from '@modelcontextprotocol/core'; +import { OAuthClientInformationFull, OAuthMetadata, OAuthTokenRevocationRequest, OAuthTokens } from '@modelcontextprotocol/core-internal'; import express, { Response } from 'express'; import supertest from 'supertest'; import { AuthInfo } from '../../src/auth/types'; diff --git a/packages/server-legacy/test/sse/sse.test.ts b/packages/server-legacy/test/sse/sse.test.ts index c9e17ea010..212ad7f958 100644 --- a/packages/server-legacy/test/sse/sse.test.ts +++ b/packages/server-legacy/test/sse/sse.test.ts @@ -2,7 +2,7 @@ import http from 'node:http'; import { type Mocked } from 'vitest'; import { SSEServerTransport } from '../../src/sse/sse'; -import type { JSONRPCMessage } from '@modelcontextprotocol/core'; +import type { JSONRPCMessage } from '@modelcontextprotocol/core-internal'; const createMockResponse = () => { const res = { diff --git a/packages/server-legacy/tsconfig.json b/packages/server-legacy/tsconfig.json index 18c1327cbc..e70d31d788 100644 --- a/packages/server-legacy/tsconfig.json +++ b/packages/server-legacy/tsconfig.json @@ -5,8 +5,8 @@ "compilerOptions": { "paths": { "*": ["./*"], - "@modelcontextprotocol/core": ["./node_modules/@modelcontextprotocol/core/src/index.ts"], - "@modelcontextprotocol/core/public": ["./node_modules/@modelcontextprotocol/core/src/exports/public/index.ts"] + "@modelcontextprotocol/core-internal": ["./node_modules/@modelcontextprotocol/core-internal/src/index.ts"], + "@modelcontextprotocol/core-internal/public": ["./node_modules/@modelcontextprotocol/core-internal/src/exports/public/index.ts"] } } } diff --git a/packages/server-legacy/tsdown.config.ts b/packages/server-legacy/tsdown.config.ts index 458d92e8a0..2fff3afcd7 100644 --- a/packages/server-legacy/tsdown.config.ts +++ b/packages/server-legacy/tsdown.config.ts @@ -15,10 +15,10 @@ export default defineConfig({ compilerOptions: { baseUrl: '.', paths: { - '@modelcontextprotocol/core': ['../core/src/index.ts'], - '@modelcontextprotocol/core/public': ['../core/src/exports/public/index.ts'] + '@modelcontextprotocol/core-internal': ['../core-internal/src/index.ts'], + '@modelcontextprotocol/core-internal/public': ['../core-internal/src/exports/public/index.ts'] } } }, - noExternal: ['@modelcontextprotocol/core'] + noExternal: ['@modelcontextprotocol/core-internal'] }); diff --git a/packages/server/CHANGELOG.md b/packages/server/CHANGELOG.md index 3f27e84e5a..a2f3e36cc8 100644 --- a/packages/server/CHANGELOG.md +++ b/packages/server/CHANGELOG.md @@ -1,5 +1,112 @@ # @modelcontextprotocol/server +## 2.0.0-alpha.3 + +### Major Changes + +- [#2128](https://github.com/modelcontextprotocol/typescript-sdk/pull/2128) [`c8d7401`](https://github.com/modelcontextprotocol/typescript-sdk/commit/c8d7401b46f34b6c49b7cfb7b321714d0d4048f6) Thanks [@felixweinberger](https://github.com/felixweinberger)! - SEP-2663: remove + 2025-11 experimental tasks (TaskManager, experimental.tasks.\* accessors). Tasks are now Extensions Track. + +### Minor Changes + +- [#1974](https://github.com/modelcontextprotocol/typescript-sdk/pull/1974) [`db83829`](https://github.com/modelcontextprotocol/typescript-sdk/commit/db83829c5bd5d6659c5e7b96638b11953b0e262d) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Add custom (non-spec) + method support: a 3-arg `setRequestHandler(method, schemas, handler)` / `setNotificationHandler(method, schemas, handler)` form for vendor-prefixed methods, and a `request(req, resultSchema)` overload (also on `ctx.mcpReq.send`) for typed custom-method results. Spec-method + calls are unchanged. + + Response result-schema validation failure now rejects with `SdkError(InvalidResult)` instead of a raw `ZodError`. Adds `SdkErrorCode.InvalidResult`. + +- [#2353](https://github.com/modelcontextprotocol/typescript-sdk/pull/2353) [`c59dc3a`](https://github.com/modelcontextprotocol/typescript-sdk/commit/c59dc3aa1a633d27fbbe873f1a430483cf7440f8) Thanks [@KKonstantinov](https://github.com/KKonstantinov)! - Support `icons` on the + high-level `McpServer.registerTool()` and `registerPrompt()` config objects (and on their `update()` methods). Tool and prompt icons now surface in `tools/list` and `prompts/list`. Resource, resource-template, and server-info (`Implementation`) icons already passed through. + This closes the gap with the MCP spec, which allows `icons` on tools, resources, resource templates, and prompts. + +- [#2354](https://github.com/modelcontextprotocol/typescript-sdk/pull/2354) [`0fb8406`](https://github.com/modelcontextprotocol/typescript-sdk/commit/0fb8406d83a3578a12a605e1b43c352d565071b1) Thanks [@KKonstantinov](https://github.com/KKonstantinov)! - Add the v2 + `IdJagTokenExchangeResponse` type to the public API and register its schema as an MCP spec type. `IdJagTokenExchangeResponse` is now exported from `@modelcontextprotocol/client` and `@modelcontextprotocol/server`, `'IdJagTokenExchangeResponse'` joins the `SpecTypeName` union, + and `isSpecType.IdJagTokenExchangeResponse(value)` / `specTypeSchemas.IdJagTokenExchangeResponse` validate it by name — matching how the OAuth/OpenID auth schemas are already exposed. The Zod schema itself, `IdJagTokenExchangeResponseSchema`, is available from + `@modelcontextprotocol/core`. + +- [#2248](https://github.com/modelcontextprotocol/typescript-sdk/pull/2248) [`db28156`](https://github.com/modelcontextprotocol/typescript-sdk/commit/db28156a23032290b3ce3bae00a17544c4807b8f) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Restore the 2025-11-25 + task wire types that were removed together with the task feature: the task schemas and inferred types, task members of the request/result/notification unions, the `task` request-params augmentation, the `tasks` capability key, the `isTaskAugmentedRequestParams` guard, and + `RELATED_TASK_META_KEY`. The task feature itself remains removed — servers do not advertise the `tasks` capability and inbound `tasks/*` requests receive `-32601` — but the wire surface stays so SDKs interoperate cleanly with peers on the 2025-11-25 revision. + +- [#1887](https://github.com/modelcontextprotocol/typescript-sdk/pull/1887) [`96db044`](https://github.com/modelcontextprotocol/typescript-sdk/commit/96db044fe965f0b7d5109e6d68598eaddce961c9) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Export `isSpecType` and + `specTypeSchemas` records for runtime validation of any MCP spec type by name. `isSpecType.ContentBlock(value)` is a type predicate; `specTypeSchemas.ContentBlock` is a `StandardSchemaV1Sync` validator — `validate()` returns the result synchronously. Guards are + standalone functions, so `arr.filter(isSpecType.ContentBlock)` works. Also export the `SpecTypeName`, `SpecTypes`, and `StandardSchemaV1Sync` types. + +- [#1871](https://github.com/modelcontextprotocol/typescript-sdk/pull/1871) [`9fc9070`](https://github.com/modelcontextprotocol/typescript-sdk/commit/9fc9070b7b8e18227127aaee9869f8809a87fdb1) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Move stdio transports + to a `./stdio` subpath export. Import `StdioClientTransport`, `getDefaultEnvironment`, `DEFAULT_INHERITED_ENV_VARS`, and `StdioServerParameters` from `@modelcontextprotocol/client/stdio`, and `StdioServerTransport` from `@modelcontextprotocol/server/stdio`. The + `@modelcontextprotocol/client` root entry no longer pulls in `node:child_process`, `node:stream`, or `cross-spawn`, fixing bundling for browser and Cloudflare Workers targets; the `@modelcontextprotocol/server` root entry drops its `node:stream` reference. Node.js, Bun, and + Deno consumers update the import path; runtime behavior is unchanged. + +### Patch Changes + +- [#2280](https://github.com/modelcontextprotocol/typescript-sdk/pull/2280) [`e8c7180`](https://github.com/modelcontextprotocol/typescript-sdk/commit/e8c7180109b417eef5cc22b7e9d821ab1c119a69) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Bound the + protocol-version checks gating SSE resumability behavior (priming events and `closeSSEStream` callbacks) in the Streamable HTTP server transport. Previously an open-ended `>= '2025-11-25'` comparison let unknown future protocol version strings from an `initialize` request body + enable this behavior; the version must now also be one of the transport's supported protocol versions. Behavior for all currently supported protocol versions is unchanged. + +- [#1897](https://github.com/modelcontextprotocol/typescript-sdk/pull/1897) [`434b2f1`](https://github.com/modelcontextprotocol/typescript-sdk/commit/434b2f11ecec452f3dca0199f68afccd8b119dd4) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Stop bundling + `@cfworker/json-schema` into the main package barrel. Previously `CfWorkerJsonSchemaValidator` was re-exported from the core internal barrel, so tsdown inlined the `@cfworker/json-schema` dependency into every consumer's bundle even when it was never used. The named validator + classes are now reachable only via the explicit `@modelcontextprotocol/{client,server}/validators/{ajv,cf-worker}` subpaths and the runtime `_shims` conditional, so consumers that import only from the root entry point no longer ship the validator dep. + +- [#2269](https://github.com/modelcontextprotocol/typescript-sdk/pull/2269) [`e84c3e9`](https://github.com/modelcontextprotocol/typescript-sdk/commit/e84c3e9ad040eb09299b1f99dd8bdd14251ae790) Thanks [@mattzcarey](https://github.com/mattzcarey)! - Non-SEP draft spec conformance + fixes + - `McpServer` now eagerly installs list/read/call handlers for every primitive capability (`tools`, `resources`, `prompts`) declared in `ServerOptions.capabilities`. Per the draft spec, a server that declares a capability MUST respond to its list method (potentially with an + empty result) instead of returning "Method not found". Previously, handlers were only installed lazily on first registration, so a server constructed with e.g. `capabilities: { tools: {} }` and zero registered tools answered `tools/list` with `-32601`. Low-level `Server` + users remain responsible for registering handlers for declared capabilities (documented on `ServerOptions.capabilities`). + - Fixed pagination doc examples on `Client.listTools`/`listPrompts`/`listResources` to loop `while (cursor !== undefined)` instead of `while (cursor)` — per the draft spec, clients MUST NOT treat an empty-string cursor as the end of results. + +- [#1834](https://github.com/modelcontextprotocol/typescript-sdk/pull/1834) [`42cb6b2`](https://github.com/modelcontextprotocol/typescript-sdk/commit/42cb6b2b728347d8b58a0d1940b7e63366a29ab9) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Export + `InMemoryTransport` for in-process testing. + +- [#1788](https://github.com/modelcontextprotocol/typescript-sdk/pull/1788) [`df4b6cc`](https://github.com/modelcontextprotocol/typescript-sdk/commit/df4b6cc88d6f24fc857519cf506a7a039f532637) Thanks [@claygeo](https://github.com/claygeo)! - Prevent stack overflow in + StreamableHTTPServerTransport.close() with re-entrant guard + +- [#1855](https://github.com/modelcontextprotocol/typescript-sdk/pull/1855) [`2c0c481`](https://github.com/modelcontextprotocol/typescript-sdk/commit/2c0c481cb9dbfd15c8613f765c940a5f5bace94d) Thanks [@Christian-Sidak](https://github.com/Christian-Sidak)! - Add `| undefined` to + optional callback and function properties on `WebStandardStreamableHTTPServerTransportOptions` (`sessionIdGenerator`, `onsessioninitialized`, `onsessionclosed`) and corresponding private fields. + + This fixes TS2430 errors for consumers using `exactOptionalPropertyTypes: true` without `skipLibCheck`, where optional properties with function types need explicit `| undefined` to match their emitted declarations. + +- [#1898](https://github.com/modelcontextprotocol/typescript-sdk/pull/1898) [`2a7611d`](https://github.com/modelcontextprotocol/typescript-sdk/commit/2a7611d46b0d4f4e7bd4147c7a3ad3da00e57e52) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Add top-level `types` + field (and `typesVersions` on client/server for their subpath exports) so consumers on legacy `moduleResolution: "node"` can resolve type declarations. The `exports` map remains the source of truth for `nodenext`/`bundler` resolution. The `typesVersions` map includes entries + for subpaths added by sibling PRs in this series (`zod-schemas`, `stdio`); those entries are no-ops until the corresponding `dist/*.d.mts` files exist. + +- [#1901](https://github.com/modelcontextprotocol/typescript-sdk/pull/1901) [`e15a8ef`](https://github.com/modelcontextprotocol/typescript-sdk/commit/e15a8ef3be19520d8159ae9f5b464ba3ac80a5ab) Thanks [@felixweinberger](https://github.com/felixweinberger)! - + `registerTool`/`registerPrompt` accept a raw Zod shape (`{ field: z.string() }`) for `inputSchema`/`outputSchema`/`argsSchema` in addition to a wrapped Standard Schema. Raw shapes are auto-wrapped with `z.object()`. The raw-shape overloads are `@deprecated`; prefer wrapping + with `z.object()`. + + Also widens the `completable()` constraint from `StandardSchemaWithJSON` to `StandardSchemaV1` so v1's `completable(z.string(), fn)` continues to work. + +- [#2268](https://github.com/modelcontextprotocol/typescript-sdk/pull/2268) [`49c0a71`](https://github.com/modelcontextprotocol/typescript-sdk/commit/49c0a711c8bf2d385f9e03b4f28ba0ff0d0db0bd) Thanks [@mattzcarey](https://github.com/mattzcarey)! - Mark the roots, sampling, and + logging runtime APIs as `@deprecated` per SEP-2577 (deprecated as of protocol version 2026-07-28; functional for at least twelve months). Annotates `Server.createMessage`/`listRoots`/`sendLoggingMessage`, `McpServer.sendLoggingMessage`, + `Client.setLoggingLevel`/`sendRootsListChanged`, the `ServerContext.mcpReq.log`/`requestSampling` helpers, and the `roots`/`sampling`/`logging` capability schema fields. JSDoc/docs only — no behavior change. + +- [#2275](https://github.com/modelcontextprotocol/typescript-sdk/pull/2275) [`1b53a41`](https://github.com/modelcontextprotocol/typescript-sdk/commit/1b53a415ea2c33aa11ac413fc9c2d68ccffde784) Thanks [@KKonstantinov](https://github.com/KKonstantinov)! - Add a configurable + `maxBufferSize` (default 10 MB) to the stdio transports. When a single message would push the read buffer past the limit, the transport now emits an `onerror` and closes instead of growing the buffer unbounded. Configure via `new StdioClientTransport({ ..., maxBufferSize })` or + `new StdioServerTransport(stdin, stdout, { maxBufferSize })`. The default is exported from `@modelcontextprotocol/core-internal` as `STDIO_DEFAULT_MAX_BUFFER_SIZE`. + +- [#2088](https://github.com/modelcontextprotocol/typescript-sdk/pull/2088) [`16d13ab`](https://github.com/modelcontextprotocol/typescript-sdk/commit/16d13abf78b5dba5de73dfa284325b13d4219bb2) Thanks [@mattzcarey](https://github.com/mattzcarey)! - Bundle automatic JSON Schema + validator defaults in `@modelcontextprotocol/client` and `@modelcontextprotocol/server` runtime shims. + + Client and server pick the right validator automatically based on the runtime: the Node shim uses AJV, the browser/workerd shim uses `@cfworker/json-schema`. Both backends are bundled into the shim chunks that select them, so the default code path needs no extra installs — + `import { McpServer } from '@modelcontextprotocol/server'` does not pull `ajv` or `@cfworker/json-schema` into the root entry chunk. + + The named validator classes remain part of the public surface for consumers who want to customize the built-in backend (pre-register schemas by `$id`, register custom AJV formats, switch dialects, change `@cfworker/json-schema` draft). They are exposed through explicit + subpaths so they do not bloat the root index chunk: + - `import { AjvJsonSchemaValidator } from '@modelcontextprotocol/{client,server}/validators/ajv'` + - `import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/{client,server}/validators/cf-worker'` + + Importing from one of these subpaths means the corresponding peer dep (`ajv` + `ajv-formats`, or `@cfworker/json-schema`) must be in your `package.json`. The shim keeps its own vendored copy for the default path, so a project can use the subpath in some files and rely on the + default in others. + + The `jsonSchemaValidator` interface remains the public extension point for replacing validation entirely with a custom implementation. + +- [#1976](https://github.com/modelcontextprotocol/typescript-sdk/pull/1976) [`55b1f06`](https://github.com/modelcontextprotocol/typescript-sdk/commit/55b1f06cd4569e334f3435b7971f0446f1ef9be9) Thanks [@felixweinberger](https://github.com/felixweinberger)! - refactor: subclasses + override `_wrapHandler` hook instead of redeclaring `setRequestHandler`. + +- [#1895](https://github.com/modelcontextprotocol/typescript-sdk/pull/1895) [`b256546`](https://github.com/modelcontextprotocol/typescript-sdk/commit/b256546750277faeb7c886792aae5ed26e6904d5) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Fix runtime crash on + `tools/list` when a tool's `inputSchema` comes from zod 4.0–4.1. The SDK requires `~standard.jsonSchema` (StandardJSONSchemaV1, added in zod 4.2.0); previously a missing `jsonSchema` crashed at `undefined[io]`. `standardSchemaToJsonSchema` now detects zod 4 schemas lacking + `jsonSchema` and falls back to the SDK-bundled `z.toJSONSchema()`, emitting a one-time console warning. zod 3 schemas (which the bundled zod 4 converter cannot introspect) and non-zod schema libraries without `jsonSchema` get a clear error pointing to `fromJsonSchema()`. The + workspace zod catalog is also bumped to `^4.2.0`. + ## 2.0.0-alpha.2 ### Patch Changes @@ -51,7 +158,7 @@ For raw JSON Schema (e.g. TypeBox output), use the new `fromJsonSchema` adapter: ```typescript - import { fromJsonSchema, AjvJsonSchemaValidator } from '@modelcontextprotocol/core'; + import { fromJsonSchema, AjvJsonSchemaValidator } from '@modelcontextprotocol/core-internal'; server.registerTool( 'greet', @@ -64,7 +171,8 @@ **Breaking changes:** - `experimental.tasks.getTaskResult()` no longer accepts a `resultSchema` parameter. Returns `GetTaskPayloadResult` (a loose `Result`); cast to the expected type at the call site. - - Removed unused exports from `@modelcontextprotocol/core`: `SchemaInput`, `schemaToJson`, `parseSchemaAsync`, `getSchemaShape`, `getSchemaDescription`, `isOptionalSchema`, `unwrapOptionalSchema`. Use the new `standardSchemaToJsonSchema` and `validateStandardSchema` instead. + - Removed unused exports from `@modelcontextprotocol/core-internal`: `SchemaInput`, `schemaToJson`, `parseSchemaAsync`, `getSchemaShape`, `getSchemaDescription`, `isOptionalSchema`, `unwrapOptionalSchema`. Use the new `standardSchemaToJsonSchema` and `validateStandardSchema` + instead. - `completable()` remains Zod-specific (it relies on Zod's `.shape` introspection). ### Patch Changes diff --git a/packages/server/eslint.config.mjs b/packages/server/eslint.config.mjs index 4f034f2235..dd9e88588a 100644 --- a/packages/server/eslint.config.mjs +++ b/packages/server/eslint.config.mjs @@ -6,7 +6,7 @@ export default [ ...baseConfig, { settings: { - 'import/internal-regex': '^@modelcontextprotocol/core' + 'import/internal-regex': '^@modelcontextprotocol/core-internal' } } ]; diff --git a/packages/server/package.json b/packages/server/package.json index 4a058cbd7b..76bbb7364e 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/server", - "version": "2.0.0-alpha.2", + "version": "2.0.0-alpha.3", "description": "Model Context Protocol implementation for TypeScript - Server package", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -94,7 +94,7 @@ "ajv": "catalog:runtimeShared", "ajv-formats": "catalog:runtimeShared", "@eslint/js": "catalog:devTools", - "@modelcontextprotocol/core": "workspace:^", + "@modelcontextprotocol/core-internal": "workspace:^", "@modelcontextprotocol/eslint-config": "workspace:^", "@modelcontextprotocol/test-helpers": "workspace:^", "@modelcontextprotocol/tsconfig": "workspace:^", diff --git a/packages/server/src/fromJsonSchema.ts b/packages/server/src/fromJsonSchema.ts index 180ef2defc..c2db3fda44 100644 --- a/packages/server/src/fromJsonSchema.ts +++ b/packages/server/src/fromJsonSchema.ts @@ -1,5 +1,5 @@ -import type { JsonSchemaType, jsonSchemaValidator, StandardSchemaWithJSON } from '@modelcontextprotocol/core'; -import { fromJsonSchema as coreFromJsonSchema } from '@modelcontextprotocol/core'; +import type { JsonSchemaType, jsonSchemaValidator, StandardSchemaWithJSON } from '@modelcontextprotocol/core-internal'; +import { fromJsonSchema as coreFromJsonSchema } from '@modelcontextprotocol/core-internal'; import { DefaultJsonSchemaValidator } from '@modelcontextprotocol/server/_shims'; let _defaultValidator: jsonSchemaValidator | undefined; diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index d74972e140..292c35abad 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -2,7 +2,7 @@ // // This file defines the complete public surface. It consists of: // - Package-specific exports: listed explicitly below (named imports) -// - Protocol-level types: re-exported from @modelcontextprotocol/core/public +// - Protocol-level types: re-exported from @modelcontextprotocol/core-internal/public // // Any new export added here becomes public API. Use named exports, not wildcards. @@ -44,4 +44,4 @@ export { WebStandardStreamableHTTPServerTransport } from './server/streamableHtt export { fromJsonSchema } from './fromJsonSchema'; // re-export curated public API from core -export * from '@modelcontextprotocol/core/public'; +export * from '@modelcontextprotocol/core-internal/public'; diff --git a/packages/server/src/server/completable.ts b/packages/server/src/server/completable.ts index 82300f7df1..b8ab7b6b68 100644 --- a/packages/server/src/server/completable.ts +++ b/packages/server/src/server/completable.ts @@ -1,4 +1,4 @@ -import type { StandardSchemaV1 } from '@modelcontextprotocol/core'; +import type { StandardSchemaV1 } from '@modelcontextprotocol/core-internal'; export const COMPLETABLE_SYMBOL: unique symbol = Symbol.for('mcp.completable'); diff --git a/packages/server/src/server/mcp.examples.ts b/packages/server/src/server/mcp.examples.ts index 7c30582e81..c4abb9ccb7 100644 --- a/packages/server/src/server/mcp.examples.ts +++ b/packages/server/src/server/mcp.examples.ts @@ -7,7 +7,7 @@ * @module */ -import type { CallToolResult } from '@modelcontextprotocol/core'; +import type { CallToolResult } from '@modelcontextprotocol/core-internal'; import * as z from 'zod/v4'; import { McpServer } from './mcp'; diff --git a/packages/server/src/server/mcp.ts b/packages/server/src/server/mcp.ts index b93d92d589..be18a7c7cd 100644 --- a/packages/server/src/server/mcp.ts +++ b/packages/server/src/server/mcp.ts @@ -24,7 +24,7 @@ import type { ToolExecution, Transport, Variables -} from '@modelcontextprotocol/core'; +} from '@modelcontextprotocol/core-internal'; import { assertCompleteRequestPrompt, assertCompleteRequestResourceTemplate, @@ -36,7 +36,7 @@ import { UriTemplate, validateAndWarnToolName, validateStandardSchema -} from '@modelcontextprotocol/core'; +} from '@modelcontextprotocol/core-internal'; import type * as z from 'zod/v4'; import { getCompleter, isCompletable } from './completable'; diff --git a/packages/server/src/server/server.ts b/packages/server/src/server/server.ts index f96d8ec1bc..b0777d118b 100644 --- a/packages/server/src/server/server.ts +++ b/packages/server/src/server/server.ts @@ -30,7 +30,7 @@ import type { ServerContext, ToolResultContent, ToolUseContent -} from '@modelcontextprotocol/core'; +} from '@modelcontextprotocol/core-internal'; import { CallToolRequestSchema, CallToolResultSchema, @@ -48,7 +48,7 @@ import { ProtocolErrorCode, SdkError, SdkErrorCode -} from '@modelcontextprotocol/core'; +} from '@modelcontextprotocol/core-internal'; import { DefaultJsonSchemaValidator } from '@modelcontextprotocol/server/_shims'; export type ServerOptions = ProtocolOptions & { diff --git a/packages/server/src/server/stdio.ts b/packages/server/src/server/stdio.ts index a790fc75a4..e8fda03257 100644 --- a/packages/server/src/server/stdio.ts +++ b/packages/server/src/server/stdio.ts @@ -1,7 +1,7 @@ import type { Readable, Writable } from 'node:stream'; -import type { JSONRPCMessage, Transport } from '@modelcontextprotocol/core'; -import { ReadBuffer, serializeMessage } from '@modelcontextprotocol/core'; +import type { JSONRPCMessage, Transport } from '@modelcontextprotocol/core-internal'; +import { ReadBuffer, serializeMessage } from '@modelcontextprotocol/core-internal'; import { process } from '@modelcontextprotocol/server/_shims'; /** diff --git a/packages/server/src/server/streamableHttp.ts b/packages/server/src/server/streamableHttp.ts index fb6ffc2b02..9b6c4d9555 100644 --- a/packages/server/src/server/streamableHttp.ts +++ b/packages/server/src/server/streamableHttp.ts @@ -7,7 +7,7 @@ * For Node.js Express/HTTP compatibility, use {@linkcode @modelcontextprotocol/node!NodeStreamableHTTPServerTransport | NodeStreamableHTTPServerTransport} which wraps this transport. */ -import type { AuthInfo, JSONRPCMessage, MessageExtraInfo, RequestId, Transport } from '@modelcontextprotocol/core'; +import type { AuthInfo, JSONRPCMessage, MessageExtraInfo, RequestId, Transport } from '@modelcontextprotocol/core-internal'; import { DEFAULT_NEGOTIATED_PROTOCOL_VERSION, isInitializeRequest, @@ -16,7 +16,7 @@ import { isJSONRPCResultResponse, JSONRPCMessageSchema, SUPPORTED_PROTOCOL_VERSIONS -} from '@modelcontextprotocol/core'; +} from '@modelcontextprotocol/core-internal'; export type StreamId = string; export type EventId = string; diff --git a/packages/server/src/shimsNode.ts b/packages/server/src/shimsNode.ts index 9354850b6e..6ec703659a 100644 --- a/packages/server/src/shimsNode.ts +++ b/packages/server/src/shimsNode.ts @@ -3,5 +3,5 @@ * * This file is selected via package.json export conditions when running in Node.js. */ -export { AjvJsonSchemaValidator as DefaultJsonSchemaValidator } from '@modelcontextprotocol/core/validators/ajv'; +export { AjvJsonSchemaValidator as DefaultJsonSchemaValidator } from '@modelcontextprotocol/core-internal/validators/ajv'; export { default as process } from 'node:process'; diff --git a/packages/server/src/shimsWorkerd.ts b/packages/server/src/shimsWorkerd.ts index dc23da8080..92e3bf0f17 100644 --- a/packages/server/src/shimsWorkerd.ts +++ b/packages/server/src/shimsWorkerd.ts @@ -3,7 +3,7 @@ * * This file is selected via package.json export conditions when running in workerd. */ -export { CfWorkerJsonSchemaValidator as DefaultJsonSchemaValidator } from '@modelcontextprotocol/core/validators/cfWorker'; +export { CfWorkerJsonSchemaValidator as DefaultJsonSchemaValidator } from '@modelcontextprotocol/core-internal/validators/cfWorker'; /** * Stub process object for non-Node.js environments. diff --git a/packages/server/src/validators/ajv.ts b/packages/server/src/validators/ajv.ts index 31f0fed3b3..8bd1a78a8d 100644 --- a/packages/server/src/validators/ajv.ts +++ b/packages/server/src/validators/ajv.ts @@ -11,4 +11,4 @@ * const validator = new AjvJsonSchemaValidator(ajv); * ``` */ -export { addFormats, Ajv, AjvJsonSchemaValidator } from '@modelcontextprotocol/core/validators/ajv'; +export { addFormats, Ajv, AjvJsonSchemaValidator } from '@modelcontextprotocol/core-internal/validators/ajv'; diff --git a/packages/server/src/validators/cfWorker.ts b/packages/server/src/validators/cfWorker.ts index 2969b4dc9d..f1d9379afc 100644 --- a/packages/server/src/validators/cfWorker.ts +++ b/packages/server/src/validators/cfWorker.ts @@ -1,3 +1,3 @@ /** Customisation entry point for the `@cfworker/json-schema` validator. */ -export type { CfWorkerSchemaDraft } from '@modelcontextprotocol/core/validators/cfWorker'; -export { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/core/validators/cfWorker'; +export type { CfWorkerSchemaDraft } from '@modelcontextprotocol/core-internal/validators/cfWorker'; +export { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/core-internal/validators/cfWorker'; diff --git a/packages/server/test/server/jsonSchemaValidatorOverride.test.ts b/packages/server/test/server/jsonSchemaValidatorOverride.test.ts index 2931f8aa00..e5edf18398 100644 --- a/packages/server/test/server/jsonSchemaValidatorOverride.test.ts +++ b/packages/server/test/server/jsonSchemaValidatorOverride.test.ts @@ -1,5 +1,5 @@ -import type { JsonSchemaType, JsonSchemaValidatorResult, jsonSchemaValidator } from '@modelcontextprotocol/core'; -import { InMemoryTransport, LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/core'; +import type { JsonSchemaType, JsonSchemaValidatorResult, jsonSchemaValidator } from '@modelcontextprotocol/core-internal'; +import { InMemoryTransport, LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/core-internal'; import { fromJsonSchema } from '../../src/fromJsonSchema'; import { Server } from '../../src/server/server'; diff --git a/packages/server/test/server/mcp.compat.test.ts b/packages/server/test/server/mcp.compat.test.ts index 2b6e960bb4..6c87e25f12 100644 --- a/packages/server/test/server/mcp.compat.test.ts +++ b/packages/server/test/server/mcp.compat.test.ts @@ -1,5 +1,5 @@ -import type { JSONRPCMessage } from '@modelcontextprotocol/core'; -import { InMemoryTransport, isStandardSchema, LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/core'; +import type { JSONRPCMessage } from '@modelcontextprotocol/core-internal'; +import { InMemoryTransport, isStandardSchema, LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/core-internal'; import { describe, expect, expectTypeOf, it, vi } from 'vitest'; import * as z from 'zod/v4'; import { McpServer } from '../../src/index'; diff --git a/packages/server/test/server/mcp.icons.test.ts b/packages/server/test/server/mcp.icons.test.ts index cbd3f15483..168a6977c0 100644 --- a/packages/server/test/server/mcp.icons.test.ts +++ b/packages/server/test/server/mcp.icons.test.ts @@ -1,5 +1,5 @@ -import type { Icon, JSONRPCMessage } from '@modelcontextprotocol/core'; -import { InMemoryTransport, LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/core'; +import type { Icon, JSONRPCMessage } from '@modelcontextprotocol/core-internal'; +import { InMemoryTransport, LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/core-internal'; import { describe, expect, it, vi } from 'vitest'; import { McpServer, ResourceTemplate } from '../../src/index'; diff --git a/packages/server/test/server/server.test.ts b/packages/server/test/server/server.test.ts index 1929197b19..8de735d73a 100644 --- a/packages/server/test/server/server.test.ts +++ b/packages/server/test/server/server.test.ts @@ -1,11 +1,11 @@ -import type { JSONRPCMessage, JSONRPCRequest } from '@modelcontextprotocol/core'; +import type { JSONRPCMessage, JSONRPCRequest } from '@modelcontextprotocol/core-internal'; import { InitializeResultSchema, InMemoryTransport, isJSONRPCResultResponse, LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS -} from '@modelcontextprotocol/core'; +} from '@modelcontextprotocol/core-internal'; import { Server } from '../../src/server/server'; /** An older protocol version the server supports out of the box. */ diff --git a/packages/server/test/server/stdio.test.ts b/packages/server/test/server/stdio.test.ts index e21143aea5..fe79e3679c 100644 --- a/packages/server/test/server/stdio.test.ts +++ b/packages/server/test/server/stdio.test.ts @@ -1,7 +1,7 @@ import { Readable, Writable } from 'node:stream'; -import type { JSONRPCMessage } from '@modelcontextprotocol/core'; -import { ReadBuffer, serializeMessage } from '@modelcontextprotocol/core'; +import type { JSONRPCMessage } from '@modelcontextprotocol/core-internal'; +import { ReadBuffer, serializeMessage } from '@modelcontextprotocol/core-internal'; import { StdioServerTransport } from '../../src/server/stdio'; diff --git a/packages/server/test/server/streamableHttp.test.ts b/packages/server/test/server/streamableHttp.test.ts index 0fc61ce32a..b9bbbd8f9f 100644 --- a/packages/server/test/server/streamableHttp.test.ts +++ b/packages/server/test/server/streamableHttp.test.ts @@ -1,6 +1,6 @@ import { randomUUID } from 'node:crypto'; -import type { CallToolResult, JSONRPCErrorResponse, JSONRPCMessage } from '@modelcontextprotocol/core'; +import type { CallToolResult, JSONRPCErrorResponse, JSONRPCMessage } from '@modelcontextprotocol/core-internal'; import * as z from 'zod/v4'; import { McpServer } from '../../src/server/mcp'; diff --git a/packages/server/test/server/streamableHttpFutureVersionGates.test.ts b/packages/server/test/server/streamableHttpFutureVersionGates.test.ts index def61b5223..4abf9fb74d 100644 --- a/packages/server/test/server/streamableHttpFutureVersionGates.test.ts +++ b/packages/server/test/server/streamableHttpFutureVersionGates.test.ts @@ -1,6 +1,6 @@ import { randomUUID } from 'node:crypto'; -import type { JSONRPCMessage, MessageExtraInfo } from '@modelcontextprotocol/core'; +import type { JSONRPCMessage, MessageExtraInfo } from '@modelcontextprotocol/core-internal'; import { McpServer } from '../../src/server/mcp'; import type { EventId, EventStore, StreamId } from '../../src/server/streamableHttp'; diff --git a/packages/server/test/server/streamableHttpUnsupportedVersionLiteral.test.ts b/packages/server/test/server/streamableHttpUnsupportedVersionLiteral.test.ts index b098af1fea..d44cc642dc 100644 --- a/packages/server/test/server/streamableHttpUnsupportedVersionLiteral.test.ts +++ b/packages/server/test/server/streamableHttpUnsupportedVersionLiteral.test.ts @@ -1,7 +1,7 @@ import { randomUUID } from 'node:crypto'; -import type { JSONRPCMessage } from '@modelcontextprotocol/core'; -import { SUPPORTED_PROTOCOL_VERSIONS } from '@modelcontextprotocol/core'; +import type { JSONRPCMessage } from '@modelcontextprotocol/core-internal'; +import { SUPPORTED_PROTOCOL_VERSIONS } from '@modelcontextprotocol/core-internal'; import { McpServer } from '../../src/server/mcp'; import { WebStandardStreamableHTTPServerTransport } from '../../src/server/streamableHttp'; diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index 24da6e426d..2d8ef8ed15 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -5,11 +5,15 @@ "compilerOptions": { "paths": { "*": ["./*"], - "@modelcontextprotocol/core": ["./node_modules/@modelcontextprotocol/core/src/index.ts"], - "@modelcontextprotocol/core/public": ["./node_modules/@modelcontextprotocol/core/src/exports/public/index.ts"], - "@modelcontextprotocol/core/validators/ajv": ["./node_modules/@modelcontextprotocol/core/src/validators/ajvProvider.ts"], - "@modelcontextprotocol/core/validators/cfWorker": [ - "./node_modules/@modelcontextprotocol/core/src/validators/cfWorkerProvider.ts" + "@modelcontextprotocol/core-internal": ["./node_modules/@modelcontextprotocol/core-internal/src/index.ts"], + "@modelcontextprotocol/core-internal/public": [ + "./node_modules/@modelcontextprotocol/core-internal/src/exports/public/index.ts" + ], + "@modelcontextprotocol/core-internal/validators/ajv": [ + "./node_modules/@modelcontextprotocol/core-internal/src/validators/ajvProvider.ts" + ], + "@modelcontextprotocol/core-internal/validators/cfWorker": [ + "./node_modules/@modelcontextprotocol/core-internal/src/validators/cfWorkerProvider.ts" ], "@modelcontextprotocol/test-helpers": ["./node_modules/@modelcontextprotocol/test-helpers/src/index.ts"], "@modelcontextprotocol/server/_shims": ["./src/shimsNode.ts"] diff --git a/packages/server/tsdown.config.ts b/packages/server/tsdown.config.ts index 891ce49641..9908a6e730 100644 --- a/packages/server/tsdown.config.ts +++ b/packages/server/tsdown.config.ts @@ -23,13 +23,13 @@ export default defineConfig({ compilerOptions: { baseUrl: '.', paths: { - '@modelcontextprotocol/core': ['../core/src/index.ts'], - '@modelcontextprotocol/core/public': ['../core/src/exports/public/index.ts'], - '@modelcontextprotocol/core/validators/ajv': ['../core/src/validators/ajvProvider.ts'], - '@modelcontextprotocol/core/validators/cfWorker': ['../core/src/validators/cfWorkerProvider.ts'] + '@modelcontextprotocol/core-internal': ['../core-internal/src/index.ts'], + '@modelcontextprotocol/core-internal/public': ['../core-internal/src/exports/public/index.ts'], + '@modelcontextprotocol/core-internal/validators/ajv': ['../core-internal/src/validators/ajvProvider.ts'], + '@modelcontextprotocol/core-internal/validators/cfWorker': ['../core-internal/src/validators/cfWorkerProvider.ts'] } } }, - noExternal: ['@modelcontextprotocol/core', 'ajv', 'ajv-formats', '@cfworker/json-schema'], + noExternal: ['@modelcontextprotocol/core-internal', 'ajv', 'ajv-formats', '@cfworker/json-schema'], external: ['@modelcontextprotocol/server/_shims'] }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8fbb1208bc..0a971a2ebd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -257,7 +257,7 @@ importers: version: 4.4.4(eslint-plugin-import@2.32.0)(eslint@9.39.4) eslint-plugin-import: specifier: ^2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.4) + version: 2.32.0(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.4) eslint-plugin-n: specifier: catalog:devTools version: 17.24.0(eslint@9.39.4)(typescript@5.9.3) @@ -428,9 +428,9 @@ importers: examples/shared: dependencies: - '@modelcontextprotocol/core': + '@modelcontextprotocol/core-internal': specifier: workspace:^ - version: link:../../packages/core + version: link:../../packages/core-internal '@modelcontextprotocol/express': specifier: workspace:^ version: link:../../packages/middleware/express @@ -526,9 +526,9 @@ importers: '@eslint/js': specifier: catalog:devTools version: 9.39.4 - '@modelcontextprotocol/core': + '@modelcontextprotocol/core-internal': specifier: workspace:^ - version: link:../core + version: link:../core-internal '@modelcontextprotocol/eslint-config': specifier: workspace:^ version: link:../../common/eslint-config @@ -637,6 +637,55 @@ importers: version: 4.1.2(@opentelemetry/api@1.9.1)(@types/node@25.5.0)(vite@7.3.0(@types/node@25.5.0)(tsx@4.21.0)(yaml@2.8.3)) packages/core: + dependencies: + zod: + specifier: catalog:runtimeShared + version: 4.3.6 + devDependencies: + '@eslint/js': + specifier: catalog:devTools + version: 9.39.4 + '@modelcontextprotocol/core-internal': + specifier: workspace:^ + version: link:../core-internal + '@modelcontextprotocol/eslint-config': + specifier: workspace:^ + version: link:../../common/eslint-config + '@modelcontextprotocol/tsconfig': + specifier: workspace:^ + version: link:../../common/tsconfig + '@modelcontextprotocol/vitest-config': + specifier: workspace:^ + version: link:../../common/vitest-config + '@typescript/native-preview': + specifier: catalog:devTools + version: 7.0.0-dev.20260327.2 + eslint: + specifier: catalog:devTools + version: 9.39.4 + eslint-config-prettier: + specifier: catalog:devTools + version: 10.1.8(eslint@9.39.4) + eslint-plugin-n: + specifier: catalog:devTools + version: 17.24.0(eslint@9.39.4)(typescript@5.9.3) + prettier: + specifier: catalog:devTools + version: 3.6.2 + tsdown: + specifier: catalog:devTools + version: 0.18.4(@typescript/native-preview@7.0.0-dev.20260327.2)(typescript@5.9.3) + typescript: + specifier: catalog:devTools + version: 5.9.3 + typescript-eslint: + specifier: catalog:devTools + version: 8.57.2(eslint@9.39.4)(typescript@5.9.3) + vitest: + specifier: catalog:devTools + version: 4.1.2(@opentelemetry/api@1.9.1)(@types/node@25.5.0)(vite@7.3.0(@types/node@25.5.0)(tsx@4.21.0)(yaml@2.8.3)) + + packages/core-internal: dependencies: json-schema-typed: specifier: catalog:runtimeShared @@ -886,9 +935,9 @@ importers: '@eslint/js': specifier: catalog:devTools version: 9.39.4 - '@modelcontextprotocol/core': + '@modelcontextprotocol/core-internal': specifier: workspace:^ - version: link:../../core + version: link:../../core-internal '@modelcontextprotocol/eslint-config': specifier: workspace:^ version: link:../../../common/eslint-config @@ -944,9 +993,9 @@ importers: '@eslint/js': specifier: catalog:devTools version: 9.39.4 - '@modelcontextprotocol/core': + '@modelcontextprotocol/core-internal': specifier: workspace:^ - version: link:../core + version: link:../core-internal '@modelcontextprotocol/eslint-config': specifier: workspace:^ version: link:../../common/eslint-config @@ -1026,9 +1075,9 @@ importers: '@eslint/js': specifier: catalog:devTools version: 9.39.4 - '@modelcontextprotocol/core': + '@modelcontextprotocol/core-internal': specifier: workspace:^ - version: link:../core + version: link:../core-internal '@modelcontextprotocol/eslint-config': specifier: workspace:^ version: link:../../common/eslint-config @@ -1092,9 +1141,9 @@ importers: '@modelcontextprotocol/conformance': specifier: 0.2.0-alpha.3 version: 0.2.0-alpha.3(@cfworker/json-schema@4.1.1) - '@modelcontextprotocol/core': + '@modelcontextprotocol/core-internal': specifier: workspace:^ - version: link:../../packages/core + version: link:../../packages/core-internal '@modelcontextprotocol/eslint-config': specifier: workspace:^ version: link:../../common/eslint-config @@ -1137,9 +1186,9 @@ importers: '@modelcontextprotocol/client': specifier: workspace:^ version: link:../../packages/client - '@modelcontextprotocol/core': + '@modelcontextprotocol/core-internal': specifier: workspace:^ - version: link:../../packages/core + version: link:../../packages/core-internal '@modelcontextprotocol/eslint-config': specifier: workspace:^ version: link:../../common/eslint-config @@ -1215,9 +1264,9 @@ importers: test/helpers: devDependencies: - '@modelcontextprotocol/core': + '@modelcontextprotocol/core-internal': specifier: workspace:^ - version: link:../../packages/core + version: link:../../packages/core-internal '@modelcontextprotocol/eslint-config': specifier: workspace:^ version: link:../../common/eslint-config @@ -1242,9 +1291,9 @@ importers: '@modelcontextprotocol/client': specifier: workspace:^ version: link:../../packages/client - '@modelcontextprotocol/core': + '@modelcontextprotocol/core-internal': specifier: workspace:^ - version: link:../../packages/core + version: link:../../packages/core-internal '@modelcontextprotocol/eslint-config': specifier: workspace:^ version: link:../../common/eslint-config @@ -7343,15 +7392,14 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.4) + eslint-plugin-import: 2.32.0(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.4) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.4): + eslint-module-utils@2.12.1(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.4): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.57.2(eslint@9.39.4)(typescript@5.9.3) eslint: 9.39.4 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 4.4.4(eslint-plugin-import@2.32.0)(eslint@9.39.4) @@ -7365,7 +7413,7 @@ snapshots: eslint: 9.39.4 eslint-compat-utils: 0.5.1(eslint@9.39.4) - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.4): + eslint-plugin-import@2.32.0(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.4): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -7376,7 +7424,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.4 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.4) + eslint-module-utils: 2.12.1(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.4) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -7387,8 +7435,6 @@ snapshots: semver: 6.3.1 string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 8.57.2(eslint@9.39.4)(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack diff --git a/scripts/fetch-spec-types.ts b/scripts/fetch-spec-types.ts index b0db8d486f..568cd34263 100644 --- a/scripts/fetch-spec-types.ts +++ b/scripts/fetch-spec-types.ts @@ -12,7 +12,7 @@ const PROJECT_ROOT = join(__dirname, '..'); * - `2025-11-25`: the frozen, released schema. * - `2026-07-28`: the upcoming protocol revision. * - * Each is written to `packages/core/src/types/spec.types..ts`. + * Each is written to `packages/core-internal/src/types/spec.types..ts`. */ const SUPPORTED_VERSIONS = ['2025-11-25', '2026-07-28'] as const; type SpecVersion = (typeof SUPPORTED_VERSIONS)[number]; @@ -90,13 +90,13 @@ async function updateSpecTypes(version: SpecVersion, providedSHA?: string): Prom const fullContent = header + specContent; // Format with prettier using the project's config so the output passes lint - const outputPath = join(PROJECT_ROOT, 'packages', 'core', 'src', 'types', `spec.types.${version}.ts`); + const outputPath = join(PROJECT_ROOT, 'packages', 'core-internal', 'src', 'types', `spec.types.${version}.ts`); const prettierConfig = await prettier.resolveConfig(outputPath); const formatted = await prettier.format(fullContent, { ...prettierConfig, filepath: outputPath }); writeFileSync(outputPath, formatted, 'utf-8'); - console.log(`[${version}] Successfully updated packages/core/src/types/spec.types.${version}.ts`); + console.log(`[${version}] Successfully updated packages/core-internal/src/types/spec.types.${version}.ts`); } function isSupportedVersion(value: string): value is SpecVersion { diff --git a/scripts/sync-snippets.ts b/scripts/sync-snippets.ts index 21a2c4e70b..6d5d81410c 100644 --- a/scripts/sync-snippets.ts +++ b/scripts/sync-snippets.ts @@ -516,7 +516,7 @@ function findPackageSrcDirs(packagesDir: string): string[] { const fullPath = join(entry.parentPath, entry.name); // Only include src dirs that are direct children of a package - // (e.g., packages/core/src, packages/middleware/express/src) + // (e.g., packages/core-internal/src, packages/middleware/express/src) // Skip nested src dirs like node_modules/*/src if (fullPath.includes('node_modules')) continue; diff --git a/test/conformance/CHANGELOG.md b/test/conformance/CHANGELOG.md new file mode 100644 index 0000000000..1c9ea88fd3 --- /dev/null +++ b/test/conformance/CHANGELOG.md @@ -0,0 +1,10 @@ +# @modelcontextprotocol/test-conformance + +## 2.0.0-alpha.1 + +### Patch Changes + +- [#2276](https://github.com/modelcontextprotocol/typescript-sdk/pull/2276) [`0657c3b`](https://github.com/modelcontextprotocol/typescript-sdk/commit/0657c3bea9218f67494850562c0c449548c972c1) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Fix the server + conformance script leaking the test server process: the cleanup trap killed the npx wrapper while the actual server kept listening on port 3000, making later runs silently test stale code or hang forever in the readiness loop. The script now spawns the server directly with + `node --import tsx`, refuses to start while the port is taken, and bounds each readiness probe; both test servers report `EADDRINUSE` with an actionable message, and the plain `test:conformance:client` script works again (`--suite core`, required since conformance + 0.2.0-alpha.1). diff --git a/test/conformance/package.json b/test/conformance/package.json index f4f927096c..8c2cf880e1 100644 --- a/test/conformance/package.json +++ b/test/conformance/package.json @@ -1,7 +1,7 @@ { "name": "@modelcontextprotocol/test-conformance", "private": true, - "version": "2.0.0-alpha.0", + "version": "2.0.0-alpha.1", "description": "Model Context Protocol implementation for TypeScript", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -38,7 +38,7 @@ "@modelcontextprotocol/conformance": "0.2.0-alpha.3", "@modelcontextprotocol/client": "workspace:^", "@modelcontextprotocol/server": "workspace:^", - "@modelcontextprotocol/core": "workspace:^", + "@modelcontextprotocol/core-internal": "workspace:^", "@modelcontextprotocol/express": "workspace:^", "@modelcontextprotocol/node": "workspace:^", "@modelcontextprotocol/tsconfig": "workspace:^", diff --git a/test/conformance/tsconfig.json b/test/conformance/tsconfig.json index f529719742..dffb32355b 100644 --- a/test/conformance/tsconfig.json +++ b/test/conformance/tsconfig.json @@ -5,8 +5,10 @@ "compilerOptions": { "paths": { "*": ["./*"], - "@modelcontextprotocol/core": ["./node_modules/@modelcontextprotocol/core/src/index.ts"], - "@modelcontextprotocol/core/public": ["./node_modules/@modelcontextprotocol/core/src/exports/public/index.ts"], + "@modelcontextprotocol/core-internal": ["./node_modules/@modelcontextprotocol/core-internal/src/index.ts"], + "@modelcontextprotocol/core-internal/public": [ + "./node_modules/@modelcontextprotocol/core-internal/src/exports/public/index.ts" + ], "@modelcontextprotocol/client": ["./node_modules/@modelcontextprotocol/client/src/index.ts"], "@modelcontextprotocol/server": ["./node_modules/@modelcontextprotocol/server/src/index.ts"], "@modelcontextprotocol/express": ["./node_modules/@modelcontextprotocol/express/src/index.ts"], diff --git a/test/e2e/CHANGELOG.md b/test/e2e/CHANGELOG.md new file mode 100644 index 0000000000..1bfd5dc0ba --- /dev/null +++ b/test/e2e/CHANGELOG.md @@ -0,0 +1,11 @@ +# @modelcontextprotocol/test-e2e + +## 2.0.0-alpha.1 + +### Patch Changes + +- [#2203](https://github.com/modelcontextprotocol/typescript-sdk/pull/2203) [`4a5c863`](https://github.com/modelcontextprotocol/typescript-sdk/commit/4a5c863a21f06e3ae43db116f32f2da7df5988b4) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Add consumer-sourced + e2e requirements (behaviors real SDK dependents rely on) and run the interaction matrix over the legacy HTTP+SSE transport, with known failures recording where v2 intentionally differs. + +- [#2179](https://github.com/modelcontextprotocol/typescript-sdk/pull/2179) [`1998a18`](https://github.com/modelcontextprotocol/typescript-sdk/commit/1998a186eeb8aa3728c1e82420e381e0f9b80a83) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Add the end-to-end + behavior test suite as a workspace package: a requirements manifest covering protocol-visible SDK behavior across the in-memory, stdio, and Streamable HTTP transports, ported from the v1.x branch and extended with coverage for v2 features. diff --git a/test/e2e/helpers/wire-sniffer.ts b/test/e2e/helpers/wire-sniffer.ts index 89663214ce..03fde0b5af 100644 --- a/test/e2e/helpers/wire-sniffer.ts +++ b/test/e2e/helpers/wire-sniffer.ts @@ -5,7 +5,7 @@ import { ServerNotificationSchema, ServerRequestSchema, ServerResultSchema -} from '@modelcontextprotocol/core'; +} from '@modelcontextprotocol/core-internal'; import type { Transport } from '@modelcontextprotocol/server'; import { isJSONRPCErrorResponse, diff --git a/test/e2e/package.json b/test/e2e/package.json index 2b24c771f7..6dfa6a781f 100644 --- a/test/e2e/package.json +++ b/test/e2e/package.json @@ -1,7 +1,7 @@ { "name": "@modelcontextprotocol/test-e2e", "private": true, - "version": "2.0.0-alpha.0", + "version": "2.0.0-alpha.1", "description": "Model Context Protocol implementation for TypeScript", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -32,7 +32,7 @@ "devDependencies": { "@hono/node-server": "catalog:runtimeServerOnly", "@modelcontextprotocol/client": "workspace:^", - "@modelcontextprotocol/core": "workspace:^", + "@modelcontextprotocol/core-internal": "workspace:^", "@modelcontextprotocol/server": "workspace:^", "@modelcontextprotocol/server-legacy": "workspace:^", "@modelcontextprotocol/express": "workspace:^", diff --git a/test/e2e/scenarios/sampling.test.ts b/test/e2e/scenarios/sampling.test.ts index acec14413d..d1b04893b8 100644 --- a/test/e2e/scenarios/sampling.test.ts +++ b/test/e2e/scenarios/sampling.test.ts @@ -10,7 +10,7 @@ import type { ClientCapabilities } from '@modelcontextprotocol/client'; import { Client } from '@modelcontextprotocol/client'; -import { CreateMessageRequestParamsSchema } from '@modelcontextprotocol/core'; +import { CreateMessageRequestParamsSchema } from '@modelcontextprotocol/core-internal'; import type { CreateMessageRequest, CreateMessageResultWithTools, ServerOptions } from '@modelcontextprotocol/server'; import { McpServer, ProtocolError, ProtocolErrorCode } from '@modelcontextprotocol/server'; import { expect } from 'vitest'; diff --git a/test/e2e/scenarios/stdio.test.ts b/test/e2e/scenarios/stdio.test.ts index f066dde648..ba4baf69f3 100644 --- a/test/e2e/scenarios/stdio.test.ts +++ b/test/e2e/scenarios/stdio.test.ts @@ -17,7 +17,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url'; import { Client } from '@modelcontextprotocol/client'; import { StdioClientTransport } from '@modelcontextprotocol/client/stdio'; -import { JSONRPCMessageSchema } from '@modelcontextprotocol/core'; +import { JSONRPCMessageSchema } from '@modelcontextprotocol/core-internal'; import { expect, vi } from 'vitest'; import { verifies } from '../helpers/verifies'; diff --git a/test/e2e/scenarios/tools.test.ts b/test/e2e/scenarios/tools.test.ts index cc12a54b1b..195aa0ce07 100644 --- a/test/e2e/scenarios/tools.test.ts +++ b/test/e2e/scenarios/tools.test.ts @@ -21,7 +21,7 @@ */ import { Client } from '@modelcontextprotocol/client'; -import type { JsonSchemaType } from '@modelcontextprotocol/core'; +import type { JsonSchemaType } from '@modelcontextprotocol/core-internal'; import type { CreateMessageRequest, CreateMessageResult, diff --git a/test/e2e/scenarios/transport-http.test.ts b/test/e2e/scenarios/transport-http.test.ts index 526e554fa2..760df8ea76 100644 --- a/test/e2e/scenarios/transport-http.test.ts +++ b/test/e2e/scenarios/transport-http.test.ts @@ -19,7 +19,7 @@ import { randomUUID } from 'node:crypto'; import { Client, SdkHttpError, StreamableHTTPClientTransport } from '@modelcontextprotocol/client'; -import { JSONRPCRequestSchema } from '@modelcontextprotocol/core'; +import { JSONRPCRequestSchema } from '@modelcontextprotocol/core-internal'; import { LATEST_PROTOCOL_VERSION, McpServer, diff --git a/test/e2e/scenarios/transport-raw.test.ts b/test/e2e/scenarios/transport-raw.test.ts index 7a6aa4fa34..0a14eb1866 100644 --- a/test/e2e/scenarios/transport-raw.test.ts +++ b/test/e2e/scenarios/transport-raw.test.ts @@ -17,7 +17,7 @@ import { fileURLToPath } from 'node:url'; import { StreamableHTTPClientTransport } from '@modelcontextprotocol/client'; import { StdioClientTransport } from '@modelcontextprotocol/client/stdio'; -import { CallToolResultSchema, InitializeResultSchema, JSONRPCResultResponseSchema } from '@modelcontextprotocol/core'; +import { CallToolResultSchema, InitializeResultSchema, JSONRPCResultResponseSchema } from '@modelcontextprotocol/core-internal'; import type { JSONRPCMessage, JSONRPCNotification, JSONRPCRequest } from '@modelcontextprotocol/server'; import { InMemoryTransport, McpServer } from '@modelcontextprotocol/server'; import { expect, vi } from 'vitest'; diff --git a/test/e2e/scenarios/validation.test.ts b/test/e2e/scenarios/validation.test.ts index dfdce32d63..2f2d638cb4 100644 --- a/test/e2e/scenarios/validation.test.ts +++ b/test/e2e/scenarios/validation.test.ts @@ -13,7 +13,7 @@ */ import { Client } from '@modelcontextprotocol/client'; -import type { JsonSchemaType, JsonSchemaValidator, jsonSchemaValidator, StandardSchemaWithJSON } from '@modelcontextprotocol/core'; +import type { JsonSchemaType, JsonSchemaValidator, jsonSchemaValidator, StandardSchemaWithJSON } from '@modelcontextprotocol/core-internal'; import type { Tool } from '@modelcontextprotocol/server'; import { fromJsonSchema, diff --git a/test/e2e/tsconfig.json b/test/e2e/tsconfig.json index 453684bc8b..e8c0fc8ba1 100644 --- a/test/e2e/tsconfig.json +++ b/test/e2e/tsconfig.json @@ -5,10 +5,12 @@ "compilerOptions": { "paths": { "*": ["./*"], - "@modelcontextprotocol/core": ["./node_modules/@modelcontextprotocol/core/src/index.ts"], - "@modelcontextprotocol/core/public": ["./node_modules/@modelcontextprotocol/core/src/exports/public/index.ts"], - "@modelcontextprotocol/core/validators/cfWorker": [ - "./node_modules/@modelcontextprotocol/core/src/validators/cfWorkerProvider.ts" + "@modelcontextprotocol/core-internal": ["./node_modules/@modelcontextprotocol/core-internal/src/index.ts"], + "@modelcontextprotocol/core-internal/public": [ + "./node_modules/@modelcontextprotocol/core-internal/src/exports/public/index.ts" + ], + "@modelcontextprotocol/core-internal/validators/cfWorker": [ + "./node_modules/@modelcontextprotocol/core-internal/src/validators/cfWorkerProvider.ts" ], "@modelcontextprotocol/client": ["./node_modules/@modelcontextprotocol/client/src/index.ts"], "@modelcontextprotocol/client/stdio": ["./node_modules/@modelcontextprotocol/client/src/stdio.ts"], diff --git a/test/helpers/package.json b/test/helpers/package.json index 1826205a1b..3b7e49950e 100644 --- a/test/helpers/package.json +++ b/test/helpers/package.json @@ -27,7 +27,7 @@ "check": "npm run typecheck && npm run lint" }, "devDependencies": { - "@modelcontextprotocol/core": "workspace:^", + "@modelcontextprotocol/core-internal": "workspace:^", "zod": "catalog:runtimeShared", "vitest": "catalog:devTools", "@modelcontextprotocol/tsconfig": "workspace:^", diff --git a/test/helpers/src/helpers/oauth.ts b/test/helpers/src/helpers/oauth.ts index 61c3d44e40..a689b33909 100644 --- a/test/helpers/src/helpers/oauth.ts +++ b/test/helpers/src/helpers/oauth.ts @@ -1,4 +1,4 @@ -import type { FetchLike } from '@modelcontextprotocol/core'; +import type { FetchLike } from '@modelcontextprotocol/core-internal'; import { vi } from 'vitest'; export interface MockOAuthFetchOptions { diff --git a/test/helpers/tsconfig.json b/test/helpers/tsconfig.json index ce44773946..18809d7ac8 100644 --- a/test/helpers/tsconfig.json +++ b/test/helpers/tsconfig.json @@ -5,8 +5,10 @@ "compilerOptions": { "paths": { "*": ["./*"], - "@modelcontextprotocol/core": ["./node_modules/@modelcontextprotocol/core/src/index.ts"], - "@modelcontextprotocol/core/public": ["./node_modules/@modelcontextprotocol/core/src/exports/public/index.ts"], + "@modelcontextprotocol/core-internal": ["./node_modules/@modelcontextprotocol/core-internal/src/index.ts"], + "@modelcontextprotocol/core-internal/public": [ + "./node_modules/@modelcontextprotocol/core-internal/src/exports/public/index.ts" + ], "@modelcontextprotocol/vitest-config": ["./node_modules/@modelcontextprotocol/vitest-config/tsconfig.json"] } } diff --git a/test/integration/package.json b/test/integration/package.json index 97a62e6c32..10997c5be3 100644 --- a/test/integration/package.json +++ b/test/integration/package.json @@ -33,7 +33,7 @@ "devDependencies": { "@cfworker/json-schema": "catalog:runtimeShared", "@modelcontextprotocol/client": "workspace:^", - "@modelcontextprotocol/core": "workspace:^", + "@modelcontextprotocol/core-internal": "workspace:^", "@modelcontextprotocol/eslint-config": "workspace:^", "@modelcontextprotocol/express": "workspace:^", "@modelcontextprotocol/node": "workspace:^", diff --git a/test/integration/test/client/client.test.ts b/test/integration/test/client/client.test.ts index a7613b24e4..a2f53ff71e 100644 --- a/test/integration/test/client/client.test.ts +++ b/test/integration/test/client/client.test.ts @@ -1,5 +1,5 @@ import { Client, getSupportedElicitationModes } from '@modelcontextprotocol/client'; -import type { Prompt, Resource, Tool, Transport } from '@modelcontextprotocol/core'; +import type { Prompt, Resource, Tool, Transport } from '@modelcontextprotocol/core-internal'; import { InMemoryTransport, LATEST_PROTOCOL_VERSION, @@ -7,7 +7,7 @@ import { SdkError, SdkErrorCode, SUPPORTED_PROTOCOL_VERSIONS -} from '@modelcontextprotocol/core'; +} from '@modelcontextprotocol/core-internal'; import { McpServer, Server } from '@modelcontextprotocol/server'; /*** diff --git a/test/integration/test/issues/test1277.zod.v4.description.test.ts b/test/integration/test/issues/test1277.zod.v4.description.test.ts index a8a8d0c7be..0d9c6e9cbe 100644 --- a/test/integration/test/issues/test1277.zod.v4.description.test.ts +++ b/test/integration/test/issues/test1277.zod.v4.description.test.ts @@ -7,7 +7,7 @@ */ import { Client } from '@modelcontextprotocol/client'; -import { InMemoryTransport } from '@modelcontextprotocol/core'; +import { InMemoryTransport } from '@modelcontextprotocol/core-internal'; import { McpServer } from '@modelcontextprotocol/server'; import * as z from 'zod/v4'; diff --git a/test/integration/test/issues/test400.optional-tool-params.test.ts b/test/integration/test/issues/test400.optional-tool-params.test.ts index b71d85b819..7671712734 100644 --- a/test/integration/test/issues/test400.optional-tool-params.test.ts +++ b/test/integration/test/issues/test400.optional-tool-params.test.ts @@ -7,7 +7,7 @@ */ import { Client } from '@modelcontextprotocol/client'; -import { InMemoryTransport } from '@modelcontextprotocol/core'; +import { InMemoryTransport } from '@modelcontextprotocol/core-internal'; import { McpServer } from '@modelcontextprotocol/server'; import * as z from 'zod/v4'; diff --git a/test/integration/test/server.test.ts b/test/integration/test/server.test.ts index dcb9cea87f..51cde29161 100644 --- a/test/integration/test/server.test.ts +++ b/test/integration/test/server.test.ts @@ -9,14 +9,14 @@ import type { jsonSchemaValidator, LoggingMessageNotification, Transport -} from '@modelcontextprotocol/core'; +} from '@modelcontextprotocol/core-internal'; import { InMemoryTransport, LATEST_PROTOCOL_VERSION, SdkError, SdkErrorCode, SUPPORTED_PROTOCOL_VERSIONS -} from '@modelcontextprotocol/core'; +} from '@modelcontextprotocol/core-internal'; import { createMcpExpressApp } from '@modelcontextprotocol/express'; import { McpServer, Server } from '@modelcontextprotocol/server'; import type { Request, Response } from 'express'; diff --git a/test/integration/test/server/declaredCapabilities.test.ts b/test/integration/test/server/declaredCapabilities.test.ts index 60e3f847bb..60e214d129 100644 --- a/test/integration/test/server/declaredCapabilities.test.ts +++ b/test/integration/test/server/declaredCapabilities.test.ts @@ -1,5 +1,5 @@ import { Client } from '@modelcontextprotocol/client'; -import { InMemoryTransport, ProtocolErrorCode } from '@modelcontextprotocol/core'; +import { InMemoryTransport, ProtocolErrorCode } from '@modelcontextprotocol/core-internal'; import { McpServer } from '@modelcontextprotocol/server'; import { describe, expect, test } from 'vitest'; import * as z from 'zod/v4'; diff --git a/test/integration/test/server/elicitation.test.ts b/test/integration/test/server/elicitation.test.ts index 84bb071f1b..5963229f0a 100644 --- a/test/integration/test/server/elicitation.test.ts +++ b/test/integration/test/server/elicitation.test.ts @@ -8,10 +8,10 @@ */ import { Client } from '@modelcontextprotocol/client'; -import type { ElicitRequestFormParams } from '@modelcontextprotocol/core'; -import { InMemoryTransport } from '@modelcontextprotocol/core'; -import { AjvJsonSchemaValidator } from '@modelcontextprotocol/core/validators/ajv'; -import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/core/validators/cfWorker'; +import type { ElicitRequestFormParams } from '@modelcontextprotocol/core-internal'; +import { InMemoryTransport } from '@modelcontextprotocol/core-internal'; +import { AjvJsonSchemaValidator } from '@modelcontextprotocol/core-internal/validators/ajv'; +import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/core-internal/validators/cfWorker'; import { Server } from '@modelcontextprotocol/server'; const ajvProvider = new AjvJsonSchemaValidator(); diff --git a/test/integration/test/server/mcp.test.ts b/test/integration/test/server/mcp.test.ts index 8c844b11cb..7bc22f2652 100644 --- a/test/integration/test/server/mcp.test.ts +++ b/test/integration/test/server/mcp.test.ts @@ -1,6 +1,12 @@ import { Client } from '@modelcontextprotocol/client'; -import type { Notification, TextContent } from '@modelcontextprotocol/core'; -import { getDisplayName, InMemoryTransport, ProtocolErrorCode, UriTemplate, UrlElicitationRequiredError } from '@modelcontextprotocol/core'; +import type { Notification, TextContent } from '@modelcontextprotocol/core-internal'; +import { + getDisplayName, + InMemoryTransport, + ProtocolErrorCode, + UriTemplate, + UrlElicitationRequiredError +} from '@modelcontextprotocol/core-internal'; import { completable, McpServer, ResourceTemplate } from '@modelcontextprotocol/server'; import { afterEach, beforeEach, describe, expect, test } from 'vitest'; import * as z from 'zod/v4'; diff --git a/test/integration/test/standardSchema.test.ts b/test/integration/test/standardSchema.test.ts index ffc41ce4d8..92f5a6c78c 100644 --- a/test/integration/test/standardSchema.test.ts +++ b/test/integration/test/standardSchema.test.ts @@ -4,8 +4,8 @@ */ import { Client } from '@modelcontextprotocol/client'; -import type { TextContent } from '@modelcontextprotocol/core'; -import { InMemoryTransport } from '@modelcontextprotocol/core'; +import type { TextContent } from '@modelcontextprotocol/core-internal'; +import { InMemoryTransport } from '@modelcontextprotocol/core-internal'; import { completable, fromJsonSchema as serverFromJsonSchema, McpServer } from '@modelcontextprotocol/server'; import { toStandardJsonSchema } from '@valibot/to-json-schema'; import { type } from 'arktype'; diff --git a/test/integration/test/title.test.ts b/test/integration/test/title.test.ts index 588d300832..50e9460dce 100644 --- a/test/integration/test/title.test.ts +++ b/test/integration/test/title.test.ts @@ -1,5 +1,5 @@ import { Client } from '@modelcontextprotocol/client'; -import { InMemoryTransport } from '@modelcontextprotocol/core'; +import { InMemoryTransport } from '@modelcontextprotocol/core-internal'; import { McpServer, ResourceTemplate, Server } from '@modelcontextprotocol/server'; import * as z from 'zod/v4'; diff --git a/test/integration/tsconfig.json b/test/integration/tsconfig.json index 64391c93e0..4764fcfc84 100644 --- a/test/integration/tsconfig.json +++ b/test/integration/tsconfig.json @@ -5,11 +5,15 @@ "compilerOptions": { "paths": { "*": ["./*"], - "@modelcontextprotocol/core": ["./node_modules/@modelcontextprotocol/core/src/index.ts"], - "@modelcontextprotocol/core/public": ["./node_modules/@modelcontextprotocol/core/src/exports/public/index.ts"], - "@modelcontextprotocol/core/validators/ajv": ["./node_modules/@modelcontextprotocol/core/src/validators/ajvProvider.ts"], - "@modelcontextprotocol/core/validators/cfWorker": [ - "./node_modules/@modelcontextprotocol/core/src/validators/cfWorkerProvider.ts" + "@modelcontextprotocol/core-internal": ["./node_modules/@modelcontextprotocol/core-internal/src/index.ts"], + "@modelcontextprotocol/core-internal/public": [ + "./node_modules/@modelcontextprotocol/core-internal/src/exports/public/index.ts" + ], + "@modelcontextprotocol/core-internal/validators/ajv": [ + "./node_modules/@modelcontextprotocol/core-internal/src/validators/ajvProvider.ts" + ], + "@modelcontextprotocol/core-internal/validators/cfWorker": [ + "./node_modules/@modelcontextprotocol/core-internal/src/validators/cfWorkerProvider.ts" ], "@modelcontextprotocol/client": ["./node_modules/@modelcontextprotocol/client/src/index.ts"], "@modelcontextprotocol/client/stdio": ["./node_modules/@modelcontextprotocol/client/src/stdio.ts"], diff --git a/typedoc.config.mjs b/typedoc.config.mjs index f2a4e50f56..5835ac2a63 100644 --- a/typedoc.config.mjs +++ b/typedoc.config.mjs @@ -3,10 +3,12 @@ import fg from 'fast-glob'; import { readFileSync } from 'node:fs'; import { join } from 'node:path'; -// Find all package.json files under packages/ and build package list +// Find all package.json files under packages/ and build package list. +// Exclude node_modules and the codemod batch-test's cloned real-world repos, which are not part +// of this SDK's public API surface (and would otherwise fail docs:check locally when present). const packageJsonPaths = await fg('packages/**/package.json', { cwd: process.cwd(), - ignore: ['**/node_modules/**'] + ignore: ['**/node_modules/**', '**/batch-test/**'] }); const packages = packageJsonPaths.map(p => { const rootDir = join(process.cwd(), p.replace('/package.json', '')); @@ -14,7 +16,12 @@ const packages = packageJsonPaths.map(p => { return { rootDir, manifest }; }); -const publicPackages = packages.filter(p => p.manifest.private !== true); +// @modelcontextprotocol/core is published for direct schema imports (CallToolResultSchema.parse(...)), +// but it's a thin re-export of the spec/OAuth Zod schemas whose JSDoc cross-references TYPES that live +// in client/server — unresolvable from core's own per-package doc scope. We skip rendering its API docs +// (the schemas mirror the documented types 1:1) so monorepo-wide invalid-link validation can stay ON. +const DOCS_EXCLUDED_PACKAGES = new Set(['@modelcontextprotocol/core']); +const publicPackages = packages.filter(p => p.manifest.private !== true && !DOCS_EXCLUDED_PACKAGES.has(p.manifest.name)); const entryPoints = publicPackages.map(p => p.rootDir); console.log( @@ -48,7 +55,7 @@ export default { treatWarningsAsErrors: true, out: 'tmp/docs/', externalSymbolLinkMappings: { - '@modelcontextprotocol/core': { + '@modelcontextprotocol/core-internal': { StandardSchemaV1: 'https://standardschema.dev/', StandardJSONSchemaV1: 'https://standardschema.dev/' }