diff --git a/.changeset/codemod-versions-from-manifests.md b/.changeset/codemod-versions-from-manifests.md new file mode 100644 index 0000000000..b0383703ad --- /dev/null +++ b/.changeset/codemod-versions-from-manifests.md @@ -0,0 +1,5 @@ +--- +'@modelcontextprotocol/codemod': patch +--- + +Read the v2 package versions the codemod writes into migrated `package.json` files directly from the workspace manifests at build time, replacing the committed generated `versions.ts` (which went stale after every release and made source builds write outdated versions). diff --git a/.changeset/pre.json b/.changeset/pre.json index 50c743f316..559973ea8a 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -25,6 +25,9 @@ "@modelcontextprotocol/test-integration": "2.0.0-alpha.1" }, "changesets": [ - "beta-release" + "beta-release", + "cjs-support-v2-packages", + "codemod-iterations-5", + "post-dispatch-32021-http-400" ] } diff --git a/docs/servers/notifications.md b/docs/servers/notifications.md index 42bc96415c..03bada8ef5 100644 --- a/docs/servers/notifications.md +++ b/docs/servers/notifications.md @@ -87,7 +87,7 @@ On stdio, [`serveStdio`](../serving/stdio.md) routes the instance's own `send*Li ::: ::: info Coming from 2025-era subscriptions -A 2025-era connection delivers per-resource updates without a listen stream — [Subscriptions](../clients/subscriptions.md#fall-back-to-legacy-per-resource-subscribe) covers that path, and [Protocol versions](../protocol-versions.md) the era split. +A 2025-era connection delivers per-resource updates without a listen stream — [Resources](./resources.md#serve-per-resource-subscriptions) covers the server's subscription bookkeeping, [Subscriptions](../clients/subscriptions.md#fall-back-to-legacy-per-resource-subscribe) the client call, and [Protocol versions](../protocol-versions.md) the era split. ::: ## Pick an event bus for multi-process deployments diff --git a/docs/servers/resources.md b/docs/servers/resources.md index 7082723d22..bb6cacd141 100644 --- a/docs/servers/resources.md +++ b/docs/servers/resources.md @@ -212,6 +212,51 @@ server.sendResourceListChanged(); The notification tells connected clients to call `resources/list` again. A change to one resource's content is a different signal, `notifications/resources/updated` — [Notifications](./notifications.md) covers both. +## Serve per-resource subscriptions + +A 2025-era client opts into `notifications/resources/updated` for one URI with `resources/subscribe`. The SDK routes the verb; the bookkeeping is yours: advertise the capability, track the URIs per connection, and send the notification to subscribers only. + +```ts source="../../examples/guides/servers/resources.examples.ts#sendResourceUpdated_subscribers" +let deployStatus = 'idle'; + +const deploys = new McpServer({ name: 'deploys', version: '1.0.0' }, { capabilities: { resources: { subscribe: true } } }); + +deploys.registerResource( + 'deploy-status', + 'deploys://status', + { description: 'The current deploy state', mimeType: 'text/plain' }, + async uri => ({ contents: [{ uri: uri.href, text: deployStatus }] }) +); + +// The SDK routes the two verbs; which URIs this connection watches is yours to track. +const subscribedUris = new Set(); +deploys.server.setRequestHandler('resources/subscribe', request => { + subscribedUris.add(request.params.uri); + return {}; +}); +deploys.server.setRequestHandler('resources/unsubscribe', request => { + subscribedUris.delete(request.params.uri); + return {}; +}); + +async function setDeployStatus(status: string): Promise { + deployStatus = status; + if (subscribedUris.has('deploys://status')) { + await deploys.server.sendResourceUpdated({ uri: 'deploys://status' }); + } +} +``` + +The `Set` belongs to one server instance, and each connection gets its own instance from your factory — a subscription never leaks across connections. Send `resources/updated` only to connections that subscribed; unsolicited per-resource updates are wrong on 2025-era connections. + +The pattern needs a connection that outlives the subscribe call: over stdio (and any sessionful wiring) the instance and its `Set` live as long as the connection. Behind `createMcpHandler`'s stateless legacy fallback each POST gets a fresh instance, so `resources/subscribe` succeeds and the `Set` is discarded with it — no update can ever be delivered on that posture. [Support legacy clients](../serving/legacy-clients.md) covers the serving postures. + +::: info +On [2026-07-28](../protocol-versions.md) connections the verb does not exist: clients name resource URIs in their `subscriptions/listen` filter, and the entry filters delivery itself — `serveStdio` routes the instance's own `sendResourceUpdated` call onto matching streams, and `createMcpHandler` delivers what you publish on its notifier ([Notifications](./notifications.md#publish-a-resource-update-through-the-handler)). +::: + +A dual-era server therefore still calls `sendResourceUpdated` on 2026-07-28 connections, where the subscribe set is always empty — gate on the connection's era as well as the set. The [`resources` example](https://github.com/modelcontextprotocol/typescript-sdk/tree/main/examples/resources) guards with `reqCtx.era === 'modern' || subscribedUris.has(uri)` in its factory and runs as a self-verifying pair: delivery is asserted over stdio on both eras and over HTTP on the 2026-07-28 listen path; the stateless legacy HTTP leg asserts only that the subscribe calls succeed. + ## Recap - `registerResource(name, uri, config, readCallback)` registers a resource at a fixed URI. @@ -220,3 +265,4 @@ The notification tells connected clients to call `resources/list` again. A chang - A template's `list` callback is what makes its instances appear in `resources/list`. - Resolve file-backed paths to their real location and reject anything outside the root before reading. - Registration changes emit `notifications/resources/list_changed` automatically. +- `resources/subscribe` bookkeeping is the server's: advertise `resources: { subscribe: true }`, track URIs per connection, send `resources/updated` to subscribers only. diff --git a/examples/README.md b/examples/README.md index ea6f518018..db8812871a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -24,7 +24,7 @@ The one exception to the generic commands is the reference pair: [`cli-client/`] | ------------------------------------- | ------------------------------------------------------------------------ | | [`tools/`](./tools/README.md) | Register tools, infer input/output schemas, call them, structured output | | [`prompts/`](./prompts/README.md) | Prompts + argument completion | -| [`resources/`](./resources/README.md) | Static + templated resources, list/read | +| [`resources/`](./resources/README.md) | Static + templated resources, list/read, era-split subscriptions | | [`dual-era/`](./dual-era/README.md) | One factory, both protocol eras, both transports | ## Feature stories diff --git a/examples/guides/servers/resources.examples.ts b/examples/guides/servers/resources.examples.ts index 57d5cbe3b7..ec74419055 100644 --- a/examples/guides/servers/resources.examples.ts +++ b/examples/guides/servers/resources.examples.ts @@ -117,6 +117,41 @@ server.registerResource( // Imported dynamically so the page's lead region stays self-contained. // --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// "Serve per-resource subscriptions" +// --------------------------------------------------------------------------- + +//#region sendResourceUpdated_subscribers +let deployStatus = 'idle'; + +const deploys = new McpServer({ name: 'deploys', version: '1.0.0' }, { capabilities: { resources: { subscribe: true } } }); + +deploys.registerResource( + 'deploy-status', + 'deploys://status', + { description: 'The current deploy state', mimeType: 'text/plain' }, + async uri => ({ contents: [{ uri: uri.href, text: deployStatus }] }) +); + +// The SDK routes the two verbs; which URIs this connection watches is yours to track. +const subscribedUris = new Set(); +deploys.server.setRequestHandler('resources/subscribe', request => { + subscribedUris.add(request.params.uri); + return {}; +}); +deploys.server.setRequestHandler('resources/unsubscribe', request => { + subscribedUris.delete(request.params.uri); + return {}; +}); + +async function setDeployStatus(status: string): Promise { + deployStatus = status; + if (subscribedUris.has('deploys://status')) { + await deploys.server.sendResourceUpdated({ uri: 'deploys://status' }); + } +} +//#endregion sendResourceUpdated_subscribers + const { Client, InMemoryTransport } = await import('@modelcontextprotocol/client'); const client = new Client({ name: 'resources-docs-harness', version: '1.0.0' }); @@ -153,5 +188,29 @@ if (uris.some(uri => uri.startsWith('users://')) || !uris.includes('teams://core server.sendResourceListChanged(); //#endregion sendResourceListChanged +// "Serve per-resource subscriptions" — a 2025-era client subscribes, the status +// changes, exactly one notifications/resources/updated arrives, and none after +// unsubscribe. +const watcher = new Client({ name: 'resources-docs-watcher', version: '1.0.0' }, { versionNegotiation: { mode: 'legacy' } }); +const [watcherTransport, deploysTransport] = InMemoryTransport.createLinkedPair(); +const updates: string[] = []; +watcher.setNotificationHandler('notifications/resources/updated', notification => { + updates.push(notification.params.uri); +}); +await deploys.connect(deploysTransport); +await watcher.connect(watcherTransport); + +await watcher.subscribeResource({ uri: 'deploys://status' }); +await setDeployStatus('deploying'); +await watcher.unsubscribeResource({ uri: 'deploys://status' }); +await setDeployStatus('done'); +await new Promise(resolve => setTimeout(resolve, 50)); +console.log('updates:', updates); +if (updates.length !== 1 || updates[0] !== 'deploys://status') { + throw new Error(`resources.md subscription claim failed: ${JSON.stringify(updates)}`); +} +await watcher.close(); +await deploys.close(); + await client.close(); await server.close(); diff --git a/examples/resources/README.md b/examples/resources/README.md index 409df6833c..35a18a8fbd 100644 --- a/examples/resources/README.md +++ b/examples/resources/README.md @@ -1,6 +1,6 @@ # resources -Direct resources (a fixed URI string) and templated resources (`ResourceTemplate('greeting://{name}')`). The client lists both, reads the direct config, and reads a templated greeting. +Direct resources (a fixed URI string), templated resources (`ResourceTemplate('greeting://{name}')`), and per-resource subscriptions. The client lists both kinds, reads the direct config and a templated greeting, then subscribes to `counter://value` — `subscriptions/listen` on 2026-07-28, `resources/subscribe` on 2025 — calls the `increment` tool, and asserts the `notifications/resources/updated` it produces. Per-request legacy HTTP has no delivery channel, so that leg skips the delivery assertion. ```bash pnpm tsx examples/resources/client.ts diff --git a/examples/resources/client.ts b/examples/resources/client.ts index c81fdf4bce..ee1325d42d 100644 --- a/examples/resources/client.ts +++ b/examples/resources/client.ts @@ -1,5 +1,11 @@ /** - * Drives the resources example: list, list templates, read direct + templated. + * Drives the resources example: list, list templates, read direct + templated, + * then subscribe to `counter://value` and assert the update notification. The + * subscription sender is era-split — `subscriptions/listen` on 2026-07-28, + * `resources/subscribe` on 2025 — while the notification handler is one + * registration either way. Per-request legacy HTTP has no channel to deliver + * notifications, so that leg asserts the calls succeed and skips the delivery + * assertion. */ import { check, parseExampleArgs, siblingPath } from '@mcp-examples/shared'; import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client'; @@ -18,6 +24,7 @@ await (transport === 'stdio' const list = await client.listResources(); check.ok(list.resources.some(r => r.uri === 'config://app')); +check.ok(list.resources.some(r => r.uri === 'counter://value')); const templates = await client.listResourceTemplates(); check.ok(templates.resourceTemplates.some(t => t.uriTemplate === 'greeting://{name}')); @@ -30,4 +37,52 @@ const hello = await client.readResource({ uri: 'greeting://world' }); const helloContent = hello.contents[0]; check.equal(helloContent && 'text' in helloContent ? helloContent.text : '', 'Hello, world!'); +// --- Subscriptions --------------------------------------------------------- + +// One handler serves both delivery paths: the 2026-07-28 listen stream and a +// 2025-era connection's unsolicited notification dispatch the same way. +let resolveUpdated: ((uri: string) => void) | undefined; +client.setNotificationHandler('notifications/resources/updated', notification => { + resolveUpdated?.(notification.params.uri); +}); + +// Per-request legacy HTTP answers each POST in isolation: subscribing succeeds, +// but there is no stream to deliver the notification on. +const deliverable = !(era === 'legacy' && transport === 'http'); + +const updated = new Promise(resolve => { + resolveUpdated = resolve; +}); + +const subscription = era === 'modern' ? await client.listen({ resourceSubscriptions: ['counter://value'] }) : undefined; +if (era === 'legacy') { + await client.subscribeResource({ uri: 'counter://value' }); +} + +const bumped = await client.callTool({ name: 'increment', arguments: {} }); +check.ok(!bumped.isError); +const bumpedContent = bumped.content[0]; +const bumpedValue = bumpedContent && bumpedContent.type === 'text' ? bumpedContent.text : ''; + +if (deliverable) { + let timer: NodeJS.Timeout | undefined; + const timeout = new Promise((_, reject) => { + timer = setTimeout(() => reject(new Error('no resources/updated within 8s')), 8000); + }); + const updatedUri = await Promise.race([updated, timeout]).finally(() => clearTimeout(timer)); + check.equal(updatedUri, 'counter://value'); + + // The resource and the tool observe the same state: the re-read matches the + // value increment returned (not a literal, so a long-lived server re-runs). + const counter = await client.readResource({ uri: 'counter://value' }); + const counterContent = counter.contents[0]; + check.equal(counterContent && 'text' in counterContent ? counterContent.text : '', bumpedValue); +} + +if (subscription) { + await subscription.close(); +} else if (era === 'legacy') { + await client.unsubscribeResource({ uri: 'counter://value' }); +} + await client.close(); diff --git a/examples/resources/server.ts b/examples/resources/server.ts index 069be5769a..2ac10338d6 100644 --- a/examples/resources/server.ts +++ b/examples/resources/server.ts @@ -1,19 +1,33 @@ /** - * Resources primitive — direct + templated. + * Resources primitive — direct, templated, and subscribable. * * `McpServer.registerResource` accepts either a fixed URI string (direct - * resource) or a `ResourceTemplate` (URI template with substitution). One - * binary, either transport — selected from argv below. + * resource) or a `ResourceTemplate` (URI template with substitution). The + * `counter://value` resource is mutable: the `increment` tool bumps it and + * announces the change — in-band to this connection (2025-era subscribers + * tracked per connection, 2026-07-28 listen streams routed by the entry), and + * on the handler's notifier for clients on other requests. One binary, either + * transport — selected from argv below. */ import { createServer } from 'node:http'; import { parseExampleArgs } from '@mcp-examples/shared'; import { toNodeHandler } from '@modelcontextprotocol/node'; +import type { McpRequestContext } from '@modelcontextprotocol/server'; import { createMcpHandler, McpServer, ResourceTemplate } from '@modelcontextprotocol/server'; import { serveStdio } from '@modelcontextprotocol/server/stdio'; -function buildServer(): McpServer { - const server = new McpServer({ name: 'resources-example', version: '1.0.0' }); +const COUNTER_URI = 'counter://value'; + +// The counter is application state, shared by every connection; which URIs a +// connection subscribed to is connection state, tracked inside buildServer. +let counter = 0; + +function buildServer(reqCtx: McpRequestContext, publishUpdated?: (uri: string) => void): McpServer { + const server = new McpServer( + { name: 'resources-example', version: '1.0.0' }, + { capabilities: { resources: { subscribe: true, listChanged: true } } } + ); // A direct resource at a fixed URI. server.registerResource( @@ -31,16 +45,52 @@ function buildServer(): McpServer { async (uri, vars) => ({ contents: [{ uri: uri.href, text: `Hello, ${vars.name}!` }] }) ); + // A mutable resource — the subscription story's subject. + server.registerResource( + 'counter', + COUNTER_URI, + { mimeType: 'text/plain', description: 'A number the increment tool bumps' }, + async uri => ({ contents: [{ uri: uri.href, mimeType: 'text/plain', text: String(counter) }] }) + ); + + // resources/subscribe bookkeeping is the application's: the SDK routes the + // two verbs, and which URIs THIS connection watches lives here. + const subscribedUris = new Set(); + server.server.setRequestHandler('resources/subscribe', request => { + subscribedUris.add(request.params.uri); + return {}; + }); + server.server.setRequestHandler('resources/unsubscribe', request => { + subscribedUris.delete(request.params.uri); + return {}; + }); + + server.registerTool('increment', { description: `Bump ${COUNTER_URI} by one` }, async () => { + counter += 1; + if (publishUpdated) { + // Per-request serving: this instance answers one request and is gone, + // so the change is published on the entry's notifier for the listen + // streams other requests hold open. + publishUpdated(COUNTER_URI); + } else if (reqCtx.era === 'modern' || subscribedUris.has(COUNTER_URI)) { + // Connection serving (stdio): announce in-band. The entry routes it + // onto 2026-07-28 listen streams; on a 2025-era connection it goes + // only to subscribers — unsolicited per-resource updates are wrong. + await server.server.sendResourceUpdated({ uri: COUNTER_URI }).catch(() => {}); + } + return { content: [{ type: 'text', text: String(counter) }] }; + }); + return server; } const { transport, port } = parseExampleArgs(); if (transport === 'stdio') { - void serveStdio(buildServer); + void serveStdio(reqCtx => buildServer(reqCtx)); console.error('[server] serving over stdio'); } else { - const handler = createMcpHandler(buildServer); + const handler = createMcpHandler(reqCtx => buildServer(reqCtx, uri => handler.notify.resourceUpdated(uri))); createServer(toNodeHandler(handler)).listen(port, () => { console.error(`[server] listening on http://127.0.0.1:${port}/mcp`); }); diff --git a/packages/client/CHANGELOG.md b/packages/client/CHANGELOG.md index b347fa782f..98ca06c430 100644 --- a/packages/client/CHANGELOG.md +++ b/packages/client/CHANGELOG.md @@ -1,5 +1,16 @@ # @modelcontextprotocol/client +## 2.0.0-beta.2 + +### Patch Changes + +- [#2405](https://github.com/modelcontextprotocol/typescript-sdk/pull/2405) [`f172626`](https://github.com/modelcontextprotocol/typescript-sdk/commit/f172626a8e98b2ae2f0f690e4afb4dc74dbf6011) Thanks [@mattzcarey](https://github.com/mattzcarey)! - Ship CommonJS builds alongside ESM. Each package now emits both `.mjs`/`.d.mts` + and `.cjs`/`.d.cts` (via tsdown `format: ['esm', 'cjs']`), and its `exports` map + adds a `require` condition so `require('@modelcontextprotocol/…')` works from + CommonJS consumers. Output extensions are normalized across all packages + (`@modelcontextprotocol/core` moves from `.js`/`.d.ts` to `.mjs`/`.d.mts`); the + public import paths are unchanged. + ## 2.0.0-beta.1 ### Patch Changes diff --git a/packages/client/package.json b/packages/client/package.json index bade926d9d..f96b681bc7 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/client", - "version": "2.0.0-beta.1", + "version": "2.0.0-beta.2", "description": "Model Context Protocol implementation for TypeScript - Client package", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", diff --git a/packages/codemod/CHANGELOG.md b/packages/codemod/CHANGELOG.md index 2268799fd8..88458c85a4 100644 --- a/packages/codemod/CHANGELOG.md +++ b/packages/codemod/CHANGELOG.md @@ -1,5 +1,18 @@ # @modelcontextprotocol/codemod +## 2.0.0-beta.2 + +### Patch Changes + +- [#2405](https://github.com/modelcontextprotocol/typescript-sdk/pull/2405) [`f172626`](https://github.com/modelcontextprotocol/typescript-sdk/commit/f172626a8e98b2ae2f0f690e4afb4dc74dbf6011) Thanks [@mattzcarey](https://github.com/mattzcarey)! - Ship CommonJS builds alongside ESM. Each package now emits both `.mjs`/`.d.mts` + and `.cjs`/`.d.cts` (via tsdown `format: ['esm', 'cjs']`), and its `exports` map + adds a `require` condition so `require('@modelcontextprotocol/…')` works from + CommonJS consumers. Output extensions are normalized across all packages + (`@modelcontextprotocol/core` moves from `.js`/`.d.ts` to `.mjs`/`.d.mts`); the + public import paths are unchanged. + +- [#2412](https://github.com/modelcontextprotocol/typescript-sdk/pull/2412) [`ef120b2`](https://github.com/modelcontextprotocol/typescript-sdk/commit/ef120b2be0c3c3d80468c3d4a9f79be30bb0c0a3) Thanks [@felixweinberger](https://github.com/felixweinberger)! - v1-to-v2 migration fixes from continued real-world migrations (codemod iterations 5). + ## 2.0.0-beta.1 ### Patch Changes diff --git a/packages/codemod/package.json b/packages/codemod/package.json index 0552fff7b3..f3c19675a6 100644 --- a/packages/codemod/package.json +++ b/packages/codemod/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/codemod", - "version": "2.0.0-beta.1", + "version": "2.0.0-beta.2", "description": "Codemod to migrate MCP TypeScript SDK code from v1 to v2", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -41,8 +41,6 @@ ], "scripts": { "typecheck": "tsgo -p tsconfig.json --noEmit", - "generate:versions": "tsx scripts/generateVersions.ts", - "prebuild": "pnpm run generate:versions", "build": "tsdown", "build:watch": "tsdown --watch", "prepack": "pnpm run build", diff --git a/packages/codemod/scripts/generateVersions.ts b/packages/codemod/scripts/generateVersions.ts deleted file mode 100644 index f4d827d76d..0000000000 --- a/packages/codemod/scripts/generateVersions.ts +++ /dev/null @@ -1,36 +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 packagesDir = path.resolve(__dirname, '../..'); - -const PACKAGE_DIRS: Record = { - '@modelcontextprotocol/client': 'client', - '@modelcontextprotocol/server': 'server', - '@modelcontextprotocol/node': 'middleware/node', - '@modelcontextprotocol/express': 'middleware/express', - '@modelcontextprotocol/server-legacy': 'server-legacy', - '@modelcontextprotocol/core': 'core' -}; - -const versions: Record = {}; - -for (const [pkg, dir] of Object.entries(PACKAGE_DIRS)) { - const pkgJsonPath = path.join(packagesDir, dir, 'package.json'); - const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf8')); - versions[pkg] = `^${pkgJson.version}`; -} - -const entries = Object.entries(versions); -const lines = entries.map(([pkg, ver], i) => ` '${pkg}': '${ver}'${i < entries.length - 1 ? ',' : ''}`).join('\n'); - -const output = `// AUTO-GENERATED — do not edit. Run \`pnpm run generate:versions\` to regenerate. -export const V2_PACKAGE_VERSIONS: Record = { -${lines} -}; -`; - -const outPath = path.resolve(__dirname, '../src/generated/versions.ts'); -writeFileSync(outPath, output); -console.log(`Wrote ${outPath}`); diff --git a/packages/codemod/src/bin/batchTest.ts b/packages/codemod/src/bin/batchTest.ts index 1a6802bc86..3d95fd23c5 100644 --- a/packages/codemod/src/bin/batchTest.ts +++ b/packages/codemod/src/bin/batchTest.ts @@ -6,10 +6,10 @@ import { tmpdir } from 'node:os'; import path from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; -import { V2_PACKAGE_VERSIONS } from '../generated/versions'; import { getMigration } from '../migrations/index'; import { run } from '../runner'; import type { Diagnostic, Migration } from '../types'; +import { V2_PACKAGE_VERSIONS } from '../versions'; // --------------------------------------------------------------------------- // Types diff --git a/packages/codemod/src/generated/versions.ts b/packages/codemod/src/generated/versions.ts deleted file mode 100644 index a82d986db8..0000000000 --- a/packages/codemod/src/generated/versions.ts +++ /dev/null @@ -1,9 +0,0 @@ -// AUTO-GENERATED — do not edit. Run `pnpm run generate:versions` to regenerate. -export const V2_PACKAGE_VERSIONS: Record = { - '@modelcontextprotocol/client': '^2.0.0-beta.1', - '@modelcontextprotocol/server': '^2.0.0-beta.1', - '@modelcontextprotocol/node': '^2.0.0-beta.1', - '@modelcontextprotocol/express': '^2.0.0-beta.1', - '@modelcontextprotocol/server-legacy': '^2.0.0-beta.1', - '@modelcontextprotocol/core': '^2.0.0-beta.1' -}; diff --git a/packages/codemod/src/utils/packageJsonUpdater.ts b/packages/codemod/src/utils/packageJsonUpdater.ts index 0a03fc1e52..c543db452d 100644 --- a/packages/codemod/src/utils/packageJsonUpdater.ts +++ b/packages/codemod/src/utils/packageJsonUpdater.ts @@ -3,8 +3,8 @@ import path from 'node:path'; import fg from 'fast-glob'; -import { V2_PACKAGE_VERSIONS } from '../generated/versions'; import type { PackageJsonChange } from '../types'; +import { V2_PACKAGE_VERSIONS } from '../versions'; import { findPackageJson } from './projectAnalyzer'; const V1_PACKAGE = '@modelcontextprotocol/sdk'; diff --git a/packages/codemod/src/versions.ts b/packages/codemod/src/versions.ts new file mode 100644 index 0000000000..64fae3a213 --- /dev/null +++ b/packages/codemod/src/versions.ts @@ -0,0 +1,20 @@ +import clientPkg from '../../client/package.json'; +import corePkg from '../../core/package.json'; +import expressPkg from '../../middleware/express/package.json'; +import nodePkg from '../../middleware/node/package.json'; +import serverPkg from '../../server/package.json'; +import serverLegacyPkg from '../../server-legacy/package.json'; + +/** + * Caret ranges for the v2 packages the codemod writes into migrated package.json + * files. Versions come straight from the workspace manifests — the bundler inlines + * them at build time — so a release bump can never leave this map stale. + */ +export const V2_PACKAGE_VERSIONS: Record = { + '@modelcontextprotocol/client': `^${clientPkg.version}`, + '@modelcontextprotocol/server': `^${serverPkg.version}`, + '@modelcontextprotocol/node': `^${nodePkg.version}`, + '@modelcontextprotocol/express': `^${expressPkg.version}`, + '@modelcontextprotocol/server-legacy': `^${serverLegacyPkg.version}`, + '@modelcontextprotocol/core': `^${corePkg.version}` +}; diff --git a/packages/core-internal/CHANGELOG.md b/packages/core-internal/CHANGELOG.md index d052594512..cce2b6f284 100644 --- a/packages/core-internal/CHANGELOG.md +++ b/packages/core-internal/CHANGELOG.md @@ -1,5 +1,11 @@ # @modelcontextprotocol/core-internal +## 2.0.0-beta.1 + +### Patch Changes + +- [#2399](https://github.com/modelcontextprotocol/typescript-sdk/pull/2399) [`3c7ddaf`](https://github.com/modelcontextprotocol/typescript-sdk/commit/3c7ddafa05d8f17fb52168bf4638f09251c3d0ff) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Return HTTP 400 for a `MissingRequiredClientCapabilityError` (`-32021`) produced after dispatch. The spec mandates `400 Bad Request` for this error with no condition on where it arose, but only the pre-dispatch capability gate honored that; the post-handler emission — the `input_required` gate rejecting an embedded request whose required capability the caller did not declare — surfaced in-band on HTTP 200. The JSON-RPC error body is unchanged, every other error code (including a handler relaying a downstream peer's `-32020`/`-32022`) keeps the origin-keyed in-band behavior, and the mapping only applies while the response is uncommitted: an exchange that already streamed — or one hosted with `responseMode: 'sse'`, which opens its stream at dispatch end — keeps its committed 200 and carries the error in-stream. + ## 2.0.0-alpha.3 ### Major Changes diff --git a/packages/core-internal/package.json b/packages/core-internal/package.json index 0239af7cb2..0514d36112 100644 --- a/packages/core-internal/package.json +++ b/packages/core-internal/package.json @@ -1,7 +1,7 @@ { "name": "@modelcontextprotocol/core-internal", "private": true, - "version": "2.0.0-beta.0", + "version": "2.0.0-beta.1", "description": "Model Context Protocol implementation for TypeScript - Core package", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 2345712ed4..5d8653d168 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,16 @@ # @modelcontextprotocol/core +## 2.0.0-beta.2 + +### Patch Changes + +- [#2405](https://github.com/modelcontextprotocol/typescript-sdk/pull/2405) [`f172626`](https://github.com/modelcontextprotocol/typescript-sdk/commit/f172626a8e98b2ae2f0f690e4afb4dc74dbf6011) Thanks [@mattzcarey](https://github.com/mattzcarey)! - Ship CommonJS builds alongside ESM. Each package now emits both `.mjs`/`.d.mts` + and `.cjs`/`.d.cts` (via tsdown `format: ['esm', 'cjs']`), and its `exports` map + adds a `require` condition so `require('@modelcontextprotocol/…')` works from + CommonJS consumers. Output extensions are normalized across all packages + (`@modelcontextprotocol/core` moves from `.js`/`.d.ts` to `.mjs`/`.d.mts`); the + public import paths are unchanged. + ## 2.0.0-beta.1 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index 7a6f115e48..66e2802e2d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/core", - "version": "2.0.0-beta.1", + "version": "2.0.0-beta.2", "description": "Model Context Protocol for TypeScript — public Zod schemas (spec + OAuth/OpenID)", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", diff --git a/packages/middleware/express/CHANGELOG.md b/packages/middleware/express/CHANGELOG.md index 2cf3441283..dd7b7f653a 100644 --- a/packages/middleware/express/CHANGELOG.md +++ b/packages/middleware/express/CHANGELOG.md @@ -1,5 +1,18 @@ # @modelcontextprotocol/express +## 2.0.0-beta.2 + +### Patch Changes + +- [#2405](https://github.com/modelcontextprotocol/typescript-sdk/pull/2405) [`f172626`](https://github.com/modelcontextprotocol/typescript-sdk/commit/f172626a8e98b2ae2f0f690e4afb4dc74dbf6011) Thanks [@mattzcarey](https://github.com/mattzcarey)! - Ship CommonJS builds alongside ESM. Each package now emits both `.mjs`/`.d.mts` + and `.cjs`/`.d.cts` (via tsdown `format: ['esm', 'cjs']`), and its `exports` map + adds a `require` condition so `require('@modelcontextprotocol/…')` works from + CommonJS consumers. Output extensions are normalized across all packages + (`@modelcontextprotocol/core` moves from `.js`/`.d.ts` to `.mjs`/`.d.mts`); the + public import paths are unchanged. +- Updated dependencies [[`f172626`](https://github.com/modelcontextprotocol/typescript-sdk/commit/f172626a8e98b2ae2f0f690e4afb4dc74dbf6011), [`3c7ddaf`](https://github.com/modelcontextprotocol/typescript-sdk/commit/3c7ddafa05d8f17fb52168bf4638f09251c3d0ff)]: + - @modelcontextprotocol/server@2.0.0-beta.2 + ## 2.0.0-beta.1 ### Patch Changes diff --git a/packages/middleware/express/package.json b/packages/middleware/express/package.json index 52c99d40a2..f1c736a2b0 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-beta.1", + "version": "2.0.0-beta.2", "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/fastify/CHANGELOG.md b/packages/middleware/fastify/CHANGELOG.md index da75b01a67..71f5747525 100644 --- a/packages/middleware/fastify/CHANGELOG.md +++ b/packages/middleware/fastify/CHANGELOG.md @@ -1,5 +1,18 @@ # @modelcontextprotocol/fastify +## 2.0.0-beta.2 + +### Patch Changes + +- [#2405](https://github.com/modelcontextprotocol/typescript-sdk/pull/2405) [`f172626`](https://github.com/modelcontextprotocol/typescript-sdk/commit/f172626a8e98b2ae2f0f690e4afb4dc74dbf6011) Thanks [@mattzcarey](https://github.com/mattzcarey)! - Ship CommonJS builds alongside ESM. Each package now emits both `.mjs`/`.d.mts` + and `.cjs`/`.d.cts` (via tsdown `format: ['esm', 'cjs']`), and its `exports` map + adds a `require` condition so `require('@modelcontextprotocol/…')` works from + CommonJS consumers. Output extensions are normalized across all packages + (`@modelcontextprotocol/core` moves from `.js`/`.d.ts` to `.mjs`/`.d.mts`); the + public import paths are unchanged. +- Updated dependencies [[`f172626`](https://github.com/modelcontextprotocol/typescript-sdk/commit/f172626a8e98b2ae2f0f690e4afb4dc74dbf6011), [`3c7ddaf`](https://github.com/modelcontextprotocol/typescript-sdk/commit/3c7ddafa05d8f17fb52168bf4638f09251c3d0ff)]: + - @modelcontextprotocol/server@2.0.0-beta.2 + ## 2.0.0-beta.1 ### Patch Changes diff --git a/packages/middleware/fastify/package.json b/packages/middleware/fastify/package.json index 84b1c91ec4..7f430b518c 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-beta.1", + "version": "2.0.0-beta.2", "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/hono/CHANGELOG.md b/packages/middleware/hono/CHANGELOG.md index 185c5a01bc..d7e9f48280 100644 --- a/packages/middleware/hono/CHANGELOG.md +++ b/packages/middleware/hono/CHANGELOG.md @@ -1,5 +1,18 @@ # @modelcontextprotocol/hono +## 2.0.0-beta.2 + +### Patch Changes + +- [#2405](https://github.com/modelcontextprotocol/typescript-sdk/pull/2405) [`f172626`](https://github.com/modelcontextprotocol/typescript-sdk/commit/f172626a8e98b2ae2f0f690e4afb4dc74dbf6011) Thanks [@mattzcarey](https://github.com/mattzcarey)! - Ship CommonJS builds alongside ESM. Each package now emits both `.mjs`/`.d.mts` + and `.cjs`/`.d.cts` (via tsdown `format: ['esm', 'cjs']`), and its `exports` map + adds a `require` condition so `require('@modelcontextprotocol/…')` works from + CommonJS consumers. Output extensions are normalized across all packages + (`@modelcontextprotocol/core` moves from `.js`/`.d.ts` to `.mjs`/`.d.mts`); the + public import paths are unchanged. +- Updated dependencies [[`f172626`](https://github.com/modelcontextprotocol/typescript-sdk/commit/f172626a8e98b2ae2f0f690e4afb4dc74dbf6011), [`3c7ddaf`](https://github.com/modelcontextprotocol/typescript-sdk/commit/3c7ddafa05d8f17fb52168bf4638f09251c3d0ff)]: + - @modelcontextprotocol/server@2.0.0-beta.2 + ## 2.0.0-beta.1 ### Patch Changes diff --git a/packages/middleware/hono/package.json b/packages/middleware/hono/package.json index e53942f33b..3c280c0ecb 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-beta.1", + "version": "2.0.0-beta.2", "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/node/CHANGELOG.md b/packages/middleware/node/CHANGELOG.md index 1ef2b9aaeb..ff35d59b7e 100644 --- a/packages/middleware/node/CHANGELOG.md +++ b/packages/middleware/node/CHANGELOG.md @@ -1,5 +1,18 @@ # @modelcontextprotocol/node +## 2.0.0-beta.2 + +### Patch Changes + +- [#2405](https://github.com/modelcontextprotocol/typescript-sdk/pull/2405) [`f172626`](https://github.com/modelcontextprotocol/typescript-sdk/commit/f172626a8e98b2ae2f0f690e4afb4dc74dbf6011) Thanks [@mattzcarey](https://github.com/mattzcarey)! - Ship CommonJS builds alongside ESM. Each package now emits both `.mjs`/`.d.mts` + and `.cjs`/`.d.cts` (via tsdown `format: ['esm', 'cjs']`), and its `exports` map + adds a `require` condition so `require('@modelcontextprotocol/…')` works from + CommonJS consumers. Output extensions are normalized across all packages + (`@modelcontextprotocol/core` moves from `.js`/`.d.ts` to `.mjs`/`.d.mts`); the + public import paths are unchanged. +- Updated dependencies [[`f172626`](https://github.com/modelcontextprotocol/typescript-sdk/commit/f172626a8e98b2ae2f0f690e4afb4dc74dbf6011), [`3c7ddaf`](https://github.com/modelcontextprotocol/typescript-sdk/commit/3c7ddafa05d8f17fb52168bf4638f09251c3d0ff)]: + - @modelcontextprotocol/server@2.0.0-beta.2 + ## 2.0.0-beta.1 ### Patch Changes diff --git a/packages/middleware/node/package.json b/packages/middleware/node/package.json index e8b22a4472..aad6227dda 100644 --- a/packages/middleware/node/package.json +++ b/packages/middleware/node/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/node", - "version": "2.0.0-beta.1", + "version": "2.0.0-beta.2", "description": "Model Context Protocol implementation for TypeScript - Node.js middleware", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", diff --git a/packages/server-legacy/CHANGELOG.md b/packages/server-legacy/CHANGELOG.md index e466d9d1f4..72c273c85d 100644 --- a/packages/server-legacy/CHANGELOG.md +++ b/packages/server-legacy/CHANGELOG.md @@ -1,5 +1,16 @@ # @modelcontextprotocol/server-legacy +## 2.0.0-beta.2 + +### Patch Changes + +- [#2405](https://github.com/modelcontextprotocol/typescript-sdk/pull/2405) [`f172626`](https://github.com/modelcontextprotocol/typescript-sdk/commit/f172626a8e98b2ae2f0f690e4afb4dc74dbf6011) Thanks [@mattzcarey](https://github.com/mattzcarey)! - Ship CommonJS builds alongside ESM. Each package now emits both `.mjs`/`.d.mts` + and `.cjs`/`.d.cts` (via tsdown `format: ['esm', 'cjs']`), and its `exports` map + adds a `require` condition so `require('@modelcontextprotocol/…')` works from + CommonJS consumers. Output extensions are normalized across all packages + (`@modelcontextprotocol/core` moves from `.js`/`.d.ts` to `.mjs`/`.d.mts`); the + public import paths are unchanged. + ## 2.0.0-beta.1 ### Patch Changes diff --git a/packages/server-legacy/package.json b/packages/server-legacy/package.json index ef2e8cf608..f571a3af95 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-beta.1", + "version": "2.0.0-beta.2", "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", diff --git a/packages/server/CHANGELOG.md b/packages/server/CHANGELOG.md index 9d4f37b992..9467763c06 100644 --- a/packages/server/CHANGELOG.md +++ b/packages/server/CHANGELOG.md @@ -1,5 +1,18 @@ # @modelcontextprotocol/server +## 2.0.0-beta.2 + +### Patch Changes + +- [#2405](https://github.com/modelcontextprotocol/typescript-sdk/pull/2405) [`f172626`](https://github.com/modelcontextprotocol/typescript-sdk/commit/f172626a8e98b2ae2f0f690e4afb4dc74dbf6011) Thanks [@mattzcarey](https://github.com/mattzcarey)! - Ship CommonJS builds alongside ESM. Each package now emits both `.mjs`/`.d.mts` + and `.cjs`/`.d.cts` (via tsdown `format: ['esm', 'cjs']`), and its `exports` map + adds a `require` condition so `require('@modelcontextprotocol/…')` works from + CommonJS consumers. Output extensions are normalized across all packages + (`@modelcontextprotocol/core` moves from `.js`/`.d.ts` to `.mjs`/`.d.mts`); the + public import paths are unchanged. + +- [#2399](https://github.com/modelcontextprotocol/typescript-sdk/pull/2399) [`3c7ddaf`](https://github.com/modelcontextprotocol/typescript-sdk/commit/3c7ddafa05d8f17fb52168bf4638f09251c3d0ff) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Return HTTP 400 for a `MissingRequiredClientCapabilityError` (`-32021`) produced after dispatch. The spec mandates `400 Bad Request` for this error with no condition on where it arose, but only the pre-dispatch capability gate honored that; the post-handler emission — the `input_required` gate rejecting an embedded request whose required capability the caller did not declare — surfaced in-band on HTTP 200. The JSON-RPC error body is unchanged, every other error code (including a handler relaying a downstream peer's `-32020`/`-32022`) keeps the origin-keyed in-band behavior, and the mapping only applies while the response is uncommitted: an exchange that already streamed — or one hosted with `responseMode: 'sse'`, which opens its stream at dispatch end — keeps its committed 200 and carries the error in-stream. + ## 2.0.0-beta.1 ### Patch Changes diff --git a/packages/server/package.json b/packages/server/package.json index 9a0750ca45..d77e533db8 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/server", - "version": "2.0.0-beta.1", + "version": "2.0.0-beta.2", "description": "Model Context Protocol implementation for TypeScript - Server package", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)",