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
9 changes: 9 additions & 0 deletions .changeset/draft-spec-non-sep-conformance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@modelcontextprotocol/server': patch
'@modelcontextprotocol/client': patch
---

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.
7 changes: 7 additions & 0 deletions .changeset/sep-2577-deprecate-runtime-apis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@modelcontextprotocol/core': patch
'@modelcontextprotocol/server': patch
'@modelcontextprotocol/client': patch
---

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.
5 changes: 5 additions & 0 deletions .changeset/sep-414-trace-context-meta-keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@modelcontextprotocol/core': 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.
65 changes: 64 additions & 1 deletion docs/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ import {
SdkError,
SdkErrorCode,
SSEClientTransport,
StreamableHTTPClientTransport
StreamableHTTPClientTransport,
TRACEPARENT_META_KEY,
TRACESTATE_META_KEY
} from '@modelcontextprotocol/client';
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';
```
Expand Down Expand Up @@ -404,6 +406,9 @@ client.setNotificationHandler('notifications/resources/list_changed', async () =
});
```

> [!WARNING]
> MCP logging (including `setLoggingLevel()` and `notifications/message`) is deprecated as of protocol version 2026-07-28 (SEP-2577). It remains fully functional during the deprecation window (at least twelve months); see the [deprecated features registry](https://modelcontextprotocol.io/specification/draft/deprecated). Servers should migrate to stderr logging (STDIO) or OpenTelemetry.

To control the minimum severity of log messages the server sends, use {@linkcode @modelcontextprotocol/client!client/client.Client#setLoggingLevel | setLoggingLevel()}:

```ts source="../examples/client/src/clientGuide.examples.ts#setLoggingLevel_basic"
Expand Down Expand Up @@ -431,6 +436,9 @@ const client = new Client(

### Sampling

> [!WARNING]
> Sampling is deprecated as of protocol version 2026-07-28 (SEP-2577). It remains fully functional during the deprecation window (at least twelve months); see the [deprecated features registry](https://modelcontextprotocol.io/specification/draft/deprecated). Servers should migrate to calling LLM provider APIs directly.

When a server needs an LLM completion during tool execution, it sends a `sampling/createMessage` request to the client (see [Sampling](https://modelcontextprotocol.io/docs/learn/client-concepts#sampling) in the MCP overview). Register a handler to fulfill it:

```ts source="../examples/client/src/clientGuide.examples.ts#sampling_handler"
Expand Down Expand Up @@ -472,6 +480,9 @@ For a full form-based elicitation handler with AJV validation, see [`simpleStrea

### Roots

> [!WARNING]
> Roots are deprecated as of protocol version 2026-07-28 (SEP-2577). They remain fully functional during the deprecation window (at least twelve months); see the [deprecated features registry](https://modelcontextprotocol.io/specification/draft/deprecated). Migrate to passing paths via tool parameters, resource URIs, or configuration.

Roots let the client expose filesystem boundaries to the server (see [Roots](https://modelcontextprotocol.io/docs/learn/client-concepts#roots) in the MCP overview). Declare the `roots` capability and register a `roots/list` handler:

```ts source="../examples/client/src/clientGuide.examples.ts#roots_handler"
Expand Down Expand Up @@ -571,6 +582,58 @@ const transport = new StreamableHTTPClientTransport(new URL('http://localhost:30
});
```

## Trace context propagation

The MCP specification ([SEP-414](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/414)) reserves the unprefixed `_meta` keys `traceparent`, `tracestate`, and `baggage` for distributed trace context, as an exception to the usual `_meta` key prefix rule. When present, the values must follow the [W3C Trace Context](https://www.w3.org/TR/trace-context/) and [W3C Baggage](https://www.w3.org/TR/baggage/) formats. The SDK does not interpret these keys — `_meta` passes through both directions untouched — so you can propagate OpenTelemetry context across any transport, including stdio where HTTP headers are unavailable. The key names are exported as `TRACEPARENT_META_KEY`, `TRACESTATE_META_KEY`, and `BAGGAGE_META_KEY`.

Attach trace context to a single request via `_meta`:

```ts source="../examples/client/src/clientGuide.examples.ts#traceContext_perRequest"
// Values would normally come from your tracer's active span context.
const result = await client.callTool({
name: 'calculate-bmi',
arguments: { weightKg: 70, heightM: 1.75 },
_meta: {
[TRACEPARENT_META_KEY]: '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01',
[TRACESTATE_META_KEY]: 'vendor1=opaqueValue1'
}
});
console.log(result.content);
```

Or inject it into every outgoing request with fetch middleware (Streamable HTTP transport):

```ts source="../examples/client/src/clientGuide.examples.ts#traceContext_middleware"
const traceContextMiddleware = createMiddleware(async (next, input, init) => {
if (typeof init?.body !== 'string') {
return next(input, init);
}
const message = JSON.parse(init.body) as {
method?: string;
params?: { _meta?: Record<string, unknown>; [key: string]: unknown };
};
// Only requests and notifications carry params._meta; skip responses.
if (message.method === undefined) {
return next(input, init);
}
message.params = {
...message.params,
_meta: {
...message.params?._meta,
// Replace with values from your tracer's active span context.
[TRACEPARENT_META_KEY]: '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'
}
};
return next(input, { ...init, body: JSON.stringify(message) });
});

const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'), {
fetch: applyMiddlewares(traceContextMiddleware)(fetch)
});
```

On the server side, handlers can read the incoming trace context from `ctx.mcpReq._meta` — see the [server guide](./server.md#trace-context-propagation).

## Resumption tokens

When using SSE-based streaming, the server can assign event IDs. Pass `onresumptiontoken` to track them, and `resumptionToken` to resume from where you left off after a disconnection:
Expand Down
42 changes: 41 additions & 1 deletion docs/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { randomUUID } from 'node:crypto';
import { createMcpExpressApp } from '@modelcontextprotocol/express';
import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node';
import type { CallToolResult, ResourceLink } from '@modelcontextprotocol/server';
import { completable, McpServer, ResourceTemplate } from '@modelcontextprotocol/server';
import { completable, McpServer, ResourceTemplate, TRACEPARENT_META_KEY } from '@modelcontextprotocol/server';
import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';
import * as z from 'zod/v4';
```
Expand Down Expand Up @@ -252,6 +252,9 @@ server.registerResource(
);
```

> [!IMPORTANT]
> **Security note:** If a resource is backed by the filesystem (for example, a `file://` server or a template whose variables map onto file paths), the spec requires sanitizing any user-influenced path before use. Resolve the requested path and verify it stays within the intended root directory, rejecting traversal sequences such as `..` (including encoded forms) and symlinks that escape the root. Never pass template variables or client-supplied URIs to filesystem APIs unchecked.

## Prompts

Prompts are reusable templates that help structure interactions with models (see [Prompts](https://modelcontextprotocol.io/docs/learn/server-concepts#prompts) in the MCP overview). Use a prompt when you want to offer a canned interaction pattern that users invoke explicitly; use a [tool](#tools) when the LLM should decide when to call it.
Expand Down Expand Up @@ -312,6 +315,9 @@ server.registerPrompt(

## Logging

> [!WARNING]
> MCP logging is deprecated as of protocol version 2026-07-28 (SEP-2577). It remains fully functional during the deprecation window (at least twelve months); see the [deprecated features registry](https://modelcontextprotocol.io/specification/draft/deprecated). Migrate to stderr logging (STDIO servers) or OpenTelemetry.

Logging lets your server send structured diagnostics — debug traces, progress updates, warnings — to the connected client as notifications (see [Logging](https://modelcontextprotocol.io/specification/latest/server/utilities/logging) in the MCP specification).

Declare the `logging` capability, then call `ctx.mcpReq.log(level, data)` (from {@linkcode @modelcontextprotocol/server!index.ServerContext | ServerContext}) inside any handler:
Expand Down Expand Up @@ -378,12 +384,43 @@ server.registerTool(

`progress` must increase on each call. `total` and `message` are optional. If the client does not provide a `progressToken`, skip the notification.

## Trace context propagation

The MCP specification ([SEP-414](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/414)) reserves the unprefixed `_meta` keys `traceparent`, `tracestate`, and `baggage` for distributed trace context, as an exception to the usual `_meta` key prefix rule. When present, the values must follow the [W3C Trace Context](https://www.w3.org/TR/trace-context/) and [W3C Baggage](https://www.w3.org/TR/baggage/) formats. The SDK does not interpret these keys — `_meta` passes through untouched on any transport, including stdio. The key names are exported as `TRACEPARENT_META_KEY`, `TRACESTATE_META_KEY`, and `BAGGAGE_META_KEY`.

Read the caller's trace context from `ctx.mcpReq._meta` in a handler:

```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_traceContext"
server.registerTool(
'traced-operation',
{
description: 'Operation that participates in distributed tracing',
inputSchema: z.object({ query: z.string() })
},
async ({ query }, ctx): Promise<CallToolResult> => {
// e.g. '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'
const traceparent = ctx.mcpReq._meta?.[TRACEPARENT_META_KEY];
if (typeof traceparent === 'string') {
// Continue the caller's trace, e.g. start a child span with your
// OpenTelemetry tracer using this trace context.
}

return { content: [{ type: 'text', text: `Results for ${query}` }] };
}
);
```

To propagate context onward (for example on a server-initiated sampling request, or back on a response), set the same keys in the outgoing `_meta`. See the [client guide](./client.md#trace-context-propagation) for injecting trace context on the client side.

## Server-initiated requests

MCP is bidirectional — servers can send requests *to* the client during tool execution, as long as the client declares matching capabilities (see [Architecture](https://modelcontextprotocol.io/docs/learn/architecture) in the MCP overview).

### Sampling

> [!WARNING]
> Sampling is deprecated as of protocol version 2026-07-28 (SEP-2577). It remains fully functional during the deprecation window (at least twelve months); see the [deprecated features registry](https://modelcontextprotocol.io/specification/draft/deprecated). Migrate to calling LLM provider APIs directly from your server.

Sampling lets a tool handler request an LLM completion from the connected client — the handler describes a prompt and the client returns the model's response (see [Sampling](https://modelcontextprotocol.io/docs/learn/client-concepts#sampling) in the MCP overview). Use sampling when a tool needs the model to generate or transform text mid-execution.

Call `ctx.mcpReq.requestSampling(params)` (from {@linkcode @modelcontextprotocol/server!index.ServerContext | ServerContext}) inside a tool handler:
Expand Down Expand Up @@ -478,6 +515,9 @@ For runnable examples, see [`elicitationFormExample.ts`](https://github.com/mode

### Roots

> [!WARNING]
> Roots are deprecated as of protocol version 2026-07-28 (SEP-2577). They remain fully functional during the deprecation window (at least twelve months); see the [deprecated features registry](https://modelcontextprotocol.io/specification/draft/deprecated). Migrate to passing paths via tool parameters, resource URIs, or configuration.

Roots let a tool handler discover the client's workspace directories — for example, to scope a file search or identify project boundaries (see [Roots](https://modelcontextprotocol.io/docs/learn/client-concepts#roots) in the MCP overview). Call {@linkcode @modelcontextprotocol/server!server/server.Server#listRoots | server.server.listRoots()} (requires the client to declare the `roots` capability):

```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_roots"
Expand Down
55 changes: 54 additions & 1 deletion examples/client/src/clientGuide.examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import {
SdkError,
SdkErrorCode,
SSEClientTransport,
StreamableHTTPClientTransport
StreamableHTTPClientTransport,
TRACEPARENT_META_KEY,
TRACESTATE_META_KEY
} from '@modelcontextprotocol/client';
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';
//#endregion imports
Expand Down Expand Up @@ -522,6 +524,55 @@ async function middleware_basic() {
return transport;
}

/** Example: Attach W3C Trace Context to a single request via `_meta`. */
async function traceContext_perRequest(client: Client) {
//#region traceContext_perRequest
// Values would normally come from your tracer's active span context.
const result = await client.callTool({
name: 'calculate-bmi',
arguments: { weightKg: 70, heightM: 1.75 },
_meta: {
[TRACEPARENT_META_KEY]: '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01',
[TRACESTATE_META_KEY]: 'vendor1=opaqueValue1'
}
});
console.log(result.content);
//#endregion traceContext_perRequest
}

/** Example: Client middleware that injects trace context into every outgoing request. */
async function traceContext_middleware() {
//#region traceContext_middleware
const traceContextMiddleware = createMiddleware(async (next, input, init) => {
if (typeof init?.body !== 'string') {
return next(input, init);
}
const message = JSON.parse(init.body) as {
method?: string;
params?: { _meta?: Record<string, unknown>; [key: string]: unknown };
};
// Only requests and notifications carry params._meta; skip responses.
if (message.method === undefined) {
return next(input, init);
}
message.params = {
...message.params,
_meta: {
...message.params?._meta,
// Replace with values from your tracer's active span context.
[TRACEPARENT_META_KEY]: '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'
}
};
return next(input, { ...init, body: JSON.stringify(message) });
});

const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'), {
fetch: applyMiddlewares(traceContextMiddleware)(fetch)
});
//#endregion traceContext_middleware
return transport;
}

/** Example: Track resumption tokens for SSE reconnection. */
async function resumptionToken_basic(client: Client) {
//#region resumptionToken_basic
Expand Down Expand Up @@ -572,4 +623,6 @@ void errorHandling_toolErrors;
void errorHandling_lifecycle;
void errorHandling_timeout;
void middleware_basic;
void traceContext_perRequest;
void traceContext_middleware;
void resumptionToken_basic;
26 changes: 25 additions & 1 deletion examples/server/src/serverGuide.examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { randomUUID } from 'node:crypto';
import { createMcpExpressApp } from '@modelcontextprotocol/express';
import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node';
import type { CallToolResult, ResourceLink } from '@modelcontextprotocol/server';
import { completable, McpServer, ResourceTemplate } from '@modelcontextprotocol/server';
import { completable, McpServer, ResourceTemplate, TRACEPARENT_META_KEY } from '@modelcontextprotocol/server';
import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';
import * as z from 'zod/v4';
//#endregion imports
Expand Down Expand Up @@ -319,6 +319,29 @@ function registerTool_progress(server: McpServer) {
//#endregion registerTool_progress
}

/** Example: Tool that reads W3C Trace Context from request `_meta`. */
function registerTool_traceContext(server: McpServer) {
//#region registerTool_traceContext
server.registerTool(
'traced-operation',
{
description: 'Operation that participates in distributed tracing',
inputSchema: z.object({ query: z.string() })
},
async ({ query }, ctx): Promise<CallToolResult> => {
// e.g. '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'
const traceparent = ctx.mcpReq._meta?.[TRACEPARENT_META_KEY];
if (typeof traceparent === 'string') {
// Continue the caller's trace, e.g. start a child span with your
// OpenTelemetry tracer using this trace context.
}

return { content: [{ type: 'text', text: `Results for ${query}` }] };
}
);
//#endregion registerTool_traceContext
}

// ---------------------------------------------------------------------------
// Server-initiated requests
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -543,6 +566,7 @@ void registerTool_errorHandling;
void registerTool_annotations;
void registerTool_logging;
void registerTool_progress;
void registerTool_traceContext;
void registerTool_sampling;
void registerTool_elicitation;
void registerTool_roots;
Expand Down
Loading