Skip to content

Commit d43894e

Browse files
nielskaspersclaude
andcommitted
docs: clarify automatic vs manual sendToolListChanged usage
Address review feedback: registerTool, tool.remove(), tool.enable(), and tool.disable() already fire the notification automatically. Reserve sendToolListChanged() for external state changes the SDK cannot observe (e.g. feature flags). Also fix spec link to use /latest/ pattern. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6dae484 commit d43894e

2 files changed

Lines changed: 56 additions & 41 deletions

File tree

docs/server.md

Lines changed: 50 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,14 @@ await server.connect(transport);
6363

6464
## Server instructions
6565

66-
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.
66+
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
67+
the system prompt. Instructions should not duplicate information already in tool descriptions.
6768

6869
```ts source="../examples/server/src/serverGuide.examples.ts#instructions_basic"
6970
const server = new McpServer(
7071
{ name: 'db-server', version: '1.0.0' },
7172
{
72-
instructions:
73-
'Always call list_tables before running queries. Use validate_schema before migrate_schema for safe migrations. Results are limited to 1000 rows.'
73+
instructions: 'Always call list_tables before running queries. Use validate_schema before migrate_schema for safe migrations. Results are limited to 1000 rows.'
7474
}
7575
);
7676
```
@@ -103,12 +103,13 @@ server.registerTool(
103103
);
104104
```
105105

106-
> [!NOTE]
107-
> When defining a named type for `structuredContent`, use a `type` alias rather than an `interface`. Named interfaces lack implicit index signatures in TypeScript, so they aren't assignable to `{ [key: string]: unknown }`:
106+
> [!NOTE] When defining a named type for `structuredContent`, use a `type` alias rather than an `interface`. Named interfaces lack implicit index signatures in TypeScript, so they aren't assignable to `{ [key: string]: unknown }`:
108107
>
109108
> ```ts
110-
> type BmiResult = { bmi: number }; // assignable
111-
> interface BmiResult { bmi: number } // type error
109+
> type BmiResult = { bmi: number }; // assignable
110+
> interface BmiResult {
111+
> bmi: number;
112+
> } // type error
112113
> ```
113114
>
114115
> Alternatively, spread the value: `structuredContent: { ...result }`.
@@ -203,26 +204,33 @@ If a handler throws instead of returning `isError`, the SDK catches the exceptio
203204

204205
### List changed notifications
205206

206-
When the set of available tools changes at runtime, the server should notify connected clients so they can refresh their tool list (see [List Changed Notification](https://spec.modelcontextprotocol.io/specification/2025-06-18/server/tools/#list-changed-notification) in the MCP specification).
207+
When the set of available tools changes at runtime, the server should notify connected clients so they can refresh their tool list (see [List Changed Notification](https://modelcontextprotocol.io/specification/latest/server/tools/#list-changed-notification) in the MCP
208+
specification).
207209

208-
{@linkcode @modelcontextprotocol/server!server/mcp.McpServer#registerTool | registerTool} sends this notification automatically when called after the client is already connected. To notify manually — for example, after removing a tool or toggling tool availability — call {@linkcode @modelcontextprotocol/server!server/mcp.McpServer#sendToolListChanged | sendToolListChanged}:
210+
The SDK sends this notification automatically whenever you call {@linkcode @modelcontextprotocol/server!server/mcp.McpServer#registerTool | registerTool}, `tool.remove()`, `tool.enable()`, or `tool.disable()` while a client is connected — no extra code needed.
211+
212+
For changes the SDK cannot observe — for example, an external feature flag that alters which tools are available — call {@linkcode @modelcontextprotocol/server!server/mcp.McpServer#sendToolListChanged | sendToolListChanged} manually:
209213

210214
```ts source="../examples/server/src/serverGuide.examples.ts#sendToolListChanged_basic"
211-
// Automatic: registering a tool at runtime sends the notification
212-
server.registerTool('new-tool', { description: 'A dynamically added tool' }, async () => ({
215+
// Automatic: registerTool, tool.remove(), tool.enable(), and tool.disable()
216+
// all send the notification — no manual call required.
217+
const tool = server.registerTool('new-tool', { description: 'A dynamically added tool' }, async () => ({
213218
content: [{ type: 'text', text: 'done' }]
214219
}));
220+
tool.remove(); // notification sent automatically
215221

216-
// Manual: notify clients explicitly (e.g. after removing a tool)
222+
// Manual: notify clients when tool availability changes through external
223+
// means the SDK cannot observe (e.g. a feature flag or config reload).
217224
server.sendToolListChanged();
218225
```
219226

220-
> [!NOTE]
221-
> On the client side, use the {@linkcode @modelcontextprotocol/client!client/client.ClientOptions | listChanged} option to automatically re-fetch tool lists when this notification arrives — see [Automatic list-change tracking](./client.md#automatic-list-change-tracking) in the client guide.
227+
> [!NOTE] On the client side, use the {@linkcode @modelcontextprotocol/client!client/client.ClientOptions | listChanged} option to automatically re-fetch tool lists when this notification arrives — see [Automatic list-change tracking](./client.md#automatic-list-change-tracking)
228+
> in the client guide.
222229
223230
## Resources
224231

225-
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.
232+
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
233+
[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.
226234

227235
A static resource at a fixed URI:
228236

@@ -272,7 +280,8 @@ server.registerResource(
272280

273281
## Prompts
274282

275-
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.
283+
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
284+
a [tool](#tools) when the LLM should decide when to call it.
276285

277286
```ts source="../examples/server/src/serverGuide.examples.ts#registerPrompt_basic"
278287
server.registerPrompt(
@@ -309,9 +318,7 @@ server.registerPrompt(
309318
title: 'Code Review',
310319
description: 'Review code for best practices',
311320
argsSchema: z.object({
312-
language: completable(z.string().describe('Programming language'), value =>
313-
['typescript', 'javascript', 'python', 'rust', 'go'].filter(lang => lang.startsWith(value))
314-
)
321+
language: completable(z.string().describe('Programming language'), value => ['typescript', 'javascript', 'python', 'rust', 'go'].filter(lang => lang.startsWith(value)))
315322
})
316323
},
317324
({ language }) => ({
@@ -398,11 +405,12 @@ server.registerTool(
398405

399406
## Server-initiated requests
400407

401-
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).
408+
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).
402409

403410
### Sampling
404411

405-
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.
412+
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
413+
when a tool needs the model to generate or transform text mid-execution.
406414

407415
Call `ctx.mcpReq.requestSampling(params)` (from {@linkcode @modelcontextprotocol/server!index.ServerContext | ServerContext}) inside a tool handler:
408416

@@ -447,8 +455,7 @@ Elicitation lets a tool handler request direct input from the user — form fiel
447455
- **Form** (`mode: 'form'`) — collects non-sensitive data via a schema-driven form.
448456
- **URL** (`mode: 'url'`) — opens a browser URL for sensitive data or secure flows (API keys, payments, OAuth).
449457

450-
> [!IMPORTANT]
451-
> Sensitive information must not be collected via form elicitation; always use URL elicitation or out-of-band flows for secrets.
458+
> [!IMPORTANT] Sensitive information must not be collected via form elicitation; always use URL elicitation or out-of-band flows for secrets.
452459
453460
Call `ctx.mcpReq.elicitInput(params)` (from {@linkcode @modelcontextprotocol/server!index.ServerContext | ServerContext}) inside a tool handler:
454461

@@ -492,11 +499,13 @@ server.registerTool(
492499
);
493500
```
494501

495-
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).
502+
For runnable examples, see [`elicitationFormExample.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/elicitationFormExample.ts) (form) and
503+
[`elicitationUrlExample.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/elicitationUrlExample.ts) (URL).
496504

497505
### Roots
498506

499-
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):
507+
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
508+
@modelcontextprotocol/server!server/server.Server#listRoots | server.server.listRoots()} (requires the client to declare the `roots` capability):
500509

501510
```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_roots"
502511
server.registerTool(
@@ -515,10 +524,10 @@ server.registerTool(
515524

516525
## Tasks (experimental)
517526

518-
> [!WARNING]
519-
> The tasks API is experimental and may change without notice.
527+
> [!WARNING] The tasks API is experimental and may change without notice.
520528
521-
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:
529+
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
530+
be polled or resumed later. To use tasks:
522531

523532
- Provide a {@linkcode @modelcontextprotocol/server!index.TaskStore | TaskStore} implementation that persists task metadata and results (see {@linkcode @modelcontextprotocol/server!index.InMemoryTaskStore | InMemoryTaskStore} for reference).
524533
- Enable the `tasks` capability when constructing the server.
@@ -563,9 +572,11 @@ For a complete multi-session server with shutdown handling, see [`simpleStreamab
563572

564573
### DNS rebinding protection
565574

566-
Under normal circumstances, cross-origin browser restrictions limit what a malicious website can do to your localhost server. [DNS rebinding attacks](https://en.wikipedia.org/wiki/DNS_rebinding) get around those restrictions entirely by making the requests appear as same-origin, since the attacking domain resolves to localhost. Validating the host header on the server side protects against this scenario. **All localhost MCP servers should use DNS rebinding protection.**
575+
Under normal circumstances, cross-origin browser restrictions limit what a malicious website can do to your localhost server. [DNS rebinding attacks](https://en.wikipedia.org/wiki/DNS_rebinding) get around those restrictions entirely by making the requests appear as same-origin,
576+
since the attacking domain resolves to localhost. Validating the host header on the server side protects against this scenario. **All localhost MCP servers should use DNS rebinding protection.**
567577

568-
The recommended approach is to use {@linkcode @modelcontextprotocol/express!express.createMcpExpressApp | createMcpExpressApp()} (from `@modelcontextprotocol/express`) or {@linkcode @modelcontextprotocol/hono!hono.createMcpHonoApp | createMcpHonoApp()} (from `@modelcontextprotocol/hono`), which enable Host header validation by default:
578+
The recommended approach is to use {@linkcode @modelcontextprotocol/express!express.createMcpExpressApp | createMcpExpressApp()} (from `@modelcontextprotocol/express`) or {@linkcode @modelcontextprotocol/hono!hono.createMcpHonoApp | createMcpHonoApp()} (from
579+
`@modelcontextprotocol/hono`), which enable Host header validation by default:
569580

570581
```ts source="../examples/server/src/serverGuide.examples.ts#dnsRebinding_basic"
571582
// Default: DNS rebinding protection auto-enabled (host is 127.0.0.1)
@@ -589,7 +600,8 @@ const app = createMcpExpressApp({
589600

590601
`createMcpHonoApp()` from `@modelcontextprotocol/hono` provides the same protection for Hono-based servers and Web Standard runtimes (Cloudflare Workers, Deno, Bun).
591602

592-
If you use `NodeStreamableHTTPServerTransport` directly with your own HTTP framework, you must implement Host header validation yourself. See the [`hostHeaderValidation`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/packages/middleware/express/src/express.ts) middleware source for reference.
603+
If you use `NodeStreamableHTTPServerTransport` directly with your own HTTP framework, you must implement Host header validation yourself. See the [`hostHeaderValidation`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/packages/middleware/express/src/express.ts)
604+
middleware source for reference.
593605

594606
## See also
595607

@@ -601,10 +613,10 @@ If you use `NodeStreamableHTTPServerTransport` directly with your own HTTP frame
601613

602614
### Additional examples
603615

604-
| Feature | Description | Example |
605-
|---------|-------------|---------|
606-
| 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) |
607-
| Session management | Per-session transport routing, initialization, and cleanup | [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts) |
608-
| Resumability | Replay missed SSE events via an event store | [`inMemoryEventStore.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/inMemoryEventStore.ts) |
609-
| CORS | Expose MCP headers for browser clients | [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts) |
610-
| 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) |
616+
| Feature | Description | Example |
617+
| ---------------------- | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
618+
| 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) |
619+
| Session management | Per-session transport routing, initialization, and cleanup | [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts) |
620+
| Resumability | Replay missed SSE events via an event store | [`inMemoryEventStore.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/inMemoryEventStore.ts) |
621+
| CORS | Expose MCP headers for browser clients | [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts) |
622+
| 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) |

examples/server/src/serverGuide.examples.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,15 @@ function registerTool_annotations(server: McpServer) {
150150
/** Example: Notifying clients when the tool list changes at runtime. */
151151
function sendToolListChanged_basic(server: McpServer) {
152152
//#region sendToolListChanged_basic
153-
// Automatic: registering a tool at runtime sends the notification
154-
server.registerTool('new-tool', { description: 'A dynamically added tool' }, async () => ({
153+
// Automatic: registerTool, tool.remove(), tool.enable(), and tool.disable()
154+
// all send the notification — no manual call required.
155+
const tool = server.registerTool('new-tool', { description: 'A dynamically added tool' }, async () => ({
155156
content: [{ type: 'text', text: 'done' }]
156157
}));
158+
tool.remove(); // notification sent automatically
157159

158-
// Manual: notify clients explicitly (e.g. after removing a tool)
160+
// Manual: notify clients when tool availability changes through external
161+
// means the SDK cannot observe (e.g. a feature flag or config reload).
159162
server.sendToolListChanged();
160163
//#endregion sendToolListChanged_basic
161164
}

0 commit comments

Comments
 (0)