From 837e315d31ee15b0f6286618043c7153e84db4dd Mon Sep 17 00:00:00 2001 From: Jonathan Hefner Date: Tue, 10 Feb 2026 11:55:43 -0600 Subject: [PATCH 1/2] Add new sections and inline code snippets to `docs/client.md` New doc sections: Connecting to a server (3 transports), Authentication (client credentials, private key JWT, full OAuth), Using server features (tools, resources, prompts, completions), Notifications (`listChanged` first, manual handlers second), Handling server-initiated requests (capabilities, sampling, elicitation), Advanced patterns (middleware, resumption tokens), and a More client features reference table. New companion file `examples/client/src/clientGuide.examples.ts` provides the snippet source with 16 regions covering: `connect_streamableHttp`, `connect_stdio`, `connect_sseFallback`, `auth_clientCredentials`, `auth_privateKeyJwt`, `callTool_basic`, `readResource_basic`, `getPrompt_basic`, `complete_basic`, `listChanged_basic`, `notificationHandler_basic`, `capabilities_declaration`, `sampling_handler`, `elicitation_handler`, `middleware_basic`, and `resumptionToken_basic`. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/client.md | 376 ++++++++++++++++++-- examples/client/src/clientGuide.examples.ts | 340 ++++++++++++++++++ 2 files changed, 677 insertions(+), 39 deletions(-) create mode 100644 examples/client/src/clientGuide.examples.ts diff --git a/docs/client.md b/docs/client.md index 41f91656a..caa932409 100644 --- a/docs/client.md +++ b/docs/client.md @@ -4,61 +4,359 @@ title: Client ## Client overview -The SDK provides a high-level `Client` class that connects to MCP servers over different transports: +The SDK provides a {@linkcode @modelcontextprotocol/client!client/client.Client | Client} class from `@modelcontextprotocol/client` that connects to MCP servers over different transports: -- `StdioClientTransport` – for local processes you spawn. -- `StreamableHTTPClientTransport` – for remote HTTP servers. -- `SSEClientTransport` – for legacy HTTP+SSE servers (deprecated). +- **Streamable HTTP** – for remote HTTP servers. +- **stdio** – for local processes you spawn. +- **SSE** – for legacy HTTP+SSE servers (deprecated). -Runnable client examples live under: +For a feature‑rich starting point, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleStreamableHttp.ts). -- [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleStreamableHttp.ts) -- [`streamableHttpWithSseFallbackClient.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/streamableHttpWithSseFallbackClient.ts) -- [`ssePollingClient.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/ssePollingClient.ts) -- [`multipleClientsParallel.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/multipleClientsParallel.ts) -- [`parallelToolCallsClient.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/parallelToolCallsClient.ts) +## Connecting to a server -## Connecting and basic operations +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. -A typical flow: +### Streamable HTTP -1. Construct a `Client` with name, version and capabilities. -2. Create a transport and call `client.connect(transport)`. -3. Use high-level helpers: - - `listTools`, `callTool` - - `listPrompts`, `getPrompt` - - `listResources`, `readResource` +For remote HTTP servers, use {@linkcode @modelcontextprotocol/client!client/streamableHttp.StreamableHTTPClientTransport | StreamableHTTPClientTransport}: -See [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleStreamableHttp.ts) for an interactive CLI client that exercises these methods and shows how to handle notifications, elicitation and tasks. +```ts source="../examples/client/src/clientGuide.examples.ts#connect_streamableHttp" +const client = new Client({ name: 'my-client', version: '1.0.0' }); -## Transports and backwards compatibility +const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp')); -To support both modern Streamable HTTP and legacy SSE servers, use a client that: +await client.connect(transport); +``` -1. Tries `StreamableHTTPClientTransport`. -2. Falls back to `SSEClientTransport` on a 4xx response. +> [!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). -Runnable example: +### stdio -- [`streamableHttpWithSseFallbackClient.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/streamableHttpWithSseFallbackClient.ts) +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: -## OAuth client authentication helpers +```ts source="../examples/client/src/clientGuide.examples.ts#connect_stdio" +const client = new Client({ name: 'my-client', version: '1.0.0' }); -For OAuth-secured MCP servers, the client `auth` module exposes: +const transport = new StdioClientTransport({ + command: 'node', + args: ['server.js'] +}); -- `ClientCredentialsProvider` -- `PrivateKeyJwtProvider` -- `StaticPrivateKeyJwtProvider` +await client.connect(transport); +``` -Examples: +### SSE fallback for legacy servers -- [`simpleOAuthClient.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleOAuthClient.ts) -- [`simpleOAuthClientProvider.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleOAuthClientProvider.ts) -- [`simpleClientCredentials.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleClientCredentials.ts) -- Server-side auth demo: [`demoInMemoryOAuthProvider.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/shared/src/demoInMemoryOAuthProvider.ts) (tests live under `examples/shared/test/demoInMemoryOAuthProvider.test.ts`) +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: -These examples show how to: +```ts source="../examples/client/src/clientGuide.examples.ts#connect_sseFallback" +const baseUrl = new URL(url); -- Perform dynamic client registration if needed. -- Acquire access tokens. -- Attach OAuth credentials to Streamable HTTP requests. +try { + // Try modern Streamable HTTP transport first + const client = new Client({ name: 'my-client', version: '1.0.0' }); + const transport = new StreamableHTTPClientTransport(baseUrl); + await client.connect(transport); + return { client, transport }; +} catch { + // Fall back to legacy SSE transport + const client = new Client({ name: 'my-client', version: '1.0.0' }); + const transport = new SSEClientTransport(baseUrl); + await client.connect(transport); + return { client, transport }; +} +``` + +> [!NOTE] +> For a complete example with error reporting, see [`streamableHttpWithSseFallbackClient.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/streamableHttpWithSseFallbackClient.ts). + +## 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. + +### Client credentials + +{@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({ + clientId: 'my-service', + clientSecret: 'my-secret' +}); + +const client = new Client({ name: 'my-client', version: '1.0.0' }); + +const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'), { authProvider }); + +await client.connect(transport); +``` + +### Private key JWT + +{@linkcode @modelcontextprotocol/client!client/authExtensions.PrivateKeyJwtProvider | PrivateKeyJwtProvider} signs JWT assertions for the `private_key_jwt` token endpoint auth method, avoiding a shared client secret: + +```ts source="../examples/client/src/clientGuide.examples.ts#auth_privateKeyJwt" +const authProvider = new PrivateKeyJwtProvider({ + clientId: 'my-service', + privateKey: pemEncodedKey, + algorithm: 'RS256' +}); + +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). + +### 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. + +> [!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). + +## Using server features + +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. + +> [!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: + +```ts source="../examples/client/src/clientGuide.examples.ts#callTool_basic" +const { tools } = await client.listTools(); +console.log( + 'Available tools:', + tools.map(t => t.name) +); + +const result = await client.callTool({ + name: 'calculate-bmi', + arguments: { weightKg: 70, heightM: 1.75 } +}); +console.log(result.content); +``` + +### Resources + +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: + +```ts source="../examples/client/src/clientGuide.examples.ts#readResource_basic" +const { resources } = await client.listResources(); +console.log( + 'Available resources:', + resources.map(r => r.name) +); + +const { contents } = await client.readResource({ uri: 'config://app' }); +for (const item of contents) { + console.log(item); +} +``` + +### Prompts + +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: + +```ts source="../examples/client/src/clientGuide.examples.ts#getPrompt_basic" +const { prompts } = await client.listPrompts(); +console.log( + 'Available prompts:', + prompts.map(p => p.name) +); + +const { messages } = await client.getPrompt({ + name: 'review-code', + arguments: { code: 'console.log("hello")' } +}); +console.log(messages); +``` + +### 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: + +```ts source="../examples/client/src/clientGuide.examples.ts#complete_basic" +const { completion } = await client.complete({ + ref: { + type: 'ref/prompt', + name: 'review-code' + }, + argument: { + name: 'language', + value: 'type' + } +}); +console.log(completion.values); // e.g. ['typescript'] +``` + +## Notifications + +### 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: + +```ts source="../examples/client/src/clientGuide.examples.ts#listChanged_basic" +const client = new Client( + { name: 'my-client', version: '1.0.0' }, + { + listChanged: { + tools: { + onChanged: (error, tools) => { + if (error) { + console.error('Failed to refresh tools:', error); + return; + } + console.log('Tools updated:', tools); + } + }, + prompts: { + onChanged: (error, prompts) => console.log('Prompts updated:', prompts) + } + } + } +); +``` + +### Manual notification handlers + +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) +client.setNotificationHandler('notifications/message', notification => { + const { level, data } = notification.params; + console.log(`[${level}]`, data); +}); + +// Server's resource list changed — re-fetch the list +client.setNotificationHandler('notifications/resources/list_changed', async () => { + const { resources } = await client.listResources(); + console.log('Resources changed:', resources.length); +}); +``` + +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. + +## Handling server‑initiated requests + +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). + +### Declaring capabilities + +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: + +```ts source="../examples/client/src/clientGuide.examples.ts#capabilities_declaration" +const client = new Client( + { name: 'my-client', version: '1.0.0' }, + { + capabilities: { + sampling: {}, + elicitation: { form: {} } + } + } +); +``` + +### 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: + +```ts source="../examples/client/src/clientGuide.examples.ts#sampling_handler" +client.setRequestHandler('sampling/createMessage', async request => { + const lastMessage = request.params.messages.at(-1); + console.log('Sampling request:', lastMessage); + + // In production, send messages to your LLM here + return { + model: 'my-model', + role: 'assistant' as const, + content: { + type: 'text' as const, + text: 'Response from the model' + } + }; +}); +``` + +### 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' }`: + +```ts source="../examples/client/src/clientGuide.examples.ts#elicitation_handler" +client.setRequestHandler('elicitation/create', async request => { + console.log('Server asks:', request.params.message); + + if (request.params.mode === 'form') { + // Present the schema-driven form to the user + console.log('Schema:', request.params.requestedSchema); + return { action: 'accept', content: { confirm: true } }; + } + + return { action: 'decline' }; +}); +``` + +> [!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). + +## Advanced patterns + +### 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: + +```ts source="../examples/client/src/clientGuide.examples.ts#middleware_basic" +const authMiddleware = createMiddleware(async (next, input, init) => { + const headers = new Headers(init?.headers); + headers.set('X-Custom-Header', 'my-value'); + return next(input, { ...init, headers }); +}); + +const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'), { + fetch: applyMiddlewares(authMiddleware)(fetch) +}); +``` + +### 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: + +```ts source="../examples/client/src/clientGuide.examples.ts#resumptionToken_basic" +let lastToken: string | undefined; + +const result = await client.request( + { + method: 'tools/call', + params: { name: 'long-running-task', arguments: {} } + }, + CallToolResultSchema, + { + resumptionToken: lastToken, + onresumptiontoken: (token: string) => { + lastToken = token; + // Persist token to survive restarts + } + } +); +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). + +## More client features + +The sections above cover the essentials. The table below links to additional capabilities. + +| Feature | Description | Reference | +|---------|-------------|-----------| +| 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) | +| 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/examples/client/src/clientGuide.examples.ts b/examples/client/src/clientGuide.examples.ts new file mode 100644 index 000000000..8cefcf224 --- /dev/null +++ b/examples/client/src/clientGuide.examples.ts @@ -0,0 +1,340 @@ +/** + * Type-checked examples for docs/client.md. + * + * Regions are synced into markdown code fences via `pnpm sync:snippets`. + * Each function wraps a single region. The function name matches the region name. + * + * @module + */ + +import { + applyMiddlewares, + CallToolResultSchema, + Client, + ClientCredentialsProvider, + createMiddleware, + PrivateKeyJwtProvider, + SSEClientTransport, + StdioClientTransport, + StreamableHTTPClientTransport +} from '@modelcontextprotocol/client'; + +// --------------------------------------------------------------------------- +// Connecting to a server +// --------------------------------------------------------------------------- + +/** Example: Streamable HTTP transport. */ +async function connect_streamableHttp() { + //#region connect_streamableHttp + const client = new Client({ name: 'my-client', version: '1.0.0' }); + + const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp')); + + await client.connect(transport); + //#endregion connect_streamableHttp +} + +/** Example: stdio transport for local process-spawned servers. */ +async function connect_stdio() { + //#region connect_stdio + const client = new Client({ name: 'my-client', version: '1.0.0' }); + + const transport = new StdioClientTransport({ + command: 'node', + args: ['server.js'] + }); + + await client.connect(transport); + //#endregion connect_stdio +} + +/** Example: Try Streamable HTTP, fall back to legacy SSE. */ +async function connect_sseFallback(url: string) { + //#region connect_sseFallback + const baseUrl = new URL(url); + + try { + // Try modern Streamable HTTP transport first + const client = new Client({ name: 'my-client', version: '1.0.0' }); + const transport = new StreamableHTTPClientTransport(baseUrl); + await client.connect(transport); + return { client, transport }; + } catch { + // Fall back to legacy SSE transport + const client = new Client({ name: 'my-client', version: '1.0.0' }); + const transport = new SSEClientTransport(baseUrl); + await client.connect(transport); + return { client, transport }; + } + //#endregion connect_sseFallback +} + +// --------------------------------------------------------------------------- +// Authentication +// --------------------------------------------------------------------------- + +/** Example: Client credentials auth for service-to-service communication. */ +async function auth_clientCredentials() { + //#region auth_clientCredentials + const authProvider = new ClientCredentialsProvider({ + clientId: 'my-service', + clientSecret: 'my-secret' + }); + + const client = new Client({ name: 'my-client', version: '1.0.0' }); + + const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'), { authProvider }); + + await client.connect(transport); + //#endregion auth_clientCredentials +} + +/** Example: Private key JWT auth. */ +async function auth_privateKeyJwt(pemEncodedKey: string) { + //#region auth_privateKeyJwt + const authProvider = new PrivateKeyJwtProvider({ + clientId: 'my-service', + privateKey: pemEncodedKey, + algorithm: 'RS256' + }); + + const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'), { authProvider }); + //#endregion auth_privateKeyJwt + return transport; +} + +// --------------------------------------------------------------------------- +// Using server features +// --------------------------------------------------------------------------- + +/** Example: List and call tools. */ +async function callTool_basic(client: Client) { + //#region callTool_basic + const { tools } = await client.listTools(); + console.log( + 'Available tools:', + tools.map(t => t.name) + ); + + const result = await client.callTool({ + name: 'calculate-bmi', + arguments: { weightKg: 70, heightM: 1.75 } + }); + console.log(result.content); + //#endregion callTool_basic +} + +/** Example: List and read resources. */ +async function readResource_basic(client: Client) { + //#region readResource_basic + const { resources } = await client.listResources(); + console.log( + 'Available resources:', + resources.map(r => r.name) + ); + + const { contents } = await client.readResource({ uri: 'config://app' }); + for (const item of contents) { + console.log(item); + } + //#endregion readResource_basic +} + +/** Example: List and get prompts. */ +async function getPrompt_basic(client: Client) { + //#region getPrompt_basic + const { prompts } = await client.listPrompts(); + console.log( + 'Available prompts:', + prompts.map(p => p.name) + ); + + const { messages } = await client.getPrompt({ + name: 'review-code', + arguments: { code: 'console.log("hello")' } + }); + console.log(messages); + //#endregion getPrompt_basic +} + +/** Example: Request argument completions. */ +async function complete_basic(client: Client) { + //#region complete_basic + const { completion } = await client.complete({ + ref: { + type: 'ref/prompt', + name: 'review-code' + }, + argument: { + name: 'language', + value: 'type' + } + }); + console.log(completion.values); // e.g. ['typescript'] + //#endregion complete_basic +} + +// --------------------------------------------------------------------------- +// Notifications +// --------------------------------------------------------------------------- + +/** 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) + client.setNotificationHandler('notifications/message', notification => { + const { level, data } = notification.params; + console.log(`[${level}]`, data); + }); + + // Server's resource list changed — re-fetch the list + client.setNotificationHandler('notifications/resources/list_changed', async () => { + const { resources } = await client.listResources(); + console.log('Resources changed:', resources.length); + }); + //#endregion notificationHandler_basic +} + +/** Example: Automatic list-change tracking via the listChanged option. */ +async function listChanged_basic() { + //#region listChanged_basic + const client = new Client( + { name: 'my-client', version: '1.0.0' }, + { + listChanged: { + tools: { + onChanged: (error, tools) => { + if (error) { + console.error('Failed to refresh tools:', error); + return; + } + console.log('Tools updated:', tools); + } + }, + prompts: { + onChanged: (error, prompts) => console.log('Prompts updated:', prompts) + } + } + } + ); + //#endregion listChanged_basic + return client; +} + +// --------------------------------------------------------------------------- +// Handling server-initiated requests +// --------------------------------------------------------------------------- + +/** Example: Declare client capabilities for sampling and elicitation. */ +function capabilities_declaration() { + //#region capabilities_declaration + const client = new Client( + { name: 'my-client', version: '1.0.0' }, + { + capabilities: { + sampling: {}, + elicitation: { form: {} } + } + } + ); + //#endregion capabilities_declaration + return client; +} + +/** Example: Handle a sampling request from the server. */ +function sampling_handler(client: Client) { + //#region sampling_handler + client.setRequestHandler('sampling/createMessage', async request => { + const lastMessage = request.params.messages.at(-1); + console.log('Sampling request:', lastMessage); + + // In production, send messages to your LLM here + return { + model: 'my-model', + role: 'assistant' as const, + content: { + type: 'text' as const, + text: 'Response from the model' + } + }; + }); + //#endregion sampling_handler +} + +/** Example: Handle an elicitation request from the server. */ +function elicitation_handler(client: Client) { + //#region elicitation_handler + client.setRequestHandler('elicitation/create', async request => { + console.log('Server asks:', request.params.message); + + if (request.params.mode === 'form') { + // Present the schema-driven form to the user + console.log('Schema:', request.params.requestedSchema); + return { action: 'accept', content: { confirm: true } }; + } + + return { action: 'decline' }; + }); + //#endregion elicitation_handler +} + +// --------------------------------------------------------------------------- +// Advanced patterns +// --------------------------------------------------------------------------- + +/** Example: Client middleware that adds a custom header. */ +async function middleware_basic() { + //#region middleware_basic + const authMiddleware = createMiddleware(async (next, input, init) => { + const headers = new Headers(init?.headers); + headers.set('X-Custom-Header', 'my-value'); + return next(input, { ...init, headers }); + }); + + const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'), { + fetch: applyMiddlewares(authMiddleware)(fetch) + }); + //#endregion middleware_basic + return transport; +} + +/** Example: Track resumption tokens for SSE reconnection. */ +async function resumptionToken_basic(client: Client) { + //#region resumptionToken_basic + let lastToken: string | undefined; + + const result = await client.request( + { + method: 'tools/call', + params: { name: 'long-running-task', arguments: {} } + }, + CallToolResultSchema, + { + resumptionToken: lastToken, + onresumptiontoken: (token: string) => { + lastToken = token; + // Persist token to survive restarts + } + } + ); + console.log(result); + //#endregion resumptionToken_basic +} + +// Suppress unused-function warnings (functions exist solely for type-checking) +void connect_streamableHttp; +void connect_stdio; +void connect_sseFallback; +void auth_clientCredentials; +void auth_privateKeyJwt; +void callTool_basic; +void readResource_basic; +void getPrompt_basic; +void complete_basic; +void notificationHandler_basic; +void listChanged_basic; +void capabilities_declaration; +void sampling_handler; +void elicitation_handler; +void middleware_basic; +void resumptionToken_basic; From 3a80499c8f4c4b15d5a8e31143aa346dde2f95f4 Mon Sep 17 00:00:00 2001 From: Jonathan Hefner Date: Wed, 11 Feb 2026 13:23:48 -0600 Subject: [PATCH 2/2] Add MCP specification links to `docs/client.md` Add a top-level scope-setting link and per-section spec links for Tools, Resources, Prompts, Sampling, Elicitation, and Authorization. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/client.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/client.md b/docs/client.md index caa932409..ae49d759d 100644 --- a/docs/client.md +++ b/docs/client.md @@ -4,6 +4,8 @@ title: Client ## 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/). + The SDK provides a {@linkcode @modelcontextprotocol/client!client/client.Client | Client} class from `@modelcontextprotocol/client` that connects to MCP servers over different transports: - **Streamable HTTP** – for remote HTTP servers. @@ -115,6 +117,8 @@ For user‑facing applications, implement the `OAuthClientProvider` interface to > [!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. ## Using server features @@ -141,6 +145,9 @@ 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. + ### Resources 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: @@ -158,6 +165,9 @@ for (const item of contents) { } ``` +> [!NOTE] +> See [Resources](https://modelcontextprotocol.io/specification/latest/server/resources) in the MCP specification for the full protocol details. + ### Prompts 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: @@ -176,6 +186,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 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: @@ -283,6 +296,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' }`: @@ -303,6 +319,8 @@ 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 protocol details, see [Elicitation](https://modelcontextprotocol.io/specification/latest/client/elicitation) in the MCP specification. ## Advanced patterns