Dissolve capabilities.md into server.md and client.md (#1532)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
dc896e198b
commit
284a4135f2
9 changed files with 336 additions and 161 deletions
|
|
@ -127,9 +127,8 @@ Next steps:
|
|||
## Documentation
|
||||
|
||||
- Local SDK docs:
|
||||
- [docs/server.md](docs/server.md) – building MCP servers, transports, tools/resources/prompts, CORS, DNS rebinding, and deployment patterns.
|
||||
- [docs/client.md](docs/client.md) – using the high-level client, transports, backwards compatibility, and OAuth helpers.
|
||||
- [docs/capabilities.md](docs/capabilities.md) – sampling, elicitation (form and URL), and experimental task-based execution.
|
||||
- [docs/server.md](docs/server.md) – building MCP servers, transports, tools/resources/prompts, sampling, elicitation, tasks, and deployment patterns.
|
||||
- [docs/client.md](docs/client.md) – using the high-level client, transports, OAuth helpers, handling server‑initiated requests, and tasks.
|
||||
- [docs/faq.md](docs/faq.md) – environment and troubleshooting FAQs (including Node.js Web Crypto support).
|
||||
- External references:
|
||||
- [SDK API documentation](https://modelcontextprotocol.github.io/typescript-sdk/)
|
||||
|
|
|
|||
|
|
@ -1,85 +0,0 @@
|
|||
---
|
||||
title: Capabilities
|
||||
---
|
||||
|
||||
## Sampling
|
||||
|
||||
MCP servers can request LLM completions from connected clients that support the sampling capability. This lets your tools offload summarisation or generation to the client’s model.
|
||||
|
||||
For a runnable server that combines tools, logging and tasks, see:
|
||||
|
||||
- [`toolWithSampleServer.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/toolWithSampleServer.ts)
|
||||
|
||||
In practice you will:
|
||||
|
||||
- Declare the sampling capability on the client.
|
||||
- Call `server.server.createMessage(...)` from within a tool handler.
|
||||
- Return the model’s response as structured content and/or text.
|
||||
|
||||
Refer to the MCP spec’s sampling section for full request/response details.
|
||||
|
||||
## Elicitation
|
||||
|
||||
### Form elicitation
|
||||
|
||||
Form elicitation lets a tool ask the user for additional, **non‑sensitive** information via a schema‑driven form. The server sends a schema and message, and the client is responsible for collecting and returning the data.
|
||||
|
||||
Runnable example:
|
||||
|
||||
- Server: [`elicitationFormExample.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/elicitationFormExample.ts)
|
||||
- Client‑side handling: [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleStreamableHttp.ts)
|
||||
|
||||
The `simpleStreamableHttp` server also includes a `collect-user-info` tool that demonstrates how to drive elicitation from a tool and handle the response.
|
||||
|
||||
### URL elicitation
|
||||
|
||||
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.
|
||||
|
||||
Runnable example:
|
||||
|
||||
- Server: [`elicitationUrlExample.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/elicitationUrlExample.ts)
|
||||
- Client: [`elicitationUrlExample.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/elicitationUrlExample.ts)
|
||||
|
||||
Key points:
|
||||
|
||||
- Use `mode: 'url'` when calling `server.server.elicitInput(...)`.
|
||||
- Implement a client‑side handler for `ElicitRequestSchema` that:
|
||||
- Shows the full URL and reason to the user.
|
||||
- Asks for explicit consent.
|
||||
- Opens the URL in the system browser.
|
||||
|
||||
Sensitive information **must not** be collected via form elicitation; always use URL elicitation or out‑of‑band flows for secrets.
|
||||
|
||||
## Task-based execution (experimental)
|
||||
|
||||
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.
|
||||
|
||||
The APIs live under the experimental `.experimental.tasks` namespace and may change without notice.
|
||||
|
||||
### Server-side concepts
|
||||
|
||||
On the server you will:
|
||||
|
||||
- Provide a `TaskStore` implementation that persists task metadata and results.
|
||||
- Enable the `tasks` capability when constructing the server.
|
||||
- Register tools with `server.experimental.tasks.registerToolTask(...)`.
|
||||
|
||||
For a runnable example that uses the in-memory store shipped with the SDK, see:
|
||||
|
||||
- [`toolWithSampleServer.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/toolWithSampleServer.ts)
|
||||
- `packages/core/src/experimental/tasks/stores/in-memory.ts`
|
||||
|
||||
### Client-side usage
|
||||
|
||||
On the client, you use:
|
||||
|
||||
- `client.experimental.tasks.callToolStream(...)` to start a tool call that may create a task and emit status updates over time.
|
||||
- `client.getTask(...)` and `client.getTaskResult(...)` to check status and fetch results after reconnecting.
|
||||
|
||||
The interactive client in:
|
||||
|
||||
- [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleStreamableHttp.ts)
|
||||
|
||||
includes commands to demonstrate calling tools that support tasks and handling their lifecycle.
|
||||
|
||||
See the MCP spec’s tasks section and the example server/client above for a full walkthrough of the task status lifecycle and TTL handling.
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
title: Client
|
||||
---
|
||||
|
||||
## Client overview
|
||||
# Client overview
|
||||
|
||||
This guide covers SDK usage for building MCP clients in TypeScript. For protocol-level details and message formats, see the [MCP specification](https://modelcontextprotocol.io/specification/latest/).
|
||||
|
||||
|
|
@ -318,7 +318,7 @@ client.setRequestHandler('elicitation/create', async request => {
|
|||
```
|
||||
|
||||
> [!NOTE]
|
||||
> For a full form‑based elicitation handler with AJV validation, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleStreamableHttp.ts). For URL elicitation mode, see [`elicitationUrlExample.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/elicitationUrlExample.ts) and the [Capabilities guide](capabilities.md#elicitation).
|
||||
> For a full form‑based elicitation handler with AJV validation, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleStreamableHttp.ts). For URL elicitation mode (`mode: 'url'`), see [`elicitationUrlExample.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/elicitationUrlExample.ts).
|
||||
>
|
||||
> For protocol details, see [Elicitation](https://modelcontextprotocol.io/specification/latest/client/elicitation) in the MCP specification.
|
||||
|
||||
|
|
@ -367,6 +367,19 @@ console.log(result);
|
|||
> [!NOTE]
|
||||
> For an end‑to‑end example of server‑initiated SSE disconnection and automatic client reconnection with event replay, see [`ssePollingClient.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/ssePollingClient.ts).
|
||||
|
||||
## Tasks (experimental)
|
||||
|
||||
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. To use tasks:
|
||||
|
||||
- Call {@linkcode @modelcontextprotocol/client!experimental/tasks/client.ExperimentalClientTasks#callToolStream | client.experimental.tasks.callToolStream(...)} to start a tool call that may create a task and emit status updates over time.
|
||||
- Call {@linkcode @modelcontextprotocol/client!experimental/tasks/client.ExperimentalClientTasks#getTask | client.experimental.tasks.getTask(...)} and {@linkcode @modelcontextprotocol/client!experimental/tasks/client.ExperimentalClientTasks#getTaskResult | getTaskResult(...)} to check status and fetch results after reconnecting.
|
||||
|
||||
> [!NOTE]
|
||||
> For a full runnable example, see [`simpleTaskInteractiveClient.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleTaskInteractiveClient.ts).
|
||||
|
||||
> [!WARNING]
|
||||
> The tasks API is experimental and may change without notice.
|
||||
|
||||
## More client features
|
||||
|
||||
The sections above cover the essentials. The table below links to additional capabilities.
|
||||
|
|
@ -377,4 +390,3 @@ The sections above cover the essentials. The table below links to additional cap
|
|||
| SSE disconnect / reconnection | Server‑initiated SSE disconnect with automatic reconnection and event replay | [`ssePollingClient.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/ssePollingClient.ts) |
|
||||
| Multiple clients | Independent client lifecycles to the same server | [`multipleClientsParallel.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/multipleClientsParallel.ts) |
|
||||
| URL elicitation | Handle sensitive data collection via browser | [`elicitationUrlExample.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/elicitationUrlExample.ts) |
|
||||
| Tasks (experimental) | Long‑running tool calls with status streaming | [`simpleTaskInteractiveClient.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleTaskInteractiveClient.ts), [Capabilities guide](capabilities.md#task-based-execution-experimental) |
|
||||
|
|
|
|||
|
|
@ -3,13 +3,11 @@ title: Documents
|
|||
children:
|
||||
- ./server.md
|
||||
- ./client.md
|
||||
- ./capabilities.md
|
||||
- ./faq.md
|
||||
---
|
||||
|
||||
# Documents
|
||||
|
||||
- [Server](./server.md) – building MCP servers, transports, tools/resources/prompts, and deployment patterns
|
||||
- [Client](./client.md) – using the high-level client, transports, backwards compatibility, and OAuth helpers
|
||||
- [Capabilities](./capabilities.md) – sampling, elicitation, and experimental task-based execution
|
||||
- [Server](./server.md) – building MCP servers, transports, tools/resources/prompts, sampling, elicitation, tasks, and deployment patterns
|
||||
- [Client](./client.md) – using the high-level client, transports, OAuth helpers, handling server‑initiated requests, and tasks
|
||||
- [FAQ](./faq.md) – frequently asked questions and troubleshooting
|
||||
|
|
|
|||
235
docs/server.md
235
docs/server.md
|
|
@ -2,7 +2,7 @@
|
|||
title: Server
|
||||
---
|
||||
|
||||
## Server overview
|
||||
# Server overview
|
||||
|
||||
This guide covers SDK usage for building MCP servers in TypeScript. For protocol-level details and message formats, see the [MCP specification](https://modelcontextprotocol.io/specification/latest/).
|
||||
|
||||
|
|
@ -92,30 +92,6 @@ const transport = new StdioServerTransport();
|
|||
await server.connect(transport);
|
||||
```
|
||||
|
||||
## DNS rebinding protection
|
||||
|
||||
MCP servers running on localhost are vulnerable to DNS rebinding attacks. Use `createMcpExpressApp()` from `@modelcontextprotocol/express` to create an Express app with DNS rebinding protection enabled by default:
|
||||
|
||||
```ts source="../examples/server/src/serverGuide.examples.ts#dnsRebinding_basic"
|
||||
// Default: DNS rebinding protection auto-enabled (host is 127.0.0.1)
|
||||
const app = createMcpExpressApp();
|
||||
|
||||
// DNS rebinding protection also auto-enabled for localhost
|
||||
const appLocal = createMcpExpressApp({ host: 'localhost' });
|
||||
|
||||
// No automatic protection when binding to all interfaces
|
||||
const appOpen = createMcpExpressApp({ host: '0.0.0.0' });
|
||||
```
|
||||
|
||||
When binding to `0.0.0.0` / `::`, provide an allow-list of hosts:
|
||||
|
||||
```ts source="../examples/server/src/serverGuide.examples.ts#dnsRebinding_allowedHosts"
|
||||
const app = createMcpExpressApp({
|
||||
host: '0.0.0.0',
|
||||
allowedHosts: ['localhost', '127.0.0.1', 'myhost.local']
|
||||
});
|
||||
```
|
||||
|
||||
## Tools, resources, and prompts
|
||||
|
||||
### Tools
|
||||
|
|
@ -185,37 +161,12 @@ server.registerTool(
|
|||
> [!NOTE]
|
||||
> For a full runnable example with `ResourceLink` outputs, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts).
|
||||
|
||||
#### Logging
|
||||
#### Tool annotations
|
||||
|
||||
Use `ctx.mcpReq.log(level, data)` (from {@linkcode @modelcontextprotocol/server!index.ServerContext | ServerContext}) inside a tool handler to send structured log messages to the client. The server must declare the `logging` capability:
|
||||
|
||||
```ts source="../examples/server/src/serverGuide.examples.ts#logging_capability"
|
||||
const server = new McpServer({ name: 'my-server', version: '1.0.0' }, { capabilities: { logging: {} } });
|
||||
```
|
||||
|
||||
Then log from any tool callback:
|
||||
|
||||
```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_logging"
|
||||
server.registerTool(
|
||||
'fetch-data',
|
||||
{
|
||||
description: 'Fetch data from an API',
|
||||
inputSchema: z.object({ url: z.string() })
|
||||
},
|
||||
async ({ url }, ctx): Promise<CallToolResult> => {
|
||||
await ctx.mcpReq.log('info', `Fetching ${url}`);
|
||||
const res = await fetch(url);
|
||||
await ctx.mcpReq.log('debug', `Response status: ${res.status}`);
|
||||
const text = await res.text();
|
||||
return { content: [{ type: 'text', text }] };
|
||||
}
|
||||
);
|
||||
```
|
||||
Tools can include annotations that hint at their behavior — for example, whether a tool is read‑only, destructive, or idempotent. Annotations help clients present tools appropriately without changing their execution semantics.
|
||||
|
||||
> [!NOTE]
|
||||
> For logging in a full server, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts) and [`jsonResponseStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/jsonResponseStreamableHttp.ts).
|
||||
>
|
||||
> For protocol details, see [Logging](https://modelcontextprotocol.io/specification/latest/server/utilities/logging) in the MCP specification.
|
||||
> For tool annotations in a full server, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts).
|
||||
|
||||
### Resources
|
||||
|
||||
|
|
@ -337,11 +288,181 @@ server.registerPrompt(
|
|||
);
|
||||
```
|
||||
|
||||
For client-side completion usage, see the [Client guide](client.md).
|
||||
### Logging
|
||||
|
||||
Unlike tools, resources, and prompts, logging is not a registered primitive — it is a handler-level API available inside any callback. Use `ctx.mcpReq.log(level, data)` (from {@linkcode @modelcontextprotocol/server!index.ServerContext | ServerContext}) to send structured log messages to the client. The server must declare the `logging` capability:
|
||||
|
||||
```ts source="../examples/server/src/serverGuide.examples.ts#logging_capability"
|
||||
const server = new McpServer({ name: 'my-server', version: '1.0.0' }, { capabilities: { logging: {} } });
|
||||
```
|
||||
|
||||
Then log from any handler callback:
|
||||
|
||||
```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_logging"
|
||||
server.registerTool(
|
||||
'fetch-data',
|
||||
{
|
||||
description: 'Fetch data from an API',
|
||||
inputSchema: z.object({ url: z.string() })
|
||||
},
|
||||
async ({ url }, ctx): Promise<CallToolResult> => {
|
||||
await ctx.mcpReq.log('info', `Fetching ${url}`);
|
||||
const res = await fetch(url);
|
||||
await ctx.mcpReq.log('debug', `Response status: ${res.status}`);
|
||||
const text = await res.text();
|
||||
return { content: [{ type: 'text', text }] };
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> For logging in a full server, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts) and [`jsonResponseStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/jsonResponseStreamableHttp.ts).
|
||||
>
|
||||
> For protocol details, see [Logging](https://modelcontextprotocol.io/specification/latest/server/utilities/logging) in the MCP specification.
|
||||
|
||||
## Server‑initiated requests
|
||||
|
||||
MCP is bidirectional — servers can also send requests *to* the client during tool execution, as long as the client declares matching capabilities.
|
||||
|
||||
### Sampling
|
||||
|
||||
Use `ctx.mcpReq.requestSampling(params)` (from {@linkcode @modelcontextprotocol/server!index.ServerContext | ServerContext}) inside a tool handler to request an LLM completion from the connected client:
|
||||
|
||||
```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_sampling"
|
||||
server.registerTool(
|
||||
'summarize',
|
||||
{
|
||||
description: 'Summarize text using the client LLM',
|
||||
inputSchema: z.object({ text: z.string() })
|
||||
},
|
||||
async ({ text }, ctx): Promise<CallToolResult> => {
|
||||
const response = await ctx.mcpReq.requestSampling({
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: {
|
||||
type: 'text',
|
||||
text: `Please summarize:\n\n${text}`
|
||||
}
|
||||
}
|
||||
],
|
||||
maxTokens: 500
|
||||
});
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Model (${response.model}): ${JSON.stringify(response.content)}`
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> For a full runnable example, see [`toolWithSampleServer.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/toolWithSampleServer.ts).
|
||||
>
|
||||
> For protocol details, see [Sampling](https://modelcontextprotocol.io/specification/latest/client/sampling) in the MCP specification.
|
||||
|
||||
### Elicitation
|
||||
|
||||
Use `ctx.mcpReq.elicitInput(params)` (from {@linkcode @modelcontextprotocol/server!index.ServerContext | ServerContext}) inside a tool handler to request user input. Elicitation supports two modes:
|
||||
|
||||
- **Form** (`mode: 'form'`) — collects **non‑sensitive** data via a schema‑driven form.
|
||||
- **URL** (`mode: 'url'`) — for sensitive data or secure web‑based flows (API keys, payments, OAuth). The client opens a URL in the browser.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Sensitive information **must not** be collected via form elicitation; always use URL elicitation or out‑of‑band flows for secrets.
|
||||
|
||||
```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_elicitation"
|
||||
server.registerTool(
|
||||
'collect-feedback',
|
||||
{
|
||||
description: 'Collect user feedback via a form',
|
||||
inputSchema: z.object({})
|
||||
},
|
||||
async (_args, ctx): Promise<CallToolResult> => {
|
||||
const result = await ctx.mcpReq.elicitInput({
|
||||
mode: 'form',
|
||||
message: 'Please share your feedback:',
|
||||
requestedSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
rating: {
|
||||
type: 'number',
|
||||
title: 'Rating (1\u20135)',
|
||||
minimum: 1,
|
||||
maximum: 5
|
||||
},
|
||||
comment: { type: 'string', title: 'Comment' }
|
||||
},
|
||||
required: ['rating']
|
||||
}
|
||||
});
|
||||
if (result.action === 'accept') {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Thanks! ${JSON.stringify(result.content)}`
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
return { content: [{ type: 'text', text: 'Feedback declined.' }] };
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> For runnable examples, see [`elicitationFormExample.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/elicitationFormExample.ts) (form mode) and [`elicitationUrlExample.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/elicitationUrlExample.ts) (URL mode).
|
||||
>
|
||||
> For protocol details, see [Elicitation](https://modelcontextprotocol.io/specification/latest/client/elicitation) in the MCP specification.
|
||||
|
||||
## Tasks (experimental)
|
||||
|
||||
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. To use tasks:
|
||||
|
||||
- Provide a {@linkcode @modelcontextprotocol/server!index.TaskStore | TaskStore} implementation that persists task metadata and results (see {@linkcode @modelcontextprotocol/server!index.InMemoryTaskStore | InMemoryTaskStore} for reference).
|
||||
- Enable the `tasks` capability when constructing the server.
|
||||
- Register tools with {@linkcode @modelcontextprotocol/server!experimental/tasks/mcpServer.ExperimentalMcpServerTasks#registerToolTask | server.experimental.tasks.registerToolTask(...)}.
|
||||
|
||||
> [!NOTE]
|
||||
> For a full runnable example, see [`simpleTaskInteractive.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleTaskInteractive.ts).
|
||||
|
||||
> [!WARNING]
|
||||
> The tasks API is experimental and may change without notice.
|
||||
|
||||
## Deployment
|
||||
|
||||
### DNS rebinding protection
|
||||
|
||||
MCP servers running on localhost are vulnerable to DNS rebinding attacks. Use `createMcpExpressApp()` from `@modelcontextprotocol/express` to create an Express app with DNS rebinding protection enabled by default:
|
||||
|
||||
```ts source="../examples/server/src/serverGuide.examples.ts#dnsRebinding_basic"
|
||||
// Default: DNS rebinding protection auto-enabled (host is 127.0.0.1)
|
||||
const app = createMcpExpressApp();
|
||||
|
||||
// DNS rebinding protection also auto-enabled for localhost
|
||||
const appLocal = createMcpExpressApp({ host: 'localhost' });
|
||||
|
||||
// No automatic protection when binding to all interfaces
|
||||
const appOpen = createMcpExpressApp({ host: '0.0.0.0' });
|
||||
```
|
||||
|
||||
When binding to `0.0.0.0` / `::`, provide an allow-list of hosts:
|
||||
|
||||
```ts source="../examples/server/src/serverGuide.examples.ts#dnsRebinding_allowedHosts"
|
||||
const app = createMcpExpressApp({
|
||||
host: '0.0.0.0',
|
||||
allowedHosts: ['localhost', '127.0.0.1', 'myhost.local']
|
||||
});
|
||||
```
|
||||
|
||||
## More server features
|
||||
|
||||
The sections above cover the essentials. The SDK supports several additional capabilities — each is demonstrated in the runnable examples and covered in more detail in the linked references.
|
||||
The sections above cover the essentials. The table below links to additional capabilities demonstrated in the runnable examples.
|
||||
|
||||
| Feature | Description | Reference |
|
||||
|---------|-------------|-----------|
|
||||
|
|
@ -349,8 +470,4 @@ The sections above cover the essentials. The SDK supports several additional cap
|
|||
| Session management | Per-session transport routing, initialization, and cleanup | [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts) |
|
||||
| Resumability | Replay missed SSE events via an event store | [`inMemoryEventStore.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/inMemoryEventStore.ts) |
|
||||
| CORS | Expose MCP headers (`mcp-session-id`, etc.) for browser clients | [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts) |
|
||||
| Tool annotations | Hint whether tools are read-only, destructive, etc. | [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts) |
|
||||
| Elicitation | Request user input (forms or URLs) during tool execution | [Capabilities guide](capabilities.md#elicitation), [`elicitationFormExample.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/elicitationFormExample.ts) |
|
||||
| Sampling | Request LLM completions from the connected client | [Capabilities guide](capabilities.md#sampling), [`toolWithSampleServer.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/toolWithSampleServer.ts) |
|
||||
| Tasks (experimental) | Long-running operations with polling and resumption | [Capabilities guide](capabilities.md#task-based-execution-experimental), [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts) |
|
||||
| Multi‑node deployment | Stateless, persistent‑storage, and distributed routing patterns | [`examples/server/README.md`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/README.md#multi-node-deployment-patterns) |
|
||||
|
|
|
|||
|
|
@ -207,6 +207,88 @@ function registerTool_logging() {
|
|||
return server;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Server-initiated requests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Example: Tool that uses sampling to request an LLM completion from the client. */
|
||||
function registerTool_sampling(server: McpServer) {
|
||||
//#region registerTool_sampling
|
||||
server.registerTool(
|
||||
'summarize',
|
||||
{
|
||||
description: 'Summarize text using the client LLM',
|
||||
inputSchema: z.object({ text: z.string() })
|
||||
},
|
||||
async ({ text }, ctx): Promise<CallToolResult> => {
|
||||
const response = await ctx.mcpReq.requestSampling({
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: {
|
||||
type: 'text',
|
||||
text: `Please summarize:\n\n${text}`
|
||||
}
|
||||
}
|
||||
],
|
||||
maxTokens: 500
|
||||
});
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Model (${response.model}): ${JSON.stringify(response.content)}`
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
);
|
||||
//#endregion registerTool_sampling
|
||||
}
|
||||
|
||||
/** Example: Tool that uses form elicitation to collect user input. */
|
||||
function registerTool_elicitation(server: McpServer) {
|
||||
//#region registerTool_elicitation
|
||||
server.registerTool(
|
||||
'collect-feedback',
|
||||
{
|
||||
description: 'Collect user feedback via a form',
|
||||
inputSchema: z.object({})
|
||||
},
|
||||
async (_args, ctx): Promise<CallToolResult> => {
|
||||
const result = await ctx.mcpReq.elicitInput({
|
||||
mode: 'form',
|
||||
message: 'Please share your feedback:',
|
||||
requestedSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
rating: {
|
||||
type: 'number',
|
||||
title: 'Rating (1\u20135)',
|
||||
minimum: 1,
|
||||
maximum: 5
|
||||
},
|
||||
comment: { type: 'string', title: 'Comment' }
|
||||
},
|
||||
required: ['rating']
|
||||
}
|
||||
});
|
||||
if (result.action === 'accept') {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Thanks! ${JSON.stringify(result.content)}`
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
return { content: [{ type: 'text', text: 'Feedback declined.' }] };
|
||||
}
|
||||
);
|
||||
//#endregion registerTool_elicitation
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Transports
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -294,6 +376,8 @@ function dnsRebinding_allowedHosts() {
|
|||
void registerTool_basic;
|
||||
void registerTool_resourceLink;
|
||||
void registerTool_logging;
|
||||
void registerTool_sampling;
|
||||
void registerTool_elicitation;
|
||||
void registerResource_static;
|
||||
void registerResource_template;
|
||||
void registerPrompt_basic;
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ export class InMemoryTaskStore implements TaskStore {
|
|||
return crypto.randomUUID().replaceAll('-', '');
|
||||
}
|
||||
|
||||
/** {@inheritDoc TaskStore.createTask} */
|
||||
async createTask(taskParams: CreateTaskOptions, requestId: RequestId, request: Request, sessionId?: string): Promise<Task> {
|
||||
// Generate a unique task ID
|
||||
const taskId = this.generateTaskId();
|
||||
|
|
@ -96,6 +97,7 @@ export class InMemoryTaskStore implements TaskStore {
|
|||
return stored ? { ...stored.task } : null;
|
||||
}
|
||||
|
||||
/** {@inheritDoc TaskStore.storeTaskResult} */
|
||||
async storeTaskResult(taskId: string, status: 'completed' | 'failed', result: Result, sessionId?: string): Promise<void> {
|
||||
const stored = this.getStoredTask(taskId, sessionId);
|
||||
if (!stored) {
|
||||
|
|
@ -129,6 +131,7 @@ export class InMemoryTaskStore implements TaskStore {
|
|||
}
|
||||
}
|
||||
|
||||
/** {@inheritDoc TaskStore.getTaskResult} */
|
||||
async getTaskResult(taskId: string, sessionId?: string): Promise<Result> {
|
||||
const stored = this.getStoredTask(taskId, sessionId);
|
||||
if (!stored) {
|
||||
|
|
@ -142,6 +145,7 @@ export class InMemoryTaskStore implements TaskStore {
|
|||
return stored.result;
|
||||
}
|
||||
|
||||
/** {@inheritDoc TaskStore.updateTaskStatus} */
|
||||
async updateTaskStatus(taskId: string, status: Task['status'], statusMessage?: string, sessionId?: string): Promise<void> {
|
||||
const stored = this.getStoredTask(taskId, sessionId);
|
||||
if (!stored) {
|
||||
|
|
@ -178,6 +182,7 @@ export class InMemoryTaskStore implements TaskStore {
|
|||
}
|
||||
}
|
||||
|
||||
/** {@inheritDoc TaskStore.listTasks} */
|
||||
async listTasks(cursor?: string, sessionId?: string): Promise<{ tasks: Task[]; nextCursor?: string }> {
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
import type { Result, Task } from '../types/types.js';
|
||||
|
||||
/**
|
||||
* Base message type
|
||||
* Base message type for the response stream.
|
||||
*/
|
||||
export interface BaseResponseMessage {
|
||||
type: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Task status update message
|
||||
* Task status update message.
|
||||
*
|
||||
* Yielded on each poll iteration while the task is active (e.g. while
|
||||
* `working`). May be emitted multiple times with the same status.
|
||||
*/
|
||||
export interface TaskStatusMessage extends BaseResponseMessage {
|
||||
type: 'taskStatus';
|
||||
|
|
@ -16,7 +19,10 @@ export interface TaskStatusMessage extends BaseResponseMessage {
|
|||
}
|
||||
|
||||
/**
|
||||
* Task created message (first message for task-augmented requests)
|
||||
* Task created message.
|
||||
*
|
||||
* Yielded once when the server creates a new task for a long-running operation.
|
||||
* This is always the first message for task-augmented requests.
|
||||
*/
|
||||
export interface TaskCreatedMessage extends BaseResponseMessage {
|
||||
type: 'taskCreated';
|
||||
|
|
@ -24,7 +30,10 @@ export interface TaskCreatedMessage extends BaseResponseMessage {
|
|||
}
|
||||
|
||||
/**
|
||||
* Final result message (terminal)
|
||||
* Final result message.
|
||||
*
|
||||
* Yielded once when the operation completes successfully. Terminal — no further
|
||||
* messages will follow.
|
||||
*/
|
||||
export interface ResultMessage<T extends Result> extends BaseResponseMessage {
|
||||
type: 'result';
|
||||
|
|
@ -32,7 +41,9 @@ export interface ResultMessage<T extends Result> extends BaseResponseMessage {
|
|||
}
|
||||
|
||||
/**
|
||||
* Error message (terminal)
|
||||
* Error message.
|
||||
*
|
||||
* Yielded once if the operation fails. Terminal — no further messages will follow.
|
||||
*/
|
||||
export interface ErrorMessage extends BaseResponseMessage {
|
||||
type: 'error';
|
||||
|
|
@ -40,14 +51,26 @@ export interface ErrorMessage extends BaseResponseMessage {
|
|||
}
|
||||
|
||||
/**
|
||||
* Union type representing all possible messages that can be yielded during request processing.
|
||||
* Note: Progress notifications are handled through the existing {@linkcode index.RequestOptions | onprogress} callback mechanism.
|
||||
* Union of all message types yielded by task-aware streaming APIs such as
|
||||
* {@linkcode @modelcontextprotocol/client!experimental/tasks/client.ExperimentalClientTasks#callToolStream | callToolStream()},
|
||||
* {@linkcode @modelcontextprotocol/client!experimental/tasks/client.ExperimentalClientTasks#requestStream | ExperimentalClientTasks.requestStream()}, and
|
||||
* {@linkcode @modelcontextprotocol/server!experimental/tasks/server.ExperimentalServerTasks#requestStream | ExperimentalServerTasks.requestStream()}.
|
||||
*
|
||||
* A typical sequence is:
|
||||
* 1. `taskCreated` — task is registered (once)
|
||||
* 2. `taskStatus` — zero or more progress updates
|
||||
* 3. `result` **or** `error` — terminal message (once)
|
||||
*
|
||||
* Progress notifications are handled through the existing {@linkcode index.RequestOptions | onprogress} callback.
|
||||
* Side-channeled messages (server requests/notifications) are handled through registered handlers.
|
||||
*/
|
||||
export type ResponseMessage<T extends Result> = TaskStatusMessage | TaskCreatedMessage | ResultMessage<T> | ErrorMessage;
|
||||
|
||||
export type AsyncGeneratorValue<T> = T extends AsyncGenerator<infer U> ? U : never;
|
||||
|
||||
/**
|
||||
* Collects all values from an async generator into an array.
|
||||
*/
|
||||
export async function toArrayAsync<T extends AsyncGenerator<unknown>>(it: T): Promise<AsyncGeneratorValue<T>[]> {
|
||||
const arr: AsyncGeneratorValue<T>[] = [];
|
||||
for await (const o of it) {
|
||||
|
|
@ -57,6 +80,11 @@ export async function toArrayAsync<T extends AsyncGenerator<unknown>>(it: T): Pr
|
|||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes a {@linkcode ResponseMessage} stream and returns the final result,
|
||||
* discarding intermediate `taskCreated` and `taskStatus` messages. Throws
|
||||
* if an `error` message is received or the stream ends without a result.
|
||||
*/
|
||||
export async function takeResult<T extends Result, U extends AsyncGenerator<ResponseMessage<T>>>(it: U): Promise<T> {
|
||||
for await (const o of it) {
|
||||
if (o.type === 'result') {
|
||||
|
|
|
|||
|
|
@ -41,10 +41,27 @@ export type TaskRequestHandler<ResultT extends Result, Args extends AnySchema |
|
|||
|
||||
/**
|
||||
* Interface for task-based tool handlers.
|
||||
*
|
||||
* Task-based tools split a long-running operation into three phases:
|
||||
* `createTask`, `getTask`, and `getTaskResult`.
|
||||
*
|
||||
* @see {@linkcode @modelcontextprotocol/server!experimental/tasks/mcpServer.ExperimentalMcpServerTasks#registerToolTask | registerToolTask} for registration.
|
||||
* @experimental
|
||||
*/
|
||||
export interface ToolTaskHandler<Args extends AnySchema | undefined = undefined> {
|
||||
/**
|
||||
* Called on the initial `tools/call` request.
|
||||
*
|
||||
* Creates a task via `ctx.task.store.createTask(...)`, starts any
|
||||
* background work, and returns the task object.
|
||||
*/
|
||||
createTask: CreateTaskRequestHandler<CreateTaskResult, Args>;
|
||||
/**
|
||||
* Handler for `tasks/get` requests.
|
||||
*/
|
||||
getTask: TaskRequestHandler<GetTaskResult, Args>;
|
||||
/**
|
||||
* Handler for `tasks/result` requests.
|
||||
*/
|
||||
getTaskResult: TaskRequestHandler<CallToolResult, Args>;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue