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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions DEPENDENCY_POLICY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Dependency Policy

As a library consumed by downstream projects, the MCP TypeScript SDK takes a conservative approach to dependency updates. Dependencies are kept stable unless there is a specific reason to update, such as a security vulnerability, a bug fix, or a need for new functionality.

This policy applies to every published package in this monorepo (`@modelcontextprotocol/core`, `client`, `server`, `server-legacy`, `codemod`, `node`, `express`, `hono`, `fastify`) and to the `v1.x` maintenance line (`@modelcontextprotocol/sdk`).

## Update Triggers

Dependencies are updated when:

- A **security vulnerability** is disclosed (via GitHub security alerts).
- A bug in a dependency directly affects the SDK.
- A new dependency feature is needed for SDK development.
- A dependency drops support for a Node.js version the SDK still targets.
- A new MCP specification revision requires it.

Routine version bumps without a clear motivation are avoided to minimize churn for downstream consumers.

## What We Don't Do

The SDK does not run scheduled version bumps for npm dependencies. Updating a dependency can force downstream consumers to adopt that update transitively, which can be disruptive for projects with strict dependency policies.

Dependencies are only updated when there is a concrete reason, not simply because a newer version is available.

## Automated Tooling

- **GitHub security updates** are enabled at the repository level and automatically open pull requests for npm packages with known vulnerabilities. This is a GitHub repo setting, separate from the `dependabot.yml` configuration.
- **GitHub Actions versions** are kept up to date via Dependabot on a weekly schedule (see `.github/dependabot.yml`).
- **Supply-chain cooldown**: pnpm's `minimumReleaseAge` (see `pnpm-workspace.yaml`) keeps newly published versions out of the lockfile for 7 days (`minimumReleaseAgeExclude` lists the exceptions, currently the MCP conformance suite), and only an allow-listed set of dependencies may run install scripts (`onlyBuiltDependencies`).

## Pinning and Ranges

Ranges shared by more than one package live in the pnpm workspace catalogs (`pnpm-workspace.yaml`), so a version is declared once. Runtime dependencies use caret ranges (`^`) to allow compatible updates within a major version; exact versions of third-party runtime dependencies are pinned only when necessary to work around a specific issue. Dependencies between the SDK's own packages (`workspace:*`) publish as exact pins by design, so a released `client` or `server` always resolves the `core` it was built against. Framework integrations (`express`, `hono`, `fastify`) declare the framework as a peer dependency rather than bundling a copy.

Runtime dependencies of published packages are kept to a minimum; adding one is a significant change under the discuss-before-you-code rule in `CONTRIBUTING.md`.
35 changes: 35 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Roadmap

## Spec Implementation Tracking

The SDK tracks implementation of MCP spec components via GitHub Projects, with a dedicated project board for each spec revision:

- [2026-07-28 spec revision board](https://github.com/orgs/modelcontextprotocol/projects/41) — implemented in v2.0.0 (released 2026-07-27, alongside the spec).
- [2025-11-25 spec revision board](https://github.com/orgs/modelcontextprotocol/projects/26) — implemented in v1.23.0 (2025-11-25) and carried into v2, minus the experimental tasks component (SEP-1686), which v2 does not serve.

Conformance against the 2025-11-25 and 2026-07-28 revisions runs on each push to `main` (and against 2025-11-25 on `v1.x`) via the [conformance workflow](https://github.com/modelcontextprotocol/typescript-sdk/actions/workflows/conformance.yml) using the [MCP conformance suite](https://github.com/modelcontextprotocol/conformance).

## Current Focus Areas

### v2 hardening

v2.0.0 is the stable release line (`main`). Post-release work is tracked as issues on this repository and released as 2.x patch and minor releases (see `VERSIONING.md`):

- Migration tooling and guides (`@modelcontextprotocol/codemod`, `docs/migration/`).
- Runtime coverage beyond Node.js (Bun, Deno, Cloudflare Workers, Vercel) and the framework integrations (`node`, `express`, `hono`, `fastify`).
- Documentation completeness for every non-experimental spec feature at https://ts.sdk.modelcontextprotocol.io/v2/.

### Next Spec Revision

The next MCP specification revision is being developed in the [protocol repository](https://github.com/modelcontextprotocol/modelcontextprotocol). The SDK implements accepted SEPs as they are finalized so that support ships with the spec release, with a dedicated project board tracking component-level progress for that revision.

### Extensions

Protocol extensions are implemented as they stabilize and are not part of the core tier requirements:

- Tasks (`io.modelcontextprotocol/tasks`) — [#2189](https://github.com/modelcontextprotocol/typescript-sdk/issues/2189).
- Client authentication extensions: Workload Identity Federation (SEP-1933) — [#2576](https://github.com/modelcontextprotocol/typescript-sdk/issues/2576); DPoP (SEP-1932).

### v1.x Maintenance

The `v1.x` branch (`@modelcontextprotocol/sdk`) continues to receive bug fixes and security updates for at least six months after the v2 release (2026-07-27). It targets the 2025-11-25 spec revision; new spec revisions are implemented on `main` only.
49 changes: 49 additions & 0 deletions VERSIONING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Versioning Policy

The MCP TypeScript SDK follows [Semantic Versioning 2.0.0](https://semver.org/) for every published package.

## Packages and Version Groups

The v2 SDK is a monorepo. Versions are managed with [Changesets](https://github.com/changesets/changesets) (`.changeset/`):

- `@modelcontextprotocol/core`, `client`, `server`, `server-legacy` and `codemod` form a **fixed group** and always release together with the same version.
- The framework integrations `@modelcontextprotocol/node`, `express`, `hono` and `fastify` are versioned through Changesets alongside the fixed group: they bump whenever their `@modelcontextprotocol/server` peer range has to move, and independently for their own changes.
- `@modelcontextprotocol/core-internal` is private and carries no compatibility promise; the `@modelcontextprotocol/core/internal` entry point is likewise not covered by this policy and may change in any release.
- The `v1.x` branch continues to publish `@modelcontextprotocol/sdk` 1.x under the same rules (patch releases on `release-X.Y` npm tags; see `CONTRIBUTING.md`).

## Version Format

`MAJOR.MINOR.PATCH`

- **MAJOR**: Incremented for breaking changes (see below).
- **MINOR**: Incremented for new features that are backward-compatible.
- **PATCH**: Incremented for backward-compatible bug fixes.

## What Constitutes a Breaking Change

The following changes are considered breaking and require a major version bump:

- Removing or renaming a public API export (class, function, type, or constant).
- Changing the signature of a public function or method in a way that breaks existing callers (removing parameters, changing required/optional status, changing types).
- Removing or renaming a public type or interface field.
- Changing the behavior of an existing API in a way that breaks documented contracts.
- Dropping support for a Node.js LTS version.
- Removing support for a transport type.
- Dropping support for an MCP protocol revision the SDK previously negotiated (see `docs/protocol-versions.md`).

The following are **not** considered breaking:

- Adding new optional parameters to existing functions.
- Adding new exports, types, or interfaces.
- Adding new optional fields to existing types.
- Bug fixes that correct behavior to match documented intent.
- Internal refactoring that does not affect the public API.
- Adding support for new MCP spec revisions or features.
- Changes to dev dependencies or build tooling.

## How Breaking Changes Are Communicated

1. **Changelog**: Every consumer-facing change ships with a changeset; the per-package `CHANGELOG.md` and the GitHub release for each package tag document breaking changes with migration instructions.
2. **Deprecation**: When feasible, APIs are deprecated for at least one minor release before removal using `@deprecated` JSDoc annotations, which surface warnings through TypeScript tooling and editors. Protocol features the specification deprecates stay available for as long as the specification keeps them.
3. **Migration guide**: Major version releases include a migration guide (see `docs/migration/`) and, where practical, a codemod (`@modelcontextprotocol/codemod`).
4. **PR labels**: Pull requests containing breaking changes are labeled with `breaking change`.
20 changes: 20 additions & 0 deletions docs/clients/calling.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,30 @@ The updates stream in while the call is still pending; the return type does not
[ { type: 'text', text: '2 orders exported as csv' } ]
```

## Check the connection

`ping` sends a `ping` request and resolves with the empty result the server returns; the SDK answers a `ping` on both sides automatically, so neither side registers a handler.

```ts source="../../examples/guides/clients/calling.examples.ts#ping_basic"
const pong = await client.ping({ timeout: 5000 });
console.log(pong);
```

The `orders` server answers at once:

```
{}
```

A server that stops answering rejects the call with an `SdkError` coded `REQUEST_TIMEOUT` once `timeout` elapses.

`ping` is a 2025-era method — see [Protocol versions](../protocol-versions.md).

## Recap

- `listTools`, `listResources`, `listResourceTemplates`, and `listPrompts` aggregate every page; `{ cursor }` fetches a single raw page and `listMaxPages` caps the walk.
- `callTool` returns `content` for the model and, when the tool declares an `outputSchema`, `structuredContent` for your application.
- `readResource({ uri })` and `getPrompt({ name, arguments })` follow the same list-then-fetch shape as tools.
- `complete()` returns the server's suggestions for a prompt or resource-template argument.
- `onprogress` in the request options streams progress updates without changing the call's return type.
- `ping()` checks that the server still answers; both sides answer pings automatically.
1 change: 1 addition & 0 deletions docs/protocol-versions.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ This table is the only copy of the era differences in these docs. `getProtocolEr
| `ctx.mcpReq.log()` level filter | session-scoped `logging/setLevel` | per-request `logLevel` `_meta` envelope key (absent = no logs) |
| HTTP `400` with a JSON-RPC error body | `SdkHttpError` | `ProtocolError`, delivered in-band |
| Era-mismatched spec method (outbound) | n/a | `SdkError(MethodNotSupportedByProtocolVersion)` |
| Liveness check | `client.ping()` | not defined — outbound call rejects per the era-mismatch row |

## Separate deprecation from era

Expand Down
135 changes: 134 additions & 1 deletion docs/servers/elicitation.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,45 @@ server.registerTool(
[ { type: 'text', text: 'Declined - nothing deleted.' } ]
```

## Prefill a field with a default

Set `default` on a field and the client renders the form with that value already filled in.

```ts source="../../examples/guides/servers/elicitation.examples.ts#registerTool_elicitDefault"
server.registerTool(
'export-report',
{
description: 'Export a report after the user picks a format',
inputSchema: z.object({ name: z.string() })
},
async ({ name }, ctx) => {
const result = await ctx.mcpReq.elicitInput({
mode: 'form',
message: `Export ${name} as which format?`,
requestedSchema: {
type: 'object',
properties: { format: { type: 'string', title: 'Format', enum: ['pdf', 'csv'], default: 'pdf' } },
required: ['format']
}
});
if (result.action !== 'accept') {
return { content: [{ type: 'text', text: `Export ${result.action}.` }] };
}
return { content: [{ type: 'text', text: `Exported ${name} as ${result.content?.format}.` }] };
}
);
```

`requestedSchema` reaches the client unchanged, `default` included; the end user submits the prefilled `pdf` or picks `csv`. An accept with `format` left out still returns:

```
[ { type: 'text', text: 'Exported quarterly-sales as pdf.' } ]
```

::: info
A client that declares `elicitation: { form: { applyDefaults: true } }` — an SDK flag, not a protocol capability — fills defaulted fields the end user leaves out before the accept reaches your handler; the output above is that case.
:::

## Send the end user to a URL

**URL mode** replaces the form with a browser flow: pass `url` and a unique `elicitationId` instead of `requestedSchema`.
Expand Down Expand Up @@ -155,6 +194,99 @@ The client opens the URL and answers once the end user finishes there; whatever
[ { type: 'text', text: 'Linked github.' } ]
```

## Signal that the URL flow finished

The client learns that the end user finished at the URL from a `notifications/elicitation/complete` notification that carries the same `elicitationId`. `server.server.createElicitationCompletionNotifier` returns the function that sends it — keep it where your callback endpoint can reach it, and pass `relatedRequestId` so the notification rides the in-flight tool call. Raise the request `timeout` too — the default is 60 seconds, and a person is on the other end of this one — and forward `ctx.mcpReq.signal` so a cancelled tool call also cancels the parked elicitation.

```ts source="../../examples/guides/servers/elicitation.examples.ts#createElicitationCompletionNotifier_connectCalendar"
const pendingFlows = new Map<string, () => Promise<void>>();

server.registerTool(
'connect-calendar',
{
description: 'Connect a calendar through a hosted consent flow',
inputSchema: z.object({ provider: z.string() })
},
async ({ provider }, ctx) => {
const elicitationId = crypto.randomUUID();
pendingFlows.set(
elicitationId,
server.server.createElicitationCompletionNotifier(elicitationId, { relatedRequestId: ctx.mcpReq.id })
);
try {
const result = await ctx.mcpReq.elicitInput(
{
mode: 'url',
message: `Grant ${provider} calendar access`,
url: `https://calendar.example.com/consent/${encodeURIComponent(provider)}?state=${elicitationId}`,
elicitationId
},
// a person is on the other end (the default timeout is 60 s); the signal
// cancels the parked elicitation if the tool call itself is cancelled
{ timeout: 10 * 60_000, signal: ctx.mcpReq.signal }
);
if (result.action !== 'accept') {
return { content: [{ type: 'text', text: `Consent ${result.action}.` }] };
}
return { content: [{ type: 'text', text: `Connected ${provider}.` }] };
} finally {
pendingFlows.delete(elicitationId);
}
}
);

// The hosted flow redirects back to your server with the id in `state`; that
// endpoint sends the notification.
async function completeFlow(elicitationId: string): Promise<void> {
await pendingFlows.get(elicitationId)?.();
}
```

On the client, hold the `elicitation/create` answer until the notification names the `elicitationId` the request carried, and let `ctx.mcpReq.signal` release it when the server cancels — a timed-out or abandoned flow must not leave the handler waiting.

```ts source="../../examples/guides/servers/elicitation.examples.ts#setNotificationHandler_elicitationComplete"
const finished = new Map<string, () => void>();

client.setNotificationHandler('notifications/elicitation/complete', notification => {
console.log('URL flow finished:', notification.params.elicitationId);
finished.get(notification.params.elicitationId)?.();
finished.delete(notification.params.elicitationId);
});

client.setRequestHandler('elicitation/create', async (request, ctx) => {
if (request.params.mode === 'url') {
// Open request.params.url in the user's browser; answer once the server signals completion.
const { elicitationId } = request.params;
const done = await new Promise<'complete' | 'cancelled'>(resolve => {
finished.set(elicitationId, () => resolve('complete'));
ctx.mcpReq.signal.addEventListener('abort', () => {
finished.delete(elicitationId);
resolve('cancelled');
});
});
return { action: done === 'complete' ? 'accept' : 'cancel' };
}
return { action: 'accept', content: { rating: 5, comment: 'Smooth setup' } };
});
```

The host's own `tools/call` has the same 60-second default, so the caller raises it as well:

```ts source="../../examples/guides/servers/elicitation.examples.ts#callTool_connectCalendar_timeout"
const connecting = client.callTool({ name: 'connect-calendar', arguments: { provider: 'google' } }, { timeout: 10 * 60_000 });
```

Let the callback endpoint run `completeFlow` with the id from `state`, and the client logs the notification before the tool result arrives (the id is fresh on every run):

```
URL flow finished: c9a7bcfc-acc9-494c-8ce5-44c921232ea6
[ { type: 'text', text: 'Connected google.' } ]
```

::: info
This notification exists on 2025-11-25 connections only — the 2026-07-28 [input-required](./input-required.md) flow has no `elicitationId` and no completion signal; see [Protocol versions](../protocol-versions.md).
:::

## Keep secrets out of forms

Form answers travel back through the client and land in the model's context like any other tool result.
Expand All @@ -181,5 +313,6 @@ Elicitation only works against a client that declared the `elicitation` capabili
- `ctx.mcpReq.elicitInput` sends an `elicitation/create` request mid-handler and resolves with the end user's answer.
- Form mode carries a `message` and a flat JSON-Schema `requestedSchema`; the SDK validates accepted content against it.
- `result.action` is `accept`, `decline`, or `cancel`; `result.content` is present only on accept.
- URL mode hands the end user a browser flow — use it for anything sensitive.
- `default` on a `requestedSchema` field prefills the form; a client that declares `applyDefaults` fills the field in when the end user leaves it out.
- URL mode hands the end user a browser flow — use it for anything sensitive; `createElicitationCompletionNotifier` returns the function that sends `notifications/elicitation/complete` so the client can answer.
- Calls against a client that never declared the `elicitation` capability fail before reaching the wire.
Loading
Loading