Skip to content

Commit 353cd32

Browse files
docs: add documentation for 26 undocumented features (Tier 1 gap)
Expand existing docs and add new protocol.md to cover all 48 non-experimental MCP features required for SEP-1730 Tier 1. docs/server.md: - Tool image, audio, embedded resource, and error results - Tool change notifications - Binary resources, resource templates, subscribing/unsubscribing - Prompt image content, embedded resources, change notifications - Completions with completable() wrapper - Logging and log level docs/client.md: - stdio transport with StdioClientTransport - Roots listing and change notifications docs/capabilities.md: - Elicitation schema validation, default values, enum values - Fix experimental tasks API paths docs/protocol.md (new): - Ping, progress notifications, cancellation - Pagination, capability negotiation, protocol version negotiation - JSON Schema 2020-12 support All code snippets verified against SDK source by review agent.
1 parent f2d2145 commit 353cd32

5 files changed

Lines changed: 643 additions & 5 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ For more details on how to run these examples (including recommended commands an
154154
- [docs/server.md](docs/server.md) – building and running MCP servers, transports, tools/resources/prompts, CORS, DNS rebinding, and multi-node deployment.
155155
- [docs/client.md](docs/client.md) – using the high-level client, transports, backwards compatibility, and OAuth helpers.
156156
- [docs/capabilities.md](docs/capabilities.md) – sampling, elicitation (form and URL), and experimental task-based execution.
157+
- [docs/protocol.md](docs/protocol.md) – protocol features: ping, progress, cancellation, pagination, capability negotiation, and JSON Schema.
157158
- [docs/faq.md](docs/faq.md) – environment and troubleshooting FAQs (including Node.js Web Crypto support).
158159
- External references:
159160
- [Model Context Protocol documentation](https://modelcontextprotocol.io)

docs/capabilities.md

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,99 @@ Runnable example:
2727

2828
The `simpleStreamableHttp` server also includes a `collect-user-info` tool that demonstrates how to drive elicitation from a tool and handle the response.
2929

30+
#### Schema validation
31+
32+
Elicitation schemas support validation constraints on each field. The server validates responses automatically against the `requestedSchema` using the SDK's JSON Schema validator.
33+
34+
```typescript
35+
const result = await server.server.elicitInput({
36+
mode: 'form',
37+
message: 'Enter your details:',
38+
requestedSchema: {
39+
type: 'object',
40+
properties: {
41+
email: {
42+
type: 'string',
43+
title: 'Email',
44+
format: 'email',
45+
minLength: 5
46+
},
47+
age: {
48+
type: 'integer',
49+
title: 'Age',
50+
minimum: 0,
51+
maximum: 150
52+
}
53+
},
54+
required: ['email']
55+
}
56+
});
57+
```
58+
59+
String fields support `minLength`, `maxLength`, and `format` (`'email'`, `'uri'`, `'date'`, `'date-time'`). Number fields support `minimum` and `maximum`.
60+
61+
#### Default values
62+
63+
Schema properties can include `default` values. When the client declares the `applyDefaults` capability, the SDK automatically fills in defaults for fields the user doesn't provide.
64+
65+
> **Note:** `applyDefaults` is a TypeScript SDK extension — it is not part of the MCP protocol specification.
66+
67+
```typescript
68+
// Client declares applyDefaults:
69+
const client = new Client(
70+
{ name: 'my-client', version: '1.0.0' },
71+
{ capabilities: { elicitation: { form: { applyDefaults: true } } } }
72+
);
73+
74+
// Server schema with defaults:
75+
requestedSchema: {
76+
type: 'object',
77+
properties: {
78+
newsletter: { type: 'boolean', title: 'Newsletter', default: false },
79+
theme: { type: 'string', title: 'Theme', default: 'dark' }
80+
}
81+
}
82+
```
83+
84+
#### Enum values
85+
86+
Elicitation schemas support several enum patterns for single-select and multi-select fields:
87+
88+
```typescript
89+
requestedSchema: {
90+
type: 'object',
91+
properties: {
92+
// Simple enum (untitled options)
93+
color: {
94+
type: 'string',
95+
title: 'Favorite Color',
96+
enum: ['red', 'green', 'blue'],
97+
default: 'blue'
98+
},
99+
// Titled enum with display labels
100+
priority: {
101+
type: 'string',
102+
title: 'Priority',
103+
oneOf: [
104+
{ const: 'low', title: 'Low Priority' },
105+
{ const: 'medium', title: 'Medium Priority' },
106+
{ const: 'high', title: 'High Priority' }
107+
]
108+
},
109+
// Multi-select
110+
tags: {
111+
type: 'array',
112+
title: 'Tags',
113+
items: { type: 'string', enum: ['frontend', 'backend', 'docs'] },
114+
minItems: 1,
115+
maxItems: 3
116+
}
117+
}
118+
}
119+
```
120+
121+
For a full example with validation, defaults, and enums, see [`elicitationFormExample.ts`](../src/examples/server/elicitationFormExample.ts).
122+
30123
### URL elicitation
31124

32125
URL elicitation is designed for sensitive data and secure web‑based flows (e.g., collecting an API key, confirming a payment, or doing third‑party OAuth). Instead of returning form data, the server asks the client to open a URL and the rest of the flow happens in the browser.
@@ -46,6 +139,23 @@ Key points:
46139

47140
Sensitive information **must not** be collected via form elicitation; always use URL elicitation or out‑of‑band flows for secrets.
48141

142+
#### Complete notification
143+
144+
When a URL elicitation flow finishes (the user completes the browser-based action), the server sends a `notifications/elicitation/complete` notification to the client. This tells the client the out-of-band flow is done and any pending UI can be dismissed.
145+
146+
Use `createElicitationCompletionNotifier` on the low-level server to create a callback that sends this notification:
147+
148+
```typescript
149+
// Create a notifier for a specific elicitation:
150+
const notifyComplete = server.server.createElicitationCompletionNotifier('setup-123');
151+
152+
// Later, when the browser flow completes (e.g. via webhook):
153+
await notifyComplete();
154+
// Client receives: { method: 'notifications/elicitation/complete', params: { elicitationId: 'setup-123' } }
155+
```
156+
157+
See [`elicitationUrlExample.ts`](../src/examples/server/elicitationUrlExample.ts) for a full working example.
158+
49159
## Task-based execution (experimental)
50160

51161
Task-based execution enables “call-now, fetch-later” patterns for long-running operations. Instead of returning a result immediately, a tool creates a task that can be polled or resumed later.
@@ -70,7 +180,7 @@ For a runnable example that uses the in-memory store shipped with the SDK, see:
70180
On the client, you use:
71181

72182
- `client.experimental.tasks.callToolStream(...)` to start a tool call that may create a task and emit status updates over time.
73-
- `client.getTask(...)` and `client.getTaskResult(...)` to check status and fetch results after reconnecting.
183+
- `client.experimental.tasks.getTask(...)` and `client.experimental.tasks.getTaskResult(...)` to check status and fetch results after reconnecting.
74184

75185
The interactive client in:
76186

docs/client.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,53 @@ These examples show how to:
5858
- Perform dynamic client registration if needed.
5959
- Acquire access tokens.
6060
- Attach OAuth credentials to Streamable HTTP requests.
61+
62+
## stdio transport
63+
64+
Use `StdioClientTransport` to connect to a server that runs as a local child process:
65+
66+
```typescript
67+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
68+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
69+
70+
const transport = new StdioClientTransport({
71+
command: 'node',
72+
args: ['server.js'],
73+
env: { NODE_ENV: 'production' },
74+
cwd: '/path/to/server'
75+
});
76+
77+
const client = new Client({ name: 'my-client', version: '1.0.0' });
78+
await client.connect(transport);
79+
// connect() calls transport.start() automatically, spawning the child process
80+
```
81+
82+
The transport communicates over the child process's stdin/stdout using JSON-RPC. The `stderr` option controls where the child's stderr goes (defaults to `'inherit'`).
83+
84+
## Roots
85+
86+
Roots let a client expose filesystem locations to the server, so the server knows which directories or files are relevant. Declare the `roots` capability and register a handler:
87+
88+
```typescript
89+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
90+
import { ListRootsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
91+
92+
const client = new Client({ name: 'my-client', version: '1.0.0' }, { capabilities: { roots: { listChanged: true } } });
93+
94+
client.setRequestHandler(ListRootsRequestSchema, async () => {
95+
return {
96+
roots: [
97+
{ uri: 'file:///home/user/project', name: 'My Project' },
98+
{ uri: 'file:///home/user/data', name: 'Data Directory' }
99+
]
100+
};
101+
});
102+
```
103+
104+
When the set of roots changes, notify the server so it can re-query:
105+
106+
```typescript
107+
await client.sendRootsListChanged();
108+
```
109+
110+
The `listChanged: true` capability flag is required to send change notifications.

docs/protocol.md

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
## Protocol features
2+
3+
This page covers cross-cutting protocol mechanics that apply to both clients and servers.
4+
5+
## Ping
6+
7+
Both client and server expose a `ping()` method for health checks. The remote side responds automatically — no handler registration is needed.
8+
9+
```typescript
10+
// Client pinging the server:
11+
await client.ping();
12+
13+
// With a timeout (milliseconds):
14+
await client.ping({ timeout: 5000 });
15+
16+
// Server pinging the client (via the low-level server):
17+
await server.server.ping();
18+
```
19+
20+
## Progress notifications
21+
22+
Long-running requests can report progress to the caller. The SDK handles `progressToken` assignment automatically when you provide an `onprogress` callback.
23+
24+
**Receiving progress** (client side):
25+
26+
```typescript
27+
const result = await client.callTool({ name: 'long-task', arguments: {} }, CallToolResultSchema, {
28+
onprogress: progress => {
29+
// progress has: { progress: number, total?: number, message?: string }
30+
console.log(`${progress.progress}/${progress.total}: ${progress.message}`);
31+
},
32+
timeout: 30000,
33+
resetTimeoutOnProgress: true
34+
});
35+
```
36+
37+
**Sending progress** (server side, from a request handler):
38+
39+
```typescript
40+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
41+
for (let i = 0; i < 100; i++) {
42+
await extra.sendNotification({
43+
method: 'notifications/progress',
44+
params: {
45+
progressToken: request.params._meta?.progressToken,
46+
progress: i,
47+
total: 100,
48+
message: `Processing item ${i}`
49+
}
50+
});
51+
await doWork();
52+
}
53+
return { content: [{ type: 'text', text: 'Done' }] };
54+
});
55+
```
56+
57+
## Cancellation
58+
59+
Requests can be cancelled by the caller using an `AbortSignal`. The SDK sends a `notifications/cancelled` message to the remote side and aborts the handler via its `signal`.
60+
61+
**Client cancelling a request**:
62+
63+
```typescript
64+
const controller = new AbortController();
65+
66+
const resultPromise = client.callTool({ name: 'slow-tool', arguments: {} }, CallToolResultSchema, { signal: controller.signal });
67+
68+
// Cancel after 5 seconds:
69+
setTimeout(() => controller.abort('User cancelled'), 5000);
70+
```
71+
72+
**Server handler responding to cancellation**:
73+
74+
```typescript
75+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
76+
for (let i = 0; i < 100; i++) {
77+
extra.signal.throwIfAborted(); // throws if the client cancelled
78+
await doWork();
79+
}
80+
return { content: [{ type: 'text', text: 'Done' }] };
81+
});
82+
```
83+
84+
## Pagination
85+
86+
All list methods (`listTools`, `listPrompts`, `listResources`, `listResourceTemplates`) support cursor-based pagination. Pass `cursor` from the previous response's `nextCursor` to fetch the next page.
87+
88+
```typescript
89+
let cursor: string | undefined;
90+
const allTools: Tool[] = [];
91+
92+
do {
93+
const result = await client.listTools({ cursor });
94+
allTools.push(...result.tools);
95+
cursor = result.nextCursor;
96+
} while (cursor);
97+
```
98+
99+
The same pattern applies to `listPrompts`, `listResources`, and `listResourceTemplates`.
100+
101+
## Capability negotiation
102+
103+
Both client and server declare their capabilities during the `initialize` handshake. The SDK enforces these — attempting to use an undeclared capability throws an error.
104+
105+
**Client capabilities** are set at construction time:
106+
107+
```typescript
108+
const client = new Client(
109+
{ name: 'my-client', version: '1.0.0' },
110+
{
111+
capabilities: {
112+
roots: { listChanged: true },
113+
sampling: {},
114+
elicitation: { form: {} }
115+
}
116+
}
117+
);
118+
```
119+
120+
After connecting, inspect what the server supports:
121+
122+
```typescript
123+
await client.connect(transport);
124+
125+
const caps = client.getServerCapabilities();
126+
if (caps?.tools) {
127+
const tools = await client.listTools();
128+
}
129+
if (caps?.resources?.subscribe) {
130+
// server supports resource subscriptions
131+
}
132+
```
133+
134+
**Server capabilities** are inferred from registered handlers. When using `McpServer`, capabilities are set automatically based on what you register (tools, resources, prompts). With the low-level `Server`, you declare them in the constructor.
135+
136+
## Protocol version negotiation
137+
138+
The SDK automatically negotiates protocol versions during `initialize`. The client sends `LATEST_PROTOCOL_VERSION` and the server responds with the highest mutually supported version.
139+
140+
Supported versions are defined in `SUPPORTED_PROTOCOL_VERSIONS` (currently `2025-11-25`, `2025-06-18`, `2025-03-26`, `2024-11-05`, `2024-10-07`). If the server responds with an unsupported version, the client throws an error.
141+
142+
No user code is needed — this is handled automatically by `client.connect()`. After connecting, you can inspect the server's identity:
143+
144+
```typescript
145+
await client.connect(transport);
146+
147+
const serverVersion = client.getServerVersion();
148+
// { name: 'my-server', version: '1.0.0' }
149+
150+
const serverCaps = client.getServerCapabilities();
151+
// { tools: { listChanged: true }, resources: { subscribe: true }, ... }
152+
```
153+
154+
## JSON Schema 2020-12
155+
156+
MCP uses JSON Schema 2020-12 for tool input and output schemas. When using `McpServer` with Zod, schemas are converted to JSON Schema automatically:
157+
158+
```typescript
159+
server.registerTool(
160+
'calculate',
161+
{
162+
description: 'Add two numbers',
163+
inputSchema: { a: z.number(), b: z.number() }
164+
},
165+
async ({ a, b }) => ({
166+
content: [{ type: 'text', text: String(a + b) }]
167+
})
168+
);
169+
```
170+
171+
With the low-level `Server`, you provide JSON Schema directly:
172+
173+
```typescript
174+
{
175+
name: 'calculate',
176+
inputSchema: {
177+
type: 'object',
178+
properties: {
179+
a: { type: 'number' },
180+
b: { type: 'number' }
181+
},
182+
required: ['a', 'b']
183+
}
184+
}
185+
```
186+
187+
The SDK validates tool outputs against `outputSchema` (when provided) using a pluggable JSON Schema validator. The default validator uses Ajv; a Cloudflare Workers-compatible alternative is available via `CfWorkerJsonSchemaValidator`.

0 commit comments

Comments
 (0)