--- title: Server Guide --- # Server overview This guide covers SDK usage for building MCP servers in TypeScript. For protocol-level details and message formats, see the [MCP specification](https://modelcontextprotocol.io/specification/latest/). 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)`. 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. ## 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 stateless server using `createMcpExpressApp()`, which includes [DNS rebinding protection](#dns-rebinding-protection) by default: ```ts const app = createMcpExpressApp(); app.post('/mcp', async (req, res) => { const server = new McpServer({ name: 'my-server', version: '1.0.0' }); const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined // stateless }); await server.connect(transport); await transport.handleRequest(req, res, req.body); }); app.listen(3000, '127.0.0.1'); ``` For stateful servers with session management, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts). > [!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. > [!WARNING] > If your server listens on localhost, use [`createMcpExpressApp()`](#dns-rebinding-protection) or [`createMcpHonoApp()`](#dns-rebinding-protection) instead of using `NodeStreamableHTTPServerTransport` directly — they include [DNS rebinding protection](#dns-rebinding-protection) by default. #### Stateless vs stateful sessions Streamable HTTP can run: - **Stateless** – no session tracking, ideal for simple API‑style servers. - **Stateful** – sessions have IDs, and you can enable resumability and advanced features. The key difference is the `sessionIdGenerator` option. Pass `undefined` for stateless mode: ```ts source="../examples/server/src/serverGuide.examples.ts#streamableHttp_stateless" const server = new McpServer({ name: 'my-server', version: '1.0.0' }); const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined }); 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); ``` > [!NOTE] > For a full runnable example, see [`jsonResponseStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/jsonResponseStreamableHttp.ts). ### stdio For local, process‑spawned integrations (Claude Desktop, CLI tools), 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' }); const transport = new StdioServerTransport(); await server.connect(transport); ``` ## Tools, resources, and prompts ### Tools Tools let MCP clients ask your server to take actions. They are usually the main way that LLMs call into your application. A typical registration with {@linkcode @modelcontextprotocol/server!server/mcp.McpServer#registerTool | registerTool}: ```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_basic" server.registerTool( 'calculate-bmi', { title: 'BMI Calculator', description: 'Calculate Body Mass Index', inputSchema: z.object({ weightKg: z.number(), heightM: z.number() }), outputSchema: z.object({ bmi: z.number() }) }, async ({ weightKg, heightM }) => { const output = { bmi: weightKg / (heightM * heightM) }; return { content: [{ type: 'text', text: JSON.stringify(output) }], structuredContent: output }; } ); ``` > [!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. > [!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 }`: > > ```ts > type BmiResult = { bmi: number }; // assignable > interface BmiResult { bmi: number } // type error > ``` > > Alternatively, spread the value: `structuredContent: { ...result }`. #### `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: ```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_resourceLink" server.registerTool( 'list-files', { title: 'List Files', description: 'Returns files as resource links without embedding content' }, async (): Promise => { const links: ResourceLink[] = [ { type: 'resource_link', uri: 'file:///projects/readme.md', name: 'README', mimeType: 'text/markdown' }, { type: 'resource_link', uri: 'file:///projects/config.json', name: 'Config', mimeType: 'application/json' } ]; return { content: links }; } ); ``` > [!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 — for example, whether a tool is read‑only, destructive, or idempotent. Annotations help clients present tools appropriately without changing their execution semantics. > [!NOTE] > For tool annotations in a full server, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts). ### 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. A static resource at a fixed URI: ```ts source="../examples/server/src/serverGuide.examples.ts#registerResource_static" server.registerResource( 'config', 'config://app', { title: 'Application Config', description: 'Application configuration data', mimeType: 'text/plain' }, async uri => ({ contents: [{ uri: uri.href, text: 'App configuration here' }] }) ); ``` Dynamic resources use {@linkcode @modelcontextprotocol/server!server/mcp.ResourceTemplate | ResourceTemplate} and can support completions on path parameters: ```ts source="../examples/server/src/serverGuide.examples.ts#registerResource_template" server.registerResource( 'user-profile', new ResourceTemplate('user://{userId}/profile', { list: async () => ({ resources: [ { uri: 'user://123/profile', name: 'Alice' }, { uri: 'user://456/profile', name: 'Bob' } ] }) }), { title: 'User Profile', description: 'User profile data', mimeType: 'application/json' }, async (uri, { userId }) => ({ contents: [ { uri: uri.href, text: JSON.stringify({ userId, name: 'Example User' }) } ] }) ); ``` > [!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 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: ```ts source="../examples/server/src/serverGuide.examples.ts#registerPrompt_basic" server.registerPrompt( 'review-code', { title: 'Code Review', description: 'Review code for best practices and potential issues', argsSchema: z.object({ code: z.string() }) }, ({ code }) => ({ messages: [ { role: 'user' as const, content: { type: 'text' as const, text: `Please review this code:\n\n${code}` } } ] }) ); ``` > [!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 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: ```ts source="../examples/server/src/serverGuide.examples.ts#registerPrompt_completion" server.registerPrompt( 'review-code', { title: 'Code Review', description: 'Review code for best practices', argsSchema: z.object({ language: completable(z.string().describe('Programming language'), value => ['typescript', 'javascript', 'python', 'rust', 'go'].filter(lang => lang.startsWith(value)) ) }) }, ({ language }) => ({ messages: [ { role: 'user' as const, content: { type: 'text' as const, text: `Review this ${language} code for best practices.` } } ] }) ); ``` ### Logging Unlike tools, resources, and prompts, logging is not a registered primitive — it is a handler-level API available inside any callback. Use `ctx.mcpReq.log(level, data)` (from {@linkcode @modelcontextprotocol/server!index.ServerContext | ServerContext}) to send structured log messages to the client. The server must declare the `logging` capability: ```ts source="../examples/server/src/serverGuide.examples.ts#logging_capability" const server = new McpServer({ name: 'my-server', version: '1.0.0' }, { capabilities: { logging: {} } }); ``` Then log from any handler callback: ```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_logging" server.registerTool( 'fetch-data', { description: 'Fetch data from an API', inputSchema: z.object({ url: z.string() }) }, async ({ url }, ctx): Promise => { await ctx.mcpReq.log('info', `Fetching ${url}`); const res = await fetch(url); await ctx.mcpReq.log('debug', `Response status: ${res.status}`); const text = await res.text(); return { content: [{ type: 'text', text }] }; } ); ``` > [!NOTE] > For logging in a full server, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts) and [`jsonResponseStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/jsonResponseStreamableHttp.ts). > > For protocol details, see [Logging](https://modelcontextprotocol.io/specification/latest/server/utilities/logging) in the MCP specification. ## Instructions Pass an `instructions` string in the server options to describe how to use the server and its features. This can be used by clients to improve the LLM's understanding of available tools, resources, and prompts. It can be thought of like a "hint" to the model — for example, a client MAY add it to the system prompt. See [Instructions](https://modelcontextprotocol.io/specification/latest/basic/lifecycle#instructions) in the MCP specification. ```ts source="../examples/server/src/serverGuide.examples.ts#instructions_basic" const server = new McpServer( { name: 'multi-tool-server', version: '1.0.0' }, { instructions: `This server provides data-pipeline tools. Always call "validate-schema" before calling "transform-data" to avoid runtime errors.` } ); ``` > [!TIP] > Use instructions for cross-tool relationships, workflow ordering, and constraints that individual tool descriptions cannot express on their own. ## Server‑initiated requests MCP is bidirectional — servers can also send requests *to* the client during tool execution, as long as the client declares matching capabilities. ### Sampling Use `ctx.mcpReq.requestSampling(params)` (from {@linkcode @modelcontextprotocol/server!index.ServerContext | ServerContext}) inside a tool handler to request an LLM completion from the connected client: ```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_sampling" server.registerTool( 'summarize', { description: 'Summarize text using the client LLM', inputSchema: z.object({ text: z.string() }) }, async ({ text }, ctx): Promise => { const response = await ctx.mcpReq.requestSampling({ messages: [ { role: 'user', content: { type: 'text', text: `Please summarize:\n\n${text}` } } ], maxTokens: 500 }); return { content: [ { type: 'text', text: `Model (${response.model}): ${JSON.stringify(response.content)}` } ] }; } ); ``` > [!NOTE] > For a full runnable example, see [`toolWithSampleServer.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/toolWithSampleServer.ts). > > For protocol details, see [Sampling](https://modelcontextprotocol.io/specification/latest/client/sampling) in the MCP specification. ### Elicitation Use `ctx.mcpReq.elicitInput(params)` (from {@linkcode @modelcontextprotocol/server!index.ServerContext | ServerContext}) inside a tool handler to request user input. Elicitation supports two modes: - **Form** (`mode: 'form'`) — collects **non‑sensitive** data via a schema‑driven form. - **URL** (`mode: 'url'`) — for sensitive data or secure web‑based flows (API keys, payments, OAuth). The client opens a URL in the browser. > [!IMPORTANT] > Sensitive information **must not** be collected via form elicitation; always use URL elicitation or out‑of‑band flows for secrets. ```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_elicitation" server.registerTool( 'collect-feedback', { description: 'Collect user feedback via a form', inputSchema: z.object({}) }, async (_args, ctx): Promise => { const result = await ctx.mcpReq.elicitInput({ mode: 'form', message: 'Please share your feedback:', requestedSchema: { type: 'object', properties: { rating: { type: 'number', title: 'Rating (1\u20135)', minimum: 1, maximum: 5 }, comment: { type: 'string', title: 'Comment' } }, required: ['rating'] } }); if (result.action === 'accept') { return { content: [ { type: 'text', text: `Thanks! ${JSON.stringify(result.content)}` } ] }; } return { content: [{ type: 'text', text: 'Feedback declined.' }] }; } ); ``` > [!NOTE] > For runnable examples, see [`elicitationFormExample.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/elicitationFormExample.ts) (form mode) and [`elicitationUrlExample.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/elicitationUrlExample.ts) (URL mode). > > For protocol details, see [Elicitation](https://modelcontextprotocol.io/specification/latest/client/elicitation) in the MCP specification. ## Tasks (experimental) Task-based execution enables "call-now, fetch-later" patterns for long-running operations. Instead of returning a result immediately, a tool creates a task that can be polled or resumed later. To use tasks: - Provide a {@linkcode @modelcontextprotocol/server!index.TaskStore | TaskStore} implementation that persists task metadata and results (see {@linkcode @modelcontextprotocol/server!index.InMemoryTaskStore | InMemoryTaskStore} for reference). - Enable the `tasks` capability when constructing the server. - Register tools with {@linkcode @modelcontextprotocol/server!experimental/tasks/mcpServer.ExperimentalMcpServerTasks#registerToolTask | server.experimental.tasks.registerToolTask(...)}. > [!NOTE] > For a full runnable example, see [`simpleTaskInteractive.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleTaskInteractive.ts). > [!WARNING] > The tasks API is experimental and may change without notice. ## Deployment ### DNS rebinding protection 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.** The recommended approach is to use `createMcpExpressApp()` (from `@modelcontextprotocol/express`) or `createMcpHonoApp()` (from `@modelcontextprotocol/hono`), which enable Host header validation by default: ```ts source="../examples/server/src/serverGuide.examples.ts#dnsRebinding_basic" // Default: DNS rebinding protection auto-enabled (host is 127.0.0.1) const app = createMcpExpressApp(); // DNS rebinding protection also auto-enabled for localhost const appLocal = createMcpExpressApp({ host: 'localhost' }); // No automatic protection when binding to all interfaces const appOpen = createMcpExpressApp({ host: '0.0.0.0' }); ``` When binding to `0.0.0.0` / `::`, provide an allow-list of hosts: ```ts source="../examples/server/src/serverGuide.examples.ts#dnsRebinding_allowedHosts" const app = createMcpExpressApp({ host: '0.0.0.0', allowedHosts: ['localhost', '127.0.0.1', 'myhost.local'] }); ``` `createMcpHonoApp()` from `@modelcontextprotocol/hono` provides the same protection for Hono-based servers and Web Standard runtimes (Cloudflare Workers, Deno, Bun). 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. ## More server features The sections above cover the essentials. The table below links to additional capabilities demonstrated in the runnable examples. | Feature | Description | Reference | |---------|-------------|-----------| | 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) |