diff --git a/README.md b/README.md index 3bd832f88..b9cd57ea8 100644 --- a/README.md +++ b/README.md @@ -127,9 +127,9 @@ Next steps: ## Documentation - Local SDK docs: - - [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). + - [docs/server.md](docs/server.md) – building MCP servers: transports, tools, resources, prompts, server-initiated requests, and deployment + - [docs/client.md](docs/client.md) – building MCP clients: connecting, tools, resources, prompts, server-initiated requests, and error handling + - [docs/faq.md](docs/faq.md) – frequently asked questions and troubleshooting - External references: - [SDK API documentation](https://modelcontextprotocol.github.io/typescript-sdk/) - [Model Context Protocol documentation](https://modelcontextprotocol.io) diff --git a/docs/client.md b/docs/client.md index 3a6e9973b..ea359f67e 100644 --- a/docs/client.md +++ b/docs/client.md @@ -2,21 +2,35 @@ title: Client Guide --- -# Client overview +# Building MCP clients -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/). +This guide covers the TypeScript SDK APIs for building MCP clients. For protocol-level concepts, see the [MCP overview](https://modelcontextprotocol.io/docs/learn/architecture). -The SDK provides a {@linkcode @modelcontextprotocol/client!client/client.Client | Client} class from `@modelcontextprotocol/client` that connects to MCP servers over different transports: +A client connects to a server, discovers what it offers — tools, resources, prompts — and invokes them. Beyond that core loop, this guide covers authentication, error handling, and responding to server-initiated requests like sampling and elicitation. -- **Streamable HTTP** – for remote HTTP servers. -- **stdio** – for local processes you spawn. -- **SSE** – for legacy HTTP+SSE servers (deprecated). +## Imports -For a feature‑rich starting point, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleStreamableHttp.ts). +The examples below use these imports. Adjust based on which features and transport you need: -## Connecting to a server +```ts source="../examples/client/src/clientGuide.examples.ts#imports" +import type { Prompt, Resource, Tool } from '@modelcontextprotocol/client'; +import { + applyMiddlewares, + CallToolResultSchema, + Client, + ClientCredentialsProvider, + createMiddleware, + PrivateKeyJwtProvider, + ProtocolError, + SdkError, + SdkErrorCode, + SSEClientTransport, + StdioClientTransport, + StreamableHTTPClientTransport +} from '@modelcontextprotocol/client'; +``` -Construct a `Client` with a name and version, create a transport, and call {@linkcode @modelcontextprotocol/client!client/client.Client#connect | client.connect(transport)}. The client automatically performs the MCP initialization handshake. +## Connecting to a server ### Streamable HTTP @@ -30,12 +44,11 @@ const transport = new StreamableHTTPClientTransport(new URL('http://localhost:30 await client.connect(transport); ``` -> [!NOTE] -> For a full interactive client over Streamable HTTP, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleStreamableHttp.ts). +For a full interactive client over Streamable HTTP, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleStreamableHttp.ts). ### stdio -For local, process‑spawned servers (Claude Desktop, CLI tools), use {@linkcode @modelcontextprotocol/client!client/stdio.StdioClientTransport | StdioClientTransport}. The transport spawns the server process and communicates over stdin/stdout: +For local, process-spawned servers (Claude Desktop, CLI tools), use {@linkcode @modelcontextprotocol/client!client/stdio.StdioClientTransport | StdioClientTransport}. The transport spawns the server process and communicates over stdin/stdout: ```ts source="../examples/client/src/clientGuide.examples.ts#connect_stdio" const client = new Client({ name: 'my-client', version: '1.0.0' }); @@ -50,7 +63,7 @@ await client.connect(transport); ### SSE fallback for legacy servers -To support both modern Streamable HTTP and legacy SSE servers, try `StreamableHTTPClientTransport` first and fall back to {@linkcode @modelcontextprotocol/client!client/sse.SSEClientTransport | SSEClientTransport} on failure: +To support both modern Streamable HTTP and legacy SSE servers, try {@linkcode @modelcontextprotocol/client!client/streamableHttp.StreamableHTTPClientTransport | StreamableHTTPClientTransport} first and fall back to {@linkcode @modelcontextprotocol/client!client/sse.SSEClientTransport | SSEClientTransport} on failure: ```ts source="../examples/client/src/clientGuide.examples.ts#connect_sseFallback" const baseUrl = new URL(url); @@ -70,16 +83,40 @@ try { } ``` -> [!NOTE] -> For a complete example with error reporting, see [`streamableHttpWithSseFallbackClient.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/streamableHttpWithSseFallbackClient.ts). +For a complete example with error reporting, see [`streamableHttpWithSseFallbackClient.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/streamableHttpWithSseFallbackClient.ts). + +### Disconnecting + +Call {@linkcode @modelcontextprotocol/client!client/client.Client#close | await client.close() } to disconnect. Pending requests are rejected with a {@linkcode @modelcontextprotocol/client!index.SdkErrorCode.ConnectionClosed | CONNECTION_CLOSED} error. + +For Streamable HTTP, terminate the server-side session first (per the MCP specification): + +```ts source="../examples/client/src/clientGuide.examples.ts#disconnect_streamableHttp" +await transport.terminateSession(); // notify the server (recommended) +await client.close(); +``` + +For stdio, `client.close()` handles graceful process shutdown (closes stdin, then SIGTERM, then SIGKILL if needed). + +### Server instructions + +Servers can provide an `instructions` string during initialization that describes how to use them — cross-tool relationships, workflow patterns, and constraints (see [Instructions](https://modelcontextprotocol.io/specification/latest/basic/lifecycle#instructions) in the MCP specification). Retrieve it after connecting and include it in the model's system prompt: + +```ts source="../examples/client/src/clientGuide.examples.ts#serverInstructions_basic" +const instructions = client.getInstructions(); + +const systemPrompt = ['You are a helpful assistant.', instructions].filter(Boolean).join('\n\n'); + +console.log(systemPrompt); +``` ## Authentication -For OAuth‑secured MCP servers, pass an `authProvider` to `StreamableHTTPClientTransport`. The SDK provides built‑in providers for common machine‑to‑machine flows, or you can implement the full {@linkcode @modelcontextprotocol/client!client/auth.OAuthClientProvider | OAuthClientProvider} interface for user‑facing OAuth. +MCP servers can require OAuth 2.0 authentication before accepting client connections (see [Authorization](https://modelcontextprotocol.io/specification/latest/basic/authorization) in the MCP specification). Pass an `authProvider` to {@linkcode @modelcontextprotocol/client!client/streamableHttp.StreamableHTTPClientTransport | StreamableHTTPClientTransport} to enable this — the SDK provides built-in providers for common machine-to-machine flows, or you can implement the full {@linkcode @modelcontextprotocol/client!client/auth.OAuthClientProvider | OAuthClientProvider} interface for user-facing OAuth. ### Client credentials -{@linkcode @modelcontextprotocol/client!client/authExtensions.ClientCredentialsProvider | ClientCredentialsProvider} handles the `client_credentials` grant flow for service‑to‑service communication: +{@linkcode @modelcontextprotocol/client!client/authExtensions.ClientCredentialsProvider | ClientCredentialsProvider} handles the `client_credentials` grant flow for service-to-service communication: ```ts source="../examples/client/src/clientGuide.examples.ts#auth_clientCredentials" const authProvider = new ClientCredentialsProvider({ @@ -108,34 +145,31 @@ const authProvider = new PrivateKeyJwtProvider({ const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'), { authProvider }); ``` -> [!NOTE] -> For a runnable example supporting both auth methods via environment variables, see [`simpleClientCredentials.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleClientCredentials.ts). +For a runnable example supporting both auth methods via environment variables, see [`simpleClientCredentials.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleClientCredentials.ts). ### Full OAuth with user authorization -For user‑facing applications, implement the `OAuthClientProvider` interface to handle the full authorization code flow (redirects, code verifiers, token storage, dynamic client registration). The `connect()` call will throw `UnauthorizedError` when authorization is needed — catch it, complete the browser flow, call `transport.finishAuth(code)`, and reconnect. +For user-facing applications, implement the {@linkcode @modelcontextprotocol/client!client/auth.OAuthClientProvider | OAuthClientProvider} interface to handle the full authorization code flow (redirects, code verifiers, token storage, dynamic client registration). The {@linkcode @modelcontextprotocol/client!client/client.Client#connect | connect()} call will throw {@linkcode @modelcontextprotocol/client!client/auth.UnauthorizedError | UnauthorizedError} when authorization is needed — catch it, complete the browser flow, call {@linkcode @modelcontextprotocol/client!client/streamableHttp.StreamableHTTPClientTransport#finishAuth | transport.finishAuth(code)}, and reconnect. -> [!NOTE] -> For a complete working OAuth flow, see [`simpleOAuthClient.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleOAuthClient.ts) and [`simpleOAuthClientProvider.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleOAuthClientProvider.ts). -> -> For protocol details, see [Authorization](https://modelcontextprotocol.io/specification/latest/basic/authorization) in the MCP specification. +For a complete working OAuth flow, see [`simpleOAuthClient.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleOAuthClient.ts) and [`simpleOAuthClientProvider.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleOAuthClientProvider.ts). -## Using server features +## Tools -Once connected, the `Client` provides high‑level helpers for the three core MCP primitives: tools, resources, and prompts. These handle JSON‑RPC request/response encoding automatically. +Tools are callable actions offered by servers — discovering and invoking them is usually how your client enables an LLM to take action (see [Tools](https://modelcontextprotocol.io/docs/learn/server-concepts#tools) in the MCP overview). -> [!NOTE] -> For a full runnable client exercising tools, resources, and prompts, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleStreamableHttp.ts). - -### Tools - -Use {@linkcode @modelcontextprotocol/client!client/client.Client#listTools | listTools()} to discover available tools, and {@linkcode @modelcontextprotocol/client!client/client.Client#callTool | callTool()} to invoke one: +Use {@linkcode @modelcontextprotocol/client!client/client.Client#listTools | listTools()} to discover available tools, and {@linkcode @modelcontextprotocol/client!client/client.Client#callTool | callTool()} to invoke one. Results may be paginated — loop on `nextCursor` to collect all pages: ```ts source="../examples/client/src/clientGuide.examples.ts#callTool_basic" -const { tools } = await client.listTools(); +const allTools: Tool[] = []; +let toolCursor: string | undefined; +do { + const { tools, nextCursor } = await client.listTools({ cursor: toolCursor }); + allTools.push(...tools); + toolCursor = nextCursor; +} while (toolCursor); console.log( 'Available tools:', - tools.map(t => t.name) + allTools.map(t => t.name) ); const result = await client.callTool({ @@ -145,18 +179,52 @@ const result = await client.callTool({ console.log(result.content); ``` -> [!NOTE] -> See [Tools](https://modelcontextprotocol.io/specification/latest/server/tools) in the MCP specification for the full protocol details. +Tool results may include a `structuredContent` field — a machine-readable JSON object for programmatic use by the client application, complementing `content` which is for the LLM: -### Resources +```ts source="../examples/client/src/clientGuide.examples.ts#callTool_structuredOutput" +const result = await client.callTool({ + name: 'calculate-bmi', + arguments: { weightKg: 70, heightM: 1.75 } +}); + +// Machine-readable output for the client application +if (result.structuredContent) { + console.log(result.structuredContent); // e.g. { bmi: 22.86 } +} +``` + +### Tracking progress + +Pass `onprogress` to receive incremental progress notifications from long-running tools. Use `resetTimeoutOnProgress` to keep the request alive while the server is actively reporting, and `maxTotalTimeout` as an absolute cap: + +```ts source="../examples/client/src/clientGuide.examples.ts#callTool_progress" +const result = await client.callTool({ name: 'long-operation', arguments: {} }, undefined, { + onprogress: ({ progress, total }) => { + console.log(`Progress: ${progress}/${total ?? '?'}`); + }, + resetTimeoutOnProgress: true, + maxTotalTimeout: 600_000 +}); +console.log(result.content); +``` + +## Resources + +Resources are read-only data — files, database schemas, configuration — that your application can retrieve from a server and attach as context for the model (see [Resources](https://modelcontextprotocol.io/docs/learn/server-concepts#resources) in the MCP overview). -Use {@linkcode @modelcontextprotocol/client!client/client.Client#listResources | listResources()} and {@linkcode @modelcontextprotocol/client!client/client.Client#readResource | readResource()} to discover and read server‑provided data: +Use {@linkcode @modelcontextprotocol/client!client/client.Client#listResources | listResources()} and {@linkcode @modelcontextprotocol/client!client/client.Client#readResource | readResource()} to discover and read server-provided data. Results may be paginated — loop on `nextCursor` to collect all pages: ```ts source="../examples/client/src/clientGuide.examples.ts#readResource_basic" -const { resources } = await client.listResources(); +const allResources: Resource[] = []; +let resourceCursor: string | undefined; +do { + const { resources, nextCursor } = await client.listResources({ cursor: resourceCursor }); + allResources.push(...resources); + resourceCursor = nextCursor; +} while (resourceCursor); console.log( 'Available resources:', - resources.map(r => r.name) + allResources.map(r => r.name) ); const { contents } = await client.readResource({ uri: 'config://app' }); @@ -165,18 +233,43 @@ for (const item of contents) { } ``` -> [!NOTE] -> See [Resources](https://modelcontextprotocol.io/specification/latest/server/resources) in the MCP specification for the full protocol details. +To discover URI templates for dynamic resources, use {@linkcode @modelcontextprotocol/client!client/client.Client#listResourceTemplates | listResourceTemplates()}. + +### Subscribing to resource changes + +If the server supports resource subscriptions, use {@linkcode @modelcontextprotocol/client!client/client.Client#subscribeResource | subscribeResource()} to receive notifications when a resource changes, then re-read it: + +```ts source="../examples/client/src/clientGuide.examples.ts#subscribeResource_basic" +await client.subscribeResource({ uri: 'config://app' }); + +client.setNotificationHandler('notifications/resources/updated', async notification => { + if (notification.params.uri === 'config://app') { + const { contents } = await client.readResource({ uri: 'config://app' }); + console.log('Config updated:', contents); + } +}); + +// Later: stop receiving updates +await client.unsubscribeResource({ uri: 'config://app' }); +``` + +## Prompts -### Prompts +Prompts are reusable message templates that servers offer to help structure interactions with models (see [Prompts](https://modelcontextprotocol.io/docs/learn/server-concepts#prompts) in the MCP overview). -Use {@linkcode @modelcontextprotocol/client!client/client.Client#listPrompts | listPrompts()} and {@linkcode @modelcontextprotocol/client!client/client.Client#getPrompt | getPrompt()} to retrieve prompt templates from the server: +Use {@linkcode @modelcontextprotocol/client!client/client.Client#listPrompts | listPrompts()} and {@linkcode @modelcontextprotocol/client!client/client.Client#getPrompt | getPrompt()} to list available prompts and retrieve them with arguments. Results may be paginated — loop on `nextCursor` to collect all pages: ```ts source="../examples/client/src/clientGuide.examples.ts#getPrompt_basic" -const { prompts } = await client.listPrompts(); +const allPrompts: Prompt[] = []; +let promptCursor: string | undefined; +do { + const { prompts, nextCursor } = await client.listPrompts({ cursor: promptCursor }); + allPrompts.push(...prompts); + promptCursor = nextCursor; +} while (promptCursor); console.log( 'Available prompts:', - prompts.map(p => p.name) + allPrompts.map(p => p.name) ); const { messages } = await client.getPrompt({ @@ -186,12 +279,9 @@ const { messages } = await client.getPrompt({ console.log(messages); ``` -> [!NOTE] -> See [Prompts](https://modelcontextprotocol.io/specification/latest/server/prompts) in the MCP specification for the full protocol details. - -### Completions +## Completions -If a server supports argument completions on prompts or resources, use {@linkcode @modelcontextprotocol/client!client/client.Client#complete | complete()} to request suggestions. This is the client‑side counterpart to {@linkcode @modelcontextprotocol/server!server/completable.completable | completable()} on the server: +Both prompts and resources can support argument completions. Use {@linkcode @modelcontextprotocol/client!client/client.Client#complete | complete()} to request autocompletion suggestions from the server as a user types: ```ts source="../examples/client/src/clientGuide.examples.ts#complete_basic" const { completion } = await client.complete({ @@ -209,9 +299,9 @@ console.log(completion.values); // e.g. ['typescript'] ## Notifications -### Automatic list‑change tracking +### Automatic list-change tracking -The `listChanged` client option keeps a local cache of tools, prompts, or resources in sync with the server. Compared to manually handling notifications, it provides automatic server capability gating, debouncing (300 ms by default), auto‑refresh, and error‑first callbacks: +The {@linkcode @modelcontextprotocol/client!client/client.ClientOptions | listChanged} client option keeps a local cache of tools, prompts, or resources in sync with the server. It provides automatic server capability gating, debouncing (300 ms by default), auto-refresh, and error-first callbacks: ```ts source="../examples/client/src/clientGuide.examples.ts#listChanged_basic" const client = new Client( @@ -240,7 +330,7 @@ const client = new Client( For full control — or for notification types not covered by `listChanged` (such as log messages) — register handlers directly with {@linkcode @modelcontextprotocol/client!client/client.Client#setNotificationHandler | setNotificationHandler()}: ```ts source="../examples/client/src/clientGuide.examples.ts#notificationHandler_basic" -// Server log messages (e.g. from ctx.mcpReq.log() in tool handlers) +// Server log messages (sent by the server during request processing) client.setNotificationHandler('notifications/message', notification => { const { level, data } = notification.params; console.log(`[${level}]`, data); @@ -253,15 +343,18 @@ client.setNotificationHandler('notifications/resources/list_changed', async () = }); ``` -Note that `listChanged` and `setNotificationHandler` are mutually exclusive per notification type — using both for the same notification will cause the manual handler to be overwritten. +To control the minimum severity of log messages the server sends, use {@linkcode @modelcontextprotocol/client!client/client.Client#setLoggingLevel | setLoggingLevel()}: -## Handling server‑initiated requests +```ts source="../examples/client/src/clientGuide.examples.ts#setLoggingLevel_basic" +await client.setLoggingLevel('warning'); +``` -MCP is bidirectional — servers can also send requests *to* the client. To handle these, declare the corresponding capability when constructing the `Client` and register a request handler. The two main server‑initiated request types are **sampling** (LLM completions) and **elicitation** (user input). +> [!WARNING] +> `listChanged` and {@linkcode @modelcontextprotocol/client!client/client.Client#setNotificationHandler | setNotificationHandler()} are mutually exclusive per notification type — using both for the same notification will cause the manual handler to be overwritten. -### Declaring capabilities +## Handling server-initiated requests -Pass a {@linkcode @modelcontextprotocol/client!client/client.ClientOptions | `capabilities`} object when constructing the `Client`. The server reads these during initialization and will only send requests your client has declared support for: +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). Declare the corresponding capability when constructing the {@linkcode @modelcontextprotocol/client!client/client.Client | Client} and register a request handler: ```ts source="../examples/client/src/clientGuide.examples.ts#capabilities_declaration" const client = new Client( @@ -277,7 +370,7 @@ const client = new Client( ### Sampling -When a server calls `server.createMessage(...)` inside a tool handler, the request is routed to the client. Register a handler for `sampling/createMessage` to fulfill it: +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" client.setRequestHandler('sampling/createMessage', async request => { @@ -296,12 +389,9 @@ client.setRequestHandler('sampling/createMessage', async request => { }); ``` -> [!NOTE] -> See [Sampling](https://modelcontextprotocol.io/specification/latest/client/sampling) in the MCP specification for the full protocol details. - ### Elicitation -When a server calls `server.elicitInput(...)`, the request arrives at the client as an `elicitation/create` request. The client should present the form to the user and return the collected data, or `{ action: 'decline' }`: +When a server needs user input during tool execution, it sends an `elicitation/create` request to the client (see [Elicitation](https://modelcontextprotocol.io/docs/learn/client-concepts#elicitation) in the MCP overview). The client should present the form to the user and return the collected data, or `{ action: 'decline' }`: ```ts source="../examples/client/src/clientGuide.examples.ts#elicitation_handler" client.setRequestHandler('elicitation/create', async request => { @@ -317,14 +407,95 @@ 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 (`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. +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). -## Advanced patterns +### Roots -### Client middleware +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" +client.setRequestHandler('roots/list', async () => { + return { + roots: [ + { uri: 'file:///home/user/projects/my-app', name: 'My App' }, + { uri: 'file:///home/user/data', name: 'Data' } + ] + }; +}); +``` + +When the available roots change, notify the server with {@linkcode @modelcontextprotocol/client!client/client.Client#sendRootsListChanged | client.sendRootsListChanged()}. + +## Error handling + +### Tool errors vs protocol errors + +{@linkcode @modelcontextprotocol/client!client/client.Client#callTool | callTool()} has two error surfaces: the tool can *run but report failure* via `isError: true` in the result, or the *request itself can fail* and throw an exception. Always check both: + +```ts source="../examples/client/src/clientGuide.examples.ts#errorHandling_toolErrors" +try { + const result = await client.callTool({ + name: 'fetch-data', + arguments: { url: 'https://example.com' } + }); + + // Tool-level error: the tool ran but reported a problem + if (result.isError) { + console.error('Tool error:', result.content); + return; + } + + console.log('Success:', result.content); +} catch (error) { + // Protocol-level error: the request itself failed + if (error instanceof ProtocolError) { + console.error(`Protocol error ${error.code}: ${error.message}`); + } else if (error instanceof SdkError) { + console.error(`SDK error [${error.code}]: ${error.message}`); + } else { + throw error; + } +} +``` + +{@linkcode @modelcontextprotocol/client!index.ProtocolError | ProtocolError} represents JSON-RPC errors from the server (method not found, invalid params, internal error). {@linkcode @modelcontextprotocol/client!index.SdkError | SdkError} represents local SDK errors — {@linkcode @modelcontextprotocol/client!index.SdkErrorCode.RequestTimeout | REQUEST_TIMEOUT}, {@linkcode @modelcontextprotocol/client!index.SdkErrorCode.ConnectionClosed | CONNECTION_CLOSED}, {@linkcode @modelcontextprotocol/client!index.SdkErrorCode.CapabilityNotSupported | CAPABILITY_NOT_SUPPORTED}, and others. + +### Connection lifecycle + +Set {@linkcode @modelcontextprotocol/client!client/client.Client#onerror | client.onerror} to catch out-of-band transport errors (SSE disconnects, parse errors). Set {@linkcode @modelcontextprotocol/client!client/client.Client#onclose | client.onclose} to detect when the connection drops — pending requests are rejected with a {@linkcode @modelcontextprotocol/client!index.SdkErrorCode.ConnectionClosed | CONNECTION_CLOSED} error: + +```ts source="../examples/client/src/clientGuide.examples.ts#errorHandling_lifecycle" +// Out-of-band errors (SSE disconnects, parse errors) +client.onerror = error => { + console.error('Transport error:', error.message); +}; + +// Connection closed (pending requests are rejected with CONNECTION_CLOSED) +client.onclose = () => { + console.log('Connection closed'); +}; +``` + +### Timeouts + +All requests have a 60-second default timeout. Pass a custom `timeout` in the options to override it. On timeout, the SDK sends a cancellation notification to the server and rejects the promise with {@linkcode @modelcontextprotocol/client!index.SdkErrorCode.RequestTimeout | SdkErrorCode.RequestTimeout}: + +```ts source="../examples/client/src/clientGuide.examples.ts#errorHandling_timeout" +try { + const result = await client.callTool( + { name: 'slow-task', arguments: {} }, + undefined, + { timeout: 120_000 } // 2 minutes instead of the default 60 seconds + ); + console.log(result.content); +} catch (error) { + if (error instanceof SdkError && error.code === SdkErrorCode.RequestTimeout) { + console.error('Request timed out'); + } +} +``` + +## Client middleware Use {@linkcode @modelcontextprotocol/client!client/middleware.createMiddleware | createMiddleware()} and {@linkcode @modelcontextprotocol/client!client/middleware.applyMiddlewares | applyMiddlewares()} to compose fetch middleware pipelines. Middleware wraps the underlying `fetch` call and can add headers, handle retries, or log requests. Pass the enhanced fetch to the transport via the `fetch` option: @@ -340,9 +511,9 @@ const transport = new StreamableHTTPClientTransport(new URL('http://localhost:30 }); ``` -### Resumption tokens +## 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: +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: ```ts source="../examples/client/src/clientGuide.examples.ts#resumptionToken_basic" let lastToken: string | undefined; @@ -364,29 +535,33 @@ const result = await client.request( 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). +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: +> [!WARNING] +> The tasks API is experimental and may change without notice. + +Task-based execution enables "call-now, fetch-later" patterns for long-running operations (see [Tasks](https://modelcontextprotocol.io/specification/latest/basic/utilities/tasks) in the MCP specification). 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). +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. +## See also -## More client features +- [`examples/client/`](https://github.com/modelcontextprotocol/typescript-sdk/tree/main/examples/client) — Full runnable client examples +- [Server guide](./server.md) — Building MCP servers with this SDK +- [MCP overview](https://modelcontextprotocol.io/docs/learn/architecture) — Protocol-level concepts: participants, layers, primitives +- [Migration guide](./migration.md) — Upgrading from previous SDK versions +- [FAQ](./faq.md) — Frequently asked questions and troubleshooting -The sections above cover the essentials. The table below links to additional capabilities. +### Additional examples -| Feature | Description | Reference | -|---------|-------------|-----------| +| Feature | Description | Example | +|---------|-------------|---------| | Parallel tool calls | Run multiple tool calls concurrently via `Promise.all` | [`parallelToolCallsClient.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/parallelToolCallsClient.ts) | -| 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) | +| 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) | diff --git a/docs/documents.md b/docs/documents.md index 37033ef0c..12455ab7f 100644 --- a/docs/documents.md +++ b/docs/documents.md @@ -11,7 +11,7 @@ children: # Documents - [Server Quickstart](./server-quickstart.md) – build a weather server from scratch and connect it to Claude for Desktop -- [Server](./server.md) – building MCP servers, transports, tools/resources/prompts, sampling, elicitation, tasks, and deployment patterns +- [Server](./server.md) – building MCP servers: transports, tools, resources, prompts, server-initiated requests, and deployment - [Client Quickstart](./client-quickstart.md) – build an LLM-powered chatbot that connects to an MCP server and calls its tools -- [Client](./client.md) – using the high-level client, transports, OAuth helpers, handling server‑initiated requests, and tasks +- [Client](./client.md) – building MCP clients: connecting, tools, resources, prompts, server-initiated requests, and error handling - [FAQ](./faq.md) – frequently asked questions and troubleshooting diff --git a/docs/server.md b/docs/server.md index 7a6a5d9fe..aec899cec 100644 --- a/docs/server.md +++ b/docs/server.md @@ -2,89 +2,58 @@ title: Server Guide --- -# Server overview +# Building MCP servers -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/). +This guide covers the TypeScript SDK APIs for building MCP servers. For protocol-level concepts — what tools, resources, and prompts are and when to use each — see the [MCP overview](https://modelcontextprotocol.io/docs/learn/architecture). Building a server takes three steps: -1. Create an {@linkcode @modelcontextprotocol/server!server/mcp.McpServer | McpServer} and register your [tools, resources, and prompts](#tools-resources-and-prompts). -2. Create a transport — [Streamable HTTP](#streamable-http) for remote servers or [stdio](#stdio) for local, process‑spawned integrations. -3. Wire the transport into your HTTP framework (or use stdio directly) and call `server.connect(transport)`. +1. Create an {@linkcode @modelcontextprotocol/server!server/mcp.McpServer | McpServer} and register your [tools](#tools), [resources](#resources), and [prompts](#prompts). +2. Create a transport — [Streamable HTTP](#streamable-http) for remote servers or [stdio](#stdio) for local integrations. +3. Connect them with `server.connect(transport)`. -The sections below cover each of these. For a feature‑rich starting point, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts) — remove what you don't need and register your own tools, resources, and prompts. For stateless or JSON‑response‑mode alternatives, see the examples linked in [Transports](#transports) below. +## Imports -## Transports - -### Streamable HTTP - -Streamable HTTP is the HTTP‑based transport. It supports: - -- Request/response over HTTP POST -- Server‑to‑client notifications over SSE (when enabled) -- Optional JSON‑only response mode with no SSE -- Session management and resumability - -A minimal stateful setup: - -```ts source="../examples/server/src/serverGuide.examples.ts#streamableHttp_stateful" -const server = new McpServer({ name: 'my-server', version: '1.0.0' }); +The examples below use these imports. Adjust based on which features and transport you need: -const transport = new NodeStreamableHTTPServerTransport({ - sessionIdGenerator: () => randomUUID() -}); +```ts source="../examples/server/src/serverGuide.examples.ts#imports" +import { randomUUID } from 'node:crypto'; -await server.connect(transport); +import { createMcpExpressApp } from '@modelcontextprotocol/express'; +import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node'; +import type { CallToolResult, ResourceLink } from '@modelcontextprotocol/server'; +import { completable, McpServer, ResourceTemplate, StdioServerTransport } from '@modelcontextprotocol/server'; +import * as z from 'zod/v4'; ``` -> [!NOTE] -> For full runnable examples, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts) (sessions, logging, tasks, elicitation, auth hooks), [`jsonResponseStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/jsonResponseStreamableHttp.ts) (`enableJsonResponse: true`, no SSE), and [`standaloneSseWithGetStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/standaloneSseWithGetStreamableHttp.ts) (notifications with Streamable HTTP GET + SSE). -> -> For protocol details, see [Transports](https://modelcontextprotocol.io/specification/latest/basic/transports) in the MCP specification. +## Transports -#### Stateless vs stateful sessions +MCP supports two transport mechanisms (see [Transport layer](https://modelcontextprotocol.io/docs/learn/architecture#transport-layer) in the MCP overview). Choose based on deployment model: -Streamable HTTP can run: +- **Streamable HTTP** — for remote servers accessible over the network. +- **stdio** — for local servers spawned as child processes (Claude Desktop, CLI tools). -- **Stateless** – no session tracking, ideal for simple API‑style servers. -- **Stateful** – sessions have IDs, and you can enable resumability and advanced features. +### Streamable HTTP -The key difference is the `sessionIdGenerator` option. Pass `undefined` for stateless mode: +Create a {@linkcode @modelcontextprotocol/node!streamableHttp.NodeStreamableHTTPServerTransport | NodeStreamableHTTPServerTransport} and connect it to your server: -```ts source="../examples/server/src/serverGuide.examples.ts#streamableHttp_stateless" +```ts source="../examples/server/src/serverGuide.examples.ts#streamableHttp_stateful" const server = new McpServer({ name: 'my-server', version: '1.0.0' }); const transport = new NodeStreamableHTTPServerTransport({ - sessionIdGenerator: undefined + sessionIdGenerator: () => randomUUID() }); await server.connect(transport); ``` -> [!NOTE] -> For full runnable examples, see [`simpleStatelessStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStatelessStreamableHttp.ts) (stateless) and [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts) (stateful with resumability). - -#### JSON response mode - -If you do not need SSE streaming, set `enableJsonResponse: true`. The server will return plain JSON responses to every POST and reject GET requests with `405`: - -```ts source="../examples/server/src/serverGuide.examples.ts#streamableHttp_jsonResponse" -const server = new McpServer({ name: 'my-server', version: '1.0.0' }); - -const transport = new NodeStreamableHTTPServerTransport({ - sessionIdGenerator: () => randomUUID(), - enableJsonResponse: true -}); - -await server.connect(transport); -``` +**Options:** Set `sessionIdGenerator` to a function (shown above) for stateful sessions. Set it to `undefined` for stateless mode (simpler, but does not support resumability). Set `enableJsonResponse: true` to return plain JSON instead of SSE streams. -> [!NOTE] -> For a full runnable example, see [`jsonResponseStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/jsonResponseStreamableHttp.ts). +For a complete server with sessions, logging, and CORS mounted on Express, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts). ### stdio -For local, process‑spawned integrations (Claude Desktop, CLI tools), use {@linkcode @modelcontextprotocol/server!server/stdio.StdioServerTransport | StdioServerTransport}: +For local, process-spawned integrations, use {@linkcode @modelcontextprotocol/server!server/stdio.StdioServerTransport | StdioServerTransport}: ```ts source="../examples/server/src/serverGuide.examples.ts#stdio_basic" const server = new McpServer({ name: 'my-server', version: '1.0.0' }); @@ -92,13 +61,25 @@ const transport = new StdioServerTransport(); await server.connect(transport); ``` -## Tools, resources, and prompts +## Server instructions -### Tools +Instructions describe how to use the server and its features — cross-tool relationships, workflow patterns, and constraints (see [Instructions](https://modelcontextprotocol.io/specification/latest/basic/lifecycle#instructions) in the MCP specification). Clients may add them to the system prompt. Instructions should not duplicate information already in tool descriptions. -Tools let MCP clients ask your server to take actions. They are usually the main way that LLMs call into your application. +```ts source="../examples/server/src/serverGuide.examples.ts#instructions_basic" +const server = new McpServer( + { name: 'db-server', version: '1.0.0' }, + { + instructions: + 'Always call list_tables before running queries. Use validate_schema before migrate_schema for safe migrations. Results are limited to 1000 rows.' + } +); +``` -A typical registration with {@linkcode @modelcontextprotocol/server!server/mcp.McpServer#registerTool | registerTool}: +## Tools + +Tools let clients invoke actions on your server — they are usually the main way LLMs call into your application (see [Tools](https://modelcontextprotocol.io/docs/learn/server-concepts#tools) in the MCP overview). + +Register a tool with {@linkcode @modelcontextprotocol/server!server/mcp.McpServer#registerTool | registerTool}. Provide an `inputSchema` (Zod) to validate arguments, and optionally an `outputSchema` for structured return values: ```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_basic" server.registerTool( @@ -122,14 +103,9 @@ server.registerTool( ); ``` -> [!NOTE] -> For full runnable examples, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts) and [`toolWithSampleServer.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/toolWithSampleServer.ts). -> -> For protocol details, see [Tools](https://modelcontextprotocol.io/specification/latest/server/tools) in the MCP specification. - -#### `ResourceLink` outputs +### `ResourceLink` outputs -Tools can return `resource_link` content items to reference large resources without embedding them directly, allowing clients to fetch only what they need: +Tools can return `resource_link` content items to reference large resources without embedding them, letting clients fetch only what they need: ```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_resourceLink" server.registerTool( @@ -158,19 +134,66 @@ 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). +### Tool annotations + +Tools can include annotations that hint at their behavior — whether a tool is read-only, destructive, or idempotent. Annotations help clients present tools appropriately without changing execution semantics: + +```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_annotations" +server.registerTool( + 'delete-file', + { + description: 'Delete a file from the project', + inputSchema: z.object({ path: z.string() }), + annotations: { + title: 'Delete File', + destructiveHint: true, + idempotentHint: true + } + }, + async ({ path }): Promise => { + // ... perform deletion ... + return { content: [{ type: 'text', text: `Deleted ${path}` }] }; + } +); +``` + +### Error handling -#### Tool annotations +Return `isError: true` to report tool-level errors. The LLM sees these and can self-correct, unlike protocol-level errors which are hidden from it: -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. +```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_errorHandling" +server.registerTool( + 'fetch-data', + { + description: 'Fetch data from a URL', + inputSchema: z.object({ url: z.string() }) + }, + async ({ url }): Promise => { + try { + const res = await fetch(url); + if (!res.ok) { + return { + content: [{ type: 'text', text: `HTTP ${res.status}: ${res.statusText}` }], + isError: true + }; + } + const text = await res.text(); + return { content: [{ type: 'text', text }] }; + } catch (error) { + return { + content: [{ type: 'text', text: `Failed: ${error instanceof Error ? error.message : String(error)}` }], + isError: true + }; + } + } +); +``` -> [!NOTE] -> For tool annotations in a full server, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts). +If a handler throws instead of returning `isError`, the SDK catches the exception and converts it to `{ isError: true }` automatically — so an explicit try/catch is optional but gives you control over the error message. When `isError` is true, output schema validation is skipped. -### Resources +## Resources -Resources expose data to clients, but should not perform heavy computation or side‑effects. They are ideal for configuration, documents, or other reference data. +Resources expose read-only data — files, database schemas, configuration — that the host application can retrieve and attach as context for the model (see [Resources](https://modelcontextprotocol.io/docs/learn/server-concepts#resources) in the MCP overview). Unlike [tools](#tools), which the LLM invokes on its own, resources are application-controlled: the host decides which resources to fetch and how to present them. A static resource at a fixed URI: @@ -189,7 +212,7 @@ server.registerResource( ); ``` -Dynamic resources use {@linkcode @modelcontextprotocol/server!server/mcp.ResourceTemplate | ResourceTemplate} and can support completions on path parameters: +Dynamic resources use {@linkcode @modelcontextprotocol/server!server/mcp.ResourceTemplate | ResourceTemplate} with URI patterns. The `list` callback lets clients discover available instances: ```ts source="../examples/server/src/serverGuide.examples.ts#registerResource_template" server.registerResource( @@ -218,16 +241,9 @@ server.registerResource( ); ``` -> [!NOTE] -> For full runnable examples of resources, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts). -> -> For protocol details, see [Resources](https://modelcontextprotocol.io/specification/latest/server/resources) in the MCP specification. - -### Prompts +## Prompts -Prompts are reusable templates that help humans (or client UIs) talk to models in a consistent way. They are declared on the server and listed through MCP. - -A minimal prompt: +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. ```ts source="../examples/server/src/serverGuide.examples.ts#registerPrompt_basic" server.registerPrompt( @@ -253,12 +269,7 @@ server.registerPrompt( ); ``` -> [!NOTE] -> For prompts integrated into a full server, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts). -> -> For protocol details, see [Prompts](https://modelcontextprotocol.io/specification/latest/server/prompts) in the MCP specification. - -### Completions +## Completions Both prompts and resources can support argument completions. Wrap a field in the `argsSchema` with {@linkcode @modelcontextprotocol/server!server/completable.completable | completable()} to provide autocompletion suggestions: @@ -288,15 +299,17 @@ server.registerPrompt( ); ``` -### Logging +## Logging + +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). -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: +Declare the `logging` capability, then call `ctx.mcpReq.log(level, data)` (from {@linkcode @modelcontextprotocol/server!index.ServerContext | ServerContext}) inside any handler: ```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: +Then log from any handler: ```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_logging" server.registerTool( @@ -315,18 +328,54 @@ server.registerTool( ); ``` -> [!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. +## Progress -## Server‑initiated requests +Progress notifications let a tool report incremental status updates during long-running operations (see [Progress](https://modelcontextprotocol.io/specification/latest/basic/utilities/progress) in the MCP specification). -MCP is bidirectional — servers can also send requests *to* the client during tool execution, as long as the client declares matching capabilities. +If the client includes a `progressToken` in the request `_meta`, send `notifications/progress` via `ctx.mcpReq.notify()` (from {@linkcode @modelcontextprotocol/server!index.BaseContext | BaseContext}): + +```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_progress" +server.registerTool( + 'process-files', + { + description: 'Process files with progress updates', + inputSchema: z.object({ files: z.array(z.string()) }) + }, + async ({ files }, ctx): Promise => { + const progressToken = ctx.mcpReq._meta?.progressToken; + + for (let i = 0; i < files.length; i++) { + // ... process files[i] ... + + if (progressToken !== undefined) { + await ctx.mcpReq.notify({ + method: 'notifications/progress', + params: { + progressToken, + progress: i + 1, + total: files.length, + message: `Processed ${files[i]}` + } + }); + } + } + + return { content: [{ type: 'text', text: `Processed ${files.length} files` }] }; + } +); +``` + +`progress` must increase on each call. `total` and `message` are optional. If the client does not provide a `progressToken`, skip the notification. + +## 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 -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: +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: ```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_sampling" server.registerTool( @@ -360,20 +409,19 @@ server.registerTool( ); ``` -> [!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. +For a full runnable example, see [`toolWithSampleServer.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/toolWithSampleServer.ts). ### 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: +Elicitation lets a tool handler request direct input from the user — form fields, confirmations, or a redirect to a URL (see [Elicitation](https://modelcontextprotocol.io/docs/learn/client-concepts#elicitation) in the MCP overview). It 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. +- **Form** (`mode: 'form'`) — collects non-sensitive data via a schema-driven form. +- **URL** (`mode: 'url'`) — opens a browser URL for sensitive data or secure flows (API keys, payments, OAuth). > [!IMPORTANT] -> Sensitive information **must not** be collected via form elicitation; always use URL elicitation or out‑of‑band flows for secrets. +> Sensitive information must not be collected via form elicitation; always use URL elicitation or out-of-band flows for secrets. + +Call `ctx.mcpReq.elicitInput(params)` (from {@linkcode @modelcontextprotocol/server!index.ServerContext | ServerContext}) inside a tool handler: ```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_elicitation" server.registerTool( @@ -415,30 +463,78 @@ server.registerTool( ); ``` -> [!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. +For runnable examples, see [`elicitationFormExample.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/elicitationFormExample.ts) (form) and [`elicitationUrlExample.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/elicitationUrlExample.ts) (URL). + +### Roots + +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" +server.registerTool( + 'list-workspace-files', + { + description: 'List files across all workspace roots', + inputSchema: z.object({}) + }, + async (_args, _ctx): Promise => { + const { roots } = await server.server.listRoots(); + const summary = roots.map(r => `${r.name ?? r.uri}: ${r.uri}`).join('\n'); + return { content: [{ type: 'text', text: summary }] }; + } +); +``` ## 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: +> [!WARNING] +> The tasks API is experimental and may change without notice. + +Task-based execution enables "call-now, fetch-later" patterns for long-running operations (see [Tasks](https://modelcontextprotocol.io/specification/latest/basic/utilities/tasks) in the MCP specification). 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). +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. +## Shutdown + +For stateful multi-session HTTP servers, capture the `http.Server` from `app.listen()` so you can stop accepting connections, then close each session transport: + +```ts source="../examples/server/src/serverGuide.examples.ts#shutdown_statefulHttp" +// Capture the http.Server so it can be closed on shutdown +const httpServer = app.listen(3000); + +process.on('SIGINT', async () => { + httpServer.close(); + + for (const [sessionId, transport] of transports) { + await transport.close(); + transports.delete(sessionId); + } + + process.exit(0); +}); +``` + +Calling {@linkcode @modelcontextprotocol/server!index.Transport#close | transport.close()} closes SSE streams and rejects any pending outbound requests. In-flight tool handlers are not automatically drained — they are terminated when the process exits. + +For stdio servers, {@linkcode @modelcontextprotocol/server!server/mcp.McpServer#close | server.close()} is sufficient: + +```ts source="../examples/server/src/serverGuide.examples.ts#shutdown_stdio" +process.on('SIGINT', async () => { + await server.close(); + process.exit(0); +}); +``` + +For a complete multi-session server with shutdown handling, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts). ## 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: +MCP servers running on localhost are vulnerable to DNS rebinding attacks. Use {@linkcode @modelcontextprotocol/express!express.createMcpExpressApp | 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) @@ -460,14 +556,20 @@ const app = createMcpExpressApp({ }); ``` -## More server features +## See also + +- [`examples/server/`](https://github.com/modelcontextprotocol/typescript-sdk/tree/main/examples/server) — Full runnable server examples +- [Client guide](./client.md) — Building MCP clients with this SDK +- [MCP overview](https://modelcontextprotocol.io/docs/learn/architecture) — Protocol-level concepts: participants, layers, primitives +- [Migration guide](./migration.md) — Upgrading from previous SDK versions +- [FAQ](./faq.md) — Frequently asked questions and troubleshooting -The sections above cover the essentials. The table below links to additional capabilities demonstrated in the runnable examples. +### Additional examples -| Feature | Description | Reference | -|---------|-------------|-----------| +| Feature | Description | Example | +|---------|-------------|---------| | Web Standard transport | Deploy on Cloudflare Workers, Deno, or Bun | [`honoWebStandardStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/honoWebStandardStreamableHttp.ts) | | 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) | -| 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) | +| CORS | Expose MCP headers for browser clients | [`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/client/src/clientGuide.examples.ts b/examples/client/src/clientGuide.examples.ts index 8cefcf224..91f0afd85 100644 --- a/examples/client/src/clientGuide.examples.ts +++ b/examples/client/src/clientGuide.examples.ts @@ -7,6 +7,8 @@ * @module */ +//#region imports +import type { Prompt, Resource, Tool } from '@modelcontextprotocol/client'; import { applyMiddlewares, CallToolResultSchema, @@ -14,10 +16,14 @@ import { ClientCredentialsProvider, createMiddleware, PrivateKeyJwtProvider, + ProtocolError, + SdkError, + SdkErrorCode, SSEClientTransport, StdioClientTransport, StreamableHTTPClientTransport } from '@modelcontextprotocol/client'; +//#endregion imports // --------------------------------------------------------------------------- // Connecting to a server @@ -69,6 +75,33 @@ async function connect_sseFallback(url: string) { //#endregion connect_sseFallback } +// --------------------------------------------------------------------------- +// Disconnecting +// --------------------------------------------------------------------------- + +/** Example: Graceful disconnect for Streamable HTTP. */ +async function disconnect_streamableHttp(client: Client, transport: StreamableHTTPClientTransport) { + //#region disconnect_streamableHttp + await transport.terminateSession(); // notify the server (recommended) + await client.close(); + //#endregion disconnect_streamableHttp +} + +// --------------------------------------------------------------------------- +// Server instructions +// --------------------------------------------------------------------------- + +/** Example: Access server instructions after connecting. */ +async function serverInstructions_basic(client: Client) { + //#region serverInstructions_basic + const instructions = client.getInstructions(); + + const systemPrompt = ['You are a helpful assistant.', instructions].filter(Boolean).join('\n\n'); + + console.log(systemPrompt); + //#endregion serverInstructions_basic +} + // --------------------------------------------------------------------------- // Authentication // --------------------------------------------------------------------------- @@ -110,10 +143,16 @@ async function auth_privateKeyJwt(pemEncodedKey: string) { /** Example: List and call tools. */ async function callTool_basic(client: Client) { //#region callTool_basic - const { tools } = await client.listTools(); + const allTools: Tool[] = []; + let toolCursor: string | undefined; + do { + const { tools, nextCursor } = await client.listTools({ cursor: toolCursor }); + allTools.push(...tools); + toolCursor = nextCursor; + } while (toolCursor); console.log( 'Available tools:', - tools.map(t => t.name) + allTools.map(t => t.name) ); const result = await client.callTool({ @@ -124,13 +163,48 @@ async function callTool_basic(client: Client) { //#endregion callTool_basic } +/** Example: Structured tool output. */ +async function callTool_structuredOutput(client: Client) { + //#region callTool_structuredOutput + const result = await client.callTool({ + name: 'calculate-bmi', + arguments: { weightKg: 70, heightM: 1.75 } + }); + + // Machine-readable output for the client application + if (result.structuredContent) { + console.log(result.structuredContent); // e.g. { bmi: 22.86 } + } + //#endregion callTool_structuredOutput +} + +/** Example: Track progress of a long-running tool call. */ +async function callTool_progress(client: Client) { + //#region callTool_progress + const result = await client.callTool({ name: 'long-operation', arguments: {} }, undefined, { + onprogress: ({ progress, total }) => { + console.log(`Progress: ${progress}/${total ?? '?'}`); + }, + resetTimeoutOnProgress: true, + maxTotalTimeout: 600_000 + }); + console.log(result.content); + //#endregion callTool_progress +} + /** Example: List and read resources. */ async function readResource_basic(client: Client) { //#region readResource_basic - const { resources } = await client.listResources(); + const allResources: Resource[] = []; + let resourceCursor: string | undefined; + do { + const { resources, nextCursor } = await client.listResources({ cursor: resourceCursor }); + allResources.push(...resources); + resourceCursor = nextCursor; + } while (resourceCursor); console.log( 'Available resources:', - resources.map(r => r.name) + allResources.map(r => r.name) ); const { contents } = await client.readResource({ uri: 'config://app' }); @@ -140,13 +214,36 @@ async function readResource_basic(client: Client) { //#endregion readResource_basic } +/** Example: Subscribe to resource changes. */ +async function subscribeResource_basic(client: Client) { + //#region subscribeResource_basic + await client.subscribeResource({ uri: 'config://app' }); + + client.setNotificationHandler('notifications/resources/updated', async notification => { + if (notification.params.uri === 'config://app') { + const { contents } = await client.readResource({ uri: 'config://app' }); + console.log('Config updated:', contents); + } + }); + + // Later: stop receiving updates + await client.unsubscribeResource({ uri: 'config://app' }); + //#endregion subscribeResource_basic +} + /** Example: List and get prompts. */ async function getPrompt_basic(client: Client) { //#region getPrompt_basic - const { prompts } = await client.listPrompts(); + const allPrompts: Prompt[] = []; + let promptCursor: string | undefined; + do { + const { prompts, nextCursor } = await client.listPrompts({ cursor: promptCursor }); + allPrompts.push(...prompts); + promptCursor = nextCursor; + } while (promptCursor); console.log( 'Available prompts:', - prompts.map(p => p.name) + allPrompts.map(p => p.name) ); const { messages } = await client.getPrompt({ @@ -181,7 +278,7 @@ async function complete_basic(client: Client) { /** Example: Handle log messages and list-change notifications. */ function notificationHandler_basic(client: Client) { //#region notificationHandler_basic - // Server log messages (e.g. from ctx.mcpReq.log() in tool handlers) + // Server log messages (sent by the server during request processing) client.setNotificationHandler('notifications/message', notification => { const { level, data } = notification.params; console.log(`[${level}]`, data); @@ -195,6 +292,13 @@ function notificationHandler_basic(client: Client) { //#endregion notificationHandler_basic } +/** Example: Control server log level. */ +async function setLoggingLevel_basic(client: Client) { + //#region setLoggingLevel_basic + await client.setLoggingLevel('warning'); + //#endregion setLoggingLevel_basic +} + /** Example: Automatic list-change tracking via the listChanged option. */ async function listChanged_basic() { //#region listChanged_basic @@ -278,6 +382,86 @@ function elicitation_handler(client: Client) { //#endregion elicitation_handler } +/** Example: Expose filesystem roots to the server. */ +function roots_handler(client: Client) { + //#region roots_handler + client.setRequestHandler('roots/list', async () => { + return { + roots: [ + { uri: 'file:///home/user/projects/my-app', name: 'My App' }, + { uri: 'file:///home/user/data', name: 'Data' } + ] + }; + }); + //#endregion roots_handler +} + +// --------------------------------------------------------------------------- +// Error handling +// --------------------------------------------------------------------------- + +/** Example: Tool errors vs protocol errors. */ +async function errorHandling_toolErrors(client: Client) { + //#region errorHandling_toolErrors + try { + const result = await client.callTool({ + name: 'fetch-data', + arguments: { url: 'https://example.com' } + }); + + // Tool-level error: the tool ran but reported a problem + if (result.isError) { + console.error('Tool error:', result.content); + return; + } + + console.log('Success:', result.content); + } catch (error) { + // Protocol-level error: the request itself failed + if (error instanceof ProtocolError) { + console.error(`Protocol error ${error.code}: ${error.message}`); + } else if (error instanceof SdkError) { + console.error(`SDK error [${error.code}]: ${error.message}`); + } else { + throw error; + } + } + //#endregion errorHandling_toolErrors +} + +/** Example: Connection lifecycle callbacks. */ +function errorHandling_lifecycle(client: Client) { + //#region errorHandling_lifecycle + // Out-of-band errors (SSE disconnects, parse errors) + client.onerror = error => { + console.error('Transport error:', error.message); + }; + + // Connection closed (pending requests are rejected with CONNECTION_CLOSED) + client.onclose = () => { + console.log('Connection closed'); + }; + //#endregion errorHandling_lifecycle +} + +/** Example: Custom timeouts. */ +async function errorHandling_timeout(client: Client) { + //#region errorHandling_timeout + try { + const result = await client.callTool( + { name: 'slow-task', arguments: {} }, + undefined, + { timeout: 120_000 } // 2 minutes instead of the default 60 seconds + ); + console.log(result.content); + } catch (error) { + if (error instanceof SdkError && error.code === SdkErrorCode.RequestTimeout) { + console.error('Request timed out'); + } + } + //#endregion errorHandling_timeout +} + // --------------------------------------------------------------------------- // Advanced patterns // --------------------------------------------------------------------------- @@ -325,16 +509,26 @@ async function resumptionToken_basic(client: Client) { void connect_streamableHttp; void connect_stdio; void connect_sseFallback; +void disconnect_streamableHttp; +void serverInstructions_basic; void auth_clientCredentials; void auth_privateKeyJwt; void callTool_basic; +void callTool_structuredOutput; +void callTool_progress; void readResource_basic; +void subscribeResource_basic; void getPrompt_basic; void complete_basic; void notificationHandler_basic; +void setLoggingLevel_basic; void listChanged_basic; void capabilities_declaration; void sampling_handler; void elicitation_handler; +void roots_handler; +void errorHandling_toolErrors; +void errorHandling_lifecycle; +void errorHandling_timeout; void middleware_basic; void resumptionToken_basic; diff --git a/examples/server/src/serverGuide.examples.ts b/examples/server/src/serverGuide.examples.ts index ca4be3d4a..70cd002d1 100644 --- a/examples/server/src/serverGuide.examples.ts +++ b/examples/server/src/serverGuide.examples.ts @@ -7,6 +7,7 @@ * @module */ +//#region imports import { randomUUID } from 'node:crypto'; import { createMcpExpressApp } from '@modelcontextprotocol/express'; @@ -14,6 +15,25 @@ import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node'; import type { CallToolResult, ResourceLink } from '@modelcontextprotocol/server'; import { completable, McpServer, ResourceTemplate, StdioServerTransport } from '@modelcontextprotocol/server'; import * as z from 'zod/v4'; +//#endregion imports + +// --------------------------------------------------------------------------- +// Server instructions +// --------------------------------------------------------------------------- + +/** Example: McpServer with instructions for LLM guidance. */ +function instructions_basic() { + //#region instructions_basic + const server = new McpServer( + { name: 'db-server', version: '1.0.0' }, + { + instructions: + 'Always call list_tables before running queries. Use validate_schema before migrate_schema for safe migrations. Results are limited to 1000 rows.' + } + ); + //#endregion instructions_basic + return server; +} // --------------------------------------------------------------------------- // Tools, resources, and prompts @@ -74,6 +94,59 @@ function registerTool_resourceLink(server: McpServer) { //#endregion registerTool_resourceLink } +/** Example: Tool with explicit error handling using isError. */ +function registerTool_errorHandling(server: McpServer) { + //#region registerTool_errorHandling + server.registerTool( + 'fetch-data', + { + description: 'Fetch data from a URL', + inputSchema: z.object({ url: z.string() }) + }, + async ({ url }): Promise => { + try { + const res = await fetch(url); + if (!res.ok) { + return { + content: [{ type: 'text', text: `HTTP ${res.status}: ${res.statusText}` }], + isError: true + }; + } + const text = await res.text(); + return { content: [{ type: 'text', text }] }; + } catch (error) { + return { + content: [{ type: 'text', text: `Failed: ${error instanceof Error ? error.message : String(error)}` }], + isError: true + }; + } + } + ); + //#endregion registerTool_errorHandling +} + +/** Example: Tool with annotations hinting at behavior. */ +function registerTool_annotations(server: McpServer) { + //#region registerTool_annotations + server.registerTool( + 'delete-file', + { + description: 'Delete a file from the project', + inputSchema: z.object({ path: z.string() }), + annotations: { + title: 'Delete File', + destructiveHint: true, + idempotentHint: true + } + }, + async ({ path }): Promise => { + // ... perform deletion ... + return { content: [{ type: 'text', text: `Deleted ${path}` }] }; + } + ); + //#endregion registerTool_annotations +} + /** Example: Registering a static resource at a fixed URI. */ function registerResource_static(server: McpServer) { //#region registerResource_static @@ -207,6 +280,44 @@ function registerTool_logging() { return server; } +// --------------------------------------------------------------------------- +// Progress +// --------------------------------------------------------------------------- + +/** Example: Tool that sends progress notifications during a long-running operation. */ +function registerTool_progress(server: McpServer) { + //#region registerTool_progress + server.registerTool( + 'process-files', + { + description: 'Process files with progress updates', + inputSchema: z.object({ files: z.array(z.string()) }) + }, + async ({ files }, ctx): Promise => { + const progressToken = ctx.mcpReq._meta?.progressToken; + + for (let i = 0; i < files.length; i++) { + // ... process files[i] ... + + if (progressToken !== undefined) { + await ctx.mcpReq.notify({ + method: 'notifications/progress', + params: { + progressToken, + progress: i + 1, + total: files.length, + message: `Processed ${files[i]}` + } + }); + } + } + + return { content: [{ type: 'text', text: `Processed ${files.length} files` }] }; + } + ); + //#endregion registerTool_progress +} + // --------------------------------------------------------------------------- // Server-initiated requests // --------------------------------------------------------------------------- @@ -289,6 +400,24 @@ function registerTool_elicitation(server: McpServer) { //#endregion registerTool_elicitation } +/** Example: Tool that requests the client's filesystem roots. */ +function registerTool_roots(server: McpServer) { + //#region registerTool_roots + server.registerTool( + 'list-workspace-files', + { + description: 'List files across all workspace roots', + inputSchema: z.object({}) + }, + async (_args, _ctx): Promise => { + const { roots } = await server.server.listRoots(); + const summary = roots.map(r => `${r.name ?? r.uri}: ${r.uri}`).join('\n'); + return { content: [{ type: 'text', text: summary }] }; + } + ); + //#endregion registerTool_roots +} + // --------------------------------------------------------------------------- // Transports // --------------------------------------------------------------------------- @@ -342,6 +471,39 @@ async function stdio_basic() { //#endregion stdio_basic } +// --------------------------------------------------------------------------- +// Shutdown +// --------------------------------------------------------------------------- + +/** Example: Graceful shutdown for a stateful multi-session HTTP server. */ +function shutdown_statefulHttp(app: ReturnType, transports: Map) { + //#region shutdown_statefulHttp + // Capture the http.Server so it can be closed on shutdown + const httpServer = app.listen(3000); + + process.on('SIGINT', async () => { + httpServer.close(); + + for (const [sessionId, transport] of transports) { + await transport.close(); + transports.delete(sessionId); + } + + process.exit(0); + }); + //#endregion shutdown_statefulHttp +} + +/** Example: Graceful shutdown for a stdio server. */ +function shutdown_stdio(server: McpServer) { + //#region shutdown_stdio + process.on('SIGINT', async () => { + await server.close(); + process.exit(0); + }); + //#endregion shutdown_stdio +} + // --------------------------------------------------------------------------- // DNS rebinding protection // --------------------------------------------------------------------------- @@ -373,11 +535,16 @@ function dnsRebinding_allowedHosts() { } // Suppress unused-function warnings (functions exist solely for type-checking) +void instructions_basic; void registerTool_basic; void registerTool_resourceLink; +void registerTool_errorHandling; +void registerTool_annotations; void registerTool_logging; +void registerTool_progress; void registerTool_sampling; void registerTool_elicitation; +void registerTool_roots; void registerResource_static; void registerResource_template; void registerPrompt_basic; @@ -386,5 +553,7 @@ void streamableHttp_stateful; void streamableHttp_stateless; void streamableHttp_jsonResponse; void stdio_basic; +void shutdown_statefulHttp; +void shutdown_stdio; void dnsRebinding_basic; void dnsRebinding_allowedHosts;