diff --git a/README.md b/README.md index 745887313..790270520 100644 --- a/README.md +++ b/README.md @@ -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/) diff --git a/docs/capabilities.md b/docs/capabilities.md deleted file mode 100644 index 579a0f09c..000000000 --- a/docs/capabilities.md +++ /dev/null @@ -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. diff --git a/docs/client.md b/docs/client.md index ae49d759d..bbef67000 100644 --- a/docs/client.md +++ b/docs/client.md @@ -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) | diff --git a/docs/documents.md b/docs/documents.md index 36b8235cb..fa5a15dab 100644 --- a/docs/documents.md +++ b/docs/documents.md @@ -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 diff --git a/docs/server.md b/docs/server.md index 941ca8740..953e65c70 100644 --- a/docs/server.md +++ b/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 => { - 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 => { + 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 => { + 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 => { + 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) | diff --git a/examples/server/src/serverGuide.examples.ts b/examples/server/src/serverGuide.examples.ts index 271d0d39b..ca4be3d4a 100644 --- a/examples/server/src/serverGuide.examples.ts +++ b/examples/server/src/serverGuide.examples.ts @@ -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 => { + 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 => { + 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; diff --git a/packages/core/src/experimental/tasks/stores/inMemory.ts b/packages/core/src/experimental/tasks/stores/inMemory.ts index 8af800029..c6c4a3015 100644 --- a/packages/core/src/experimental/tasks/stores/inMemory.ts +++ b/packages/core/src/experimental/tasks/stores/inMemory.ts @@ -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 { // 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 { 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 { 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 { 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; diff --git a/packages/core/src/shared/responseMessage.ts b/packages/core/src/shared/responseMessage.ts index 49059489b..b776d853c 100644 --- a/packages/core/src/shared/responseMessage.ts +++ b/packages/core/src/shared/responseMessage.ts @@ -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 extends BaseResponseMessage { type: 'result'; @@ -32,7 +41,9 @@ export interface ResultMessage 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 = TaskStatusMessage | TaskCreatedMessage | ResultMessage | ErrorMessage; export type AsyncGeneratorValue = T extends AsyncGenerator ? U : never; +/** + * Collects all values from an async generator into an array. + */ export async function toArrayAsync>(it: T): Promise[]> { const arr: AsyncGeneratorValue[] = []; for await (const o of it) { @@ -57,6 +80,11 @@ export async function toArrayAsync>(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>>(it: U): Promise { for await (const o of it) { if (o.type === 'result') { diff --git a/packages/server/src/experimental/tasks/interfaces.ts b/packages/server/src/experimental/tasks/interfaces.ts index b80f1f5a4..d12ee11d8 100644 --- a/packages/server/src/experimental/tasks/interfaces.ts +++ b/packages/server/src/experimental/tasks/interfaces.ts @@ -41,10 +41,27 @@ export type TaskRequestHandler { + /** + * 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; + /** + * Handler for `tasks/get` requests. + */ getTask: TaskRequestHandler; + /** + * Handler for `tasks/result` requests. + */ getTaskResult: TaskRequestHandler; }