@modelcontextprotocol/sdk 1.19.1 → 1.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,28 +1,31 @@
1
1
  # MCP TypeScript SDK ![NPM Version](https://img.shields.io/npm/v/%40modelcontextprotocol%2Fsdk) ![MIT licensed](https://img.shields.io/npm/l/%40modelcontextprotocol%2Fsdk)
2
2
 
3
- ## Table of Contents
3
+ <details>
4
+ <summary>Table of Contents</summary>
4
5
 
5
6
  - [Overview](#overview)
6
7
  - [Installation](#installation)
7
- - [Quickstart](#quick-start)
8
- - [What is MCP?](#what-is-mcp)
8
+ - [Quick Start](#quick-start)
9
9
  - [Core Concepts](#core-concepts)
10
10
  - [Server](#server)
11
- - [Resources](#resources)
12
11
  - [Tools](#tools)
12
+ - [Resources](#resources)
13
13
  - [Prompts](#prompts)
14
14
  - [Completions](#completions)
15
+ - [Display Names and Metadata](#display-names-and-metadata)
15
16
  - [Sampling](#sampling)
16
17
  - [Running Your Server](#running-your-server)
17
- - [stdio](#stdio)
18
18
  - [Streamable HTTP](#streamable-http)
19
+ - [stdio](#stdio)
19
20
  - [Testing and Debugging](#testing-and-debugging)
20
21
  - [Examples](#examples)
21
22
  - [Echo Server](#echo-server)
22
23
  - [SQLite Explorer](#sqlite-explorer)
23
24
  - [Advanced Usage](#advanced-usage)
24
25
  - [Dynamic Servers](#dynamic-servers)
26
+ - [Improving Network Efficiency with Notification Debouncing](#improving-network-efficiency-with-notification-debouncing)
25
27
  - [Low-Level Server](#low-level-server)
28
+ - [Eliciting User Input](#eliciting-user-input)
26
29
  - [Writing MCP Clients](#writing-mcp-clients)
27
30
  - [Proxy Authorization Requests Upstream](#proxy-authorization-requests-upstream)
28
31
  - [Backwards Compatibility](#backwards-compatibility)
@@ -30,14 +33,16 @@
30
33
  - [Contributing](#contributing)
31
34
  - [License](#license)
32
35
 
36
+ </details>
37
+
33
38
  ## Overview
34
39
 
35
- The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. This TypeScript SDK implements the full MCP specification, making it easy to:
40
+ The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. This TypeScript SDK implements
41
+ [the full MCP specification](https://modelcontextprotocol.io/specification/latest), making it easy to:
36
42
 
37
- - Build MCP clients that can connect to any MCP server
38
43
  - Create MCP servers that expose resources, prompts and tools
44
+ - Build MCP clients that can connect to any MCP server
39
45
  - Use standard transports like stdio and Streamable HTTP
40
- - Handle all MCP protocol messages and lifecycle events
41
46
 
42
47
  ## Installation
43
48
 
@@ -45,15 +50,14 @@ The Model Context Protocol allows applications to provide context for LLMs in a
45
50
  npm install @modelcontextprotocol/sdk
46
51
  ```
47
52
 
48
- > ⚠️ MCP requires Node.js v18.x or higher to work fine.
49
-
50
53
  ## Quick Start
51
54
 
52
- Let's create a simple MCP server that exposes a calculator tool and some data:
55
+ Let's create a simple MCP server that exposes a calculator tool and some data. Save the following as `server.ts`:
53
56
 
54
57
  ```typescript
55
58
  import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
56
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
59
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
60
+ import express from 'express';
57
61
  import { z } from 'zod';
58
62
 
59
63
  // Create an MCP server
@@ -68,11 +72,16 @@ server.registerTool(
68
72
  {
69
73
  title: 'Addition Tool',
70
74
  description: 'Add two numbers',
71
- inputSchema: { a: z.number(), b: z.number() }
75
+ inputSchema: { a: z.number(), b: z.number() },
76
+ outputSchema: { result: z.number() }
72
77
  },
73
- async ({ a, b }) => ({
74
- content: [{ type: 'text', text: String(a + b) }]
75
- })
78
+ async ({ a, b }) => {
79
+ const output = { result: a + b };
80
+ return {
81
+ content: [{ type: 'text', text: JSON.stringify(output) }],
82
+ structuredContent: output
83
+ };
84
+ }
76
85
  );
77
86
 
78
87
  // Add a dynamic greeting resource
@@ -93,19 +102,44 @@ server.registerResource(
93
102
  })
94
103
  );
95
104
 
96
- // Start receiving messages on stdin and sending messages on stdout
97
- const transport = new StdioServerTransport();
98
- await server.connect(transport);
105
+ // Set up Express and HTTP transport
106
+ const app = express();
107
+ app.use(express.json());
108
+
109
+ app.post('/mcp', async (req, res) => {
110
+ // Create a new transport for each request to prevent request ID collisions
111
+ const transport = new StreamableHTTPServerTransport({
112
+ sessionIdGenerator: undefined,
113
+ enableJsonResponse: true
114
+ });
115
+
116
+ res.on('close', () => {
117
+ transport.close();
118
+ });
119
+
120
+ await server.connect(transport);
121
+ await transport.handleRequest(req, res, req.body);
122
+ });
123
+
124
+ const port = parseInt(process.env.PORT || '3000');
125
+ app.listen(port, () => {
126
+ console.log(`Demo MCP Server running on http://localhost:${port}/mcp`);
127
+ }).on('error', error => {
128
+ console.error('Server error:', error);
129
+ process.exit(1);
130
+ });
99
131
  ```
100
132
 
101
- ## What is MCP?
133
+ Install the deps with `npm install @modelcontextprotocol/sdk express zod@3`, and run with `npx -y tsx server.ts`.
102
134
 
103
- The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. Think of it like a web API, but specifically designed for LLM interactions. MCP servers can:
135
+ You can connect to it using any MCP client that supports streamable http, such as:
104
136
 
105
- - Expose data through **Resources** (think of these sort of like GET endpoints; they are used to load information into the LLM's context)
106
- - Provide functionality through **Tools** (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect)
107
- - Define interaction patterns through **Prompts** (reusable templates for LLM interactions)
108
- - And more!
137
+ - [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector): `npx @modelcontextprotocol/inspector` and connect to the streamable HTTP URL `http://localhost:3000/mcp`
138
+ - [Claude Code](https://docs.claude.com/en/docs/claude-code/mcp): `claude mcp add --transport http my-server http://localhost:3000/mcp`
139
+ - [VS Code](https://code.visualstudio.com/docs/copilot/customization/mcp-servers): `code --add-mcp "{\"name\":\"my-server\",\"type\":\"http\",\"url\":\"http://localhost:3000/mcp\"}"`
140
+ - [Cursor](https://cursor.com/docs/context/mcp): Click [this deeplink](cursor://anysphere.cursor-deeplink/mcp/install?name=my-server&config=eyJ1cmwiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAvbWNwIn0%3D)
141
+
142
+ Then try asking your agent to add two numbers using its new tool!
109
143
 
110
144
  ## Core Concepts
111
145
 
@@ -120,9 +154,112 @@ const server = new McpServer({
120
154
  });
121
155
  ```
122
156
 
157
+ ### Tools
158
+
159
+ [Tools](https://modelcontextprotocol.io/specification/latest/server/tools) let LLMs take actions through your server. Tools can perform computation, fetch data and have side effects. Tools should be designed to be model-controlled - i.e. AI models will decide which tools to call,
160
+ and the arguments.
161
+
162
+ ```typescript
163
+ // Simple tool with parameters
164
+ server.registerTool(
165
+ 'calculate-bmi',
166
+ {
167
+ title: 'BMI Calculator',
168
+ description: 'Calculate Body Mass Index',
169
+ inputSchema: {
170
+ weightKg: z.number(),
171
+ heightM: z.number()
172
+ },
173
+ outputSchema: { bmi: z.number() }
174
+ },
175
+ async ({ weightKg, heightM }) => {
176
+ const output = { bmi: weightKg / (heightM * heightM) };
177
+ return {
178
+ content: [
179
+ {
180
+ type: 'text',
181
+ text: JSON.stringify(output)
182
+ }
183
+ ],
184
+ structuredContent: output
185
+ };
186
+ }
187
+ );
188
+
189
+ // Async tool with external API call
190
+ server.registerTool(
191
+ 'fetch-weather',
192
+ {
193
+ title: 'Weather Fetcher',
194
+ description: 'Get weather data for a city',
195
+ inputSchema: { city: z.string() },
196
+ outputSchema: { temperature: z.number(), conditions: z.string() }
197
+ },
198
+ async ({ city }) => {
199
+ const response = await fetch(`https://api.weather.com/${city}`);
200
+ const data = await response.json();
201
+ const output = { temperature: data.temp, conditions: data.conditions };
202
+ return {
203
+ content: [{ type: 'text', text: JSON.stringify(output) }],
204
+ structuredContent: output
205
+ };
206
+ }
207
+ );
208
+
209
+ // Tool that returns ResourceLinks
210
+ server.registerTool(
211
+ 'list-files',
212
+ {
213
+ title: 'List Files',
214
+ description: 'List project files',
215
+ inputSchema: { pattern: z.string() },
216
+ outputSchema: {
217
+ count: z.number(),
218
+ files: z.array(z.object({ name: z.string(), uri: z.string() }))
219
+ }
220
+ },
221
+ async ({ pattern }) => {
222
+ const output = {
223
+ count: 2,
224
+ files: [
225
+ { name: 'README.md', uri: 'file:///project/README.md' },
226
+ { name: 'index.ts', uri: 'file:///project/src/index.ts' }
227
+ ]
228
+ };
229
+ return {
230
+ content: [
231
+ { type: 'text', text: JSON.stringify(output) },
232
+ // ResourceLinks let tools return references without file content
233
+ {
234
+ type: 'resource_link',
235
+ uri: 'file:///project/README.md',
236
+ name: 'README.md',
237
+ mimeType: 'text/markdown',
238
+ description: 'A README file'
239
+ },
240
+ {
241
+ type: 'resource_link',
242
+ uri: 'file:///project/src/index.ts',
243
+ name: 'index.ts',
244
+ mimeType: 'text/typescript',
245
+ description: 'An index file'
246
+ }
247
+ ],
248
+ structuredContent: output
249
+ };
250
+ }
251
+ );
252
+ ```
253
+
254
+ #### ResourceLinks
255
+
256
+ Tools can return `ResourceLink` objects to reference resources without embedding their full content. This can be helpful for performance when dealing with large files or many resources - clients can then selectively read only the resources they need using the provided URIs.
257
+
123
258
  ### Resources
124
259
 
125
- Resources are how you expose data to LLMs. They're similar to GET endpoints in a REST API - they provide data but shouldn't perform significant computation or have side effects:
260
+ [Resources](https://modelcontextprotocol.io/specification/latest/server/resources) can also expose data to LLMs, but unlike tools shouldn't perform significant computation or have side effects.
261
+
262
+ Resources are designed to be used in an application-driven way, meaning MCP client applications can decide how to expose them. For example, a client could expose a resource picker to the human, or could expose them to the model directly.
126
263
 
127
264
  ```typescript
128
265
  // Static resource
@@ -192,87 +329,9 @@ server.registerResource(
192
329
  );
193
330
  ```
194
331
 
195
- ### Tools
196
-
197
- Tools let LLMs take actions through your server. Unlike resources, tools are expected to perform computation and have side effects:
198
-
199
- ```typescript
200
- // Simple tool with parameters
201
- server.registerTool(
202
- 'calculate-bmi',
203
- {
204
- title: 'BMI Calculator',
205
- description: 'Calculate Body Mass Index',
206
- inputSchema: {
207
- weightKg: z.number(),
208
- heightM: z.number()
209
- }
210
- },
211
- async ({ weightKg, heightM }) => ({
212
- content: [
213
- {
214
- type: 'text',
215
- text: String(weightKg / (heightM * heightM))
216
- }
217
- ]
218
- })
219
- );
220
-
221
- // Async tool with external API call
222
- server.registerTool(
223
- 'fetch-weather',
224
- {
225
- title: 'Weather Fetcher',
226
- description: 'Get weather data for a city',
227
- inputSchema: { city: z.string() }
228
- },
229
- async ({ city }) => {
230
- const response = await fetch(`https://api.weather.com/${city}`);
231
- const data = await response.text();
232
- return {
233
- content: [{ type: 'text', text: data }]
234
- };
235
- }
236
- );
237
-
238
- // Tool that returns ResourceLinks
239
- server.registerTool(
240
- 'list-files',
241
- {
242
- title: 'List Files',
243
- description: 'List project files',
244
- inputSchema: { pattern: z.string() }
245
- },
246
- async ({ pattern }) => ({
247
- content: [
248
- { type: 'text', text: `Found files matching "${pattern}":` },
249
- // ResourceLinks let tools return references without file content
250
- {
251
- type: 'resource_link',
252
- uri: 'file:///project/README.md',
253
- name: 'README.md',
254
- mimeType: 'text/markdown',
255
- description: 'A README file'
256
- },
257
- {
258
- type: 'resource_link',
259
- uri: 'file:///project/src/index.ts',
260
- name: 'index.ts',
261
- mimeType: 'text/typescript',
262
- description: 'An index file'
263
- }
264
- ]
265
- })
266
- );
267
- ```
268
-
269
- #### ResourceLinks
270
-
271
- Tools can return `ResourceLink` objects to reference resources without embedding their full content. This is essential for performance when dealing with large files or many resources - clients can then selectively read only the resources they need using the provided URIs.
272
-
273
332
  ### Prompts
274
333
 
275
- Prompts are reusable templates that help LLMs interact with your server effectively:
334
+ [Prompts](https://modelcontextprotocol.io/specification/latest/server/prompts) are reusable templates that help humans prompt models to interact with your server. They're designed to be user-driven, and might appear as slash commands in a chat interface.
276
335
 
277
336
  ```typescript
278
337
  import { completable } from '@modelcontextprotocol/sdk/server/completable.js';
@@ -364,7 +423,7 @@ const result = await client.complete({
364
423
 
365
424
  ### Display Names and Metadata
366
425
 
367
- All resources, tools, and prompts support an optional `title` field for better UI presentation. The `title` is used as a display name, while `name` remains the unique identifier.
426
+ All resources, tools, and prompts support an optional `title` field for better UI presentation. The `title` is used as a display name (e.g. 'Create a new issue'), while `name` remains the unique identifier (e.g. `create_issue`).
368
427
 
369
428
  **Note:** The `register*` methods (`registerTool`, `registerPrompt`, `registerResource`) are the recommended approach for new code. The older methods (`tool`, `prompt`, `resource`) remain available for backwards compatibility.
370
429
 
@@ -416,7 +475,8 @@ MCP servers can request LLM completions from connected clients that support samp
416
475
 
417
476
  ```typescript
418
477
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
419
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
478
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
479
+ import express from 'express';
420
480
  import { z } from 'zod';
421
481
 
422
482
  const mcpServer = new McpServer({
@@ -428,10 +488,12 @@ const mcpServer = new McpServer({
428
488
  mcpServer.registerTool(
429
489
  'summarize',
430
490
  {
491
+ title: 'Text Summarizer',
431
492
  description: 'Summarize any text using an LLM',
432
493
  inputSchema: {
433
494
  text: z.string().describe('Text to summarize')
434
- }
495
+ },
496
+ outputSchema: { summary: z.string() }
435
497
  },
436
498
  async ({ text }) => {
437
499
  // Call the LLM through MCP sampling
@@ -448,24 +510,36 @@ mcpServer.registerTool(
448
510
  maxTokens: 500
449
511
  });
450
512
 
513
+ const summary = response.content.type === 'text' ? response.content.text : 'Unable to generate summary';
514
+ const output = { summary };
451
515
  return {
452
- content: [
453
- {
454
- type: 'text',
455
- text: response.content.type === 'text' ? response.content.text : 'Unable to generate summary'
456
- }
457
- ]
516
+ content: [{ type: 'text', text: JSON.stringify(output) }],
517
+ structuredContent: output
458
518
  };
459
519
  }
460
520
  );
461
521
 
462
- async function main() {
463
- const transport = new StdioServerTransport();
522
+ const app = express();
523
+ app.use(express.json());
524
+
525
+ app.post('/mcp', async (req, res) => {
526
+ const transport = new StreamableHTTPServerTransport({
527
+ sessionIdGenerator: undefined,
528
+ enableJsonResponse: true
529
+ });
530
+
531
+ res.on('close', () => {
532
+ transport.close();
533
+ });
534
+
464
535
  await mcpServer.connect(transport);
465
- console.error('MCP server is running...');
466
- }
536
+ await transport.handleRequest(req, res, req.body);
537
+ });
467
538
 
468
- main().catch(error => {
539
+ const port = parseInt(process.env.PORT || '3000');
540
+ app.listen(port, () => {
541
+ console.log(`MCP Server running on http://localhost:${port}/mcp`);
542
+ }).on('error', error => {
469
543
  console.error('Server error:', error);
470
544
  process.exit(1);
471
545
  });
@@ -475,32 +549,92 @@ main().catch(error => {
475
549
 
476
550
  MCP servers in TypeScript need to be connected to a transport to communicate with clients. How you start the server depends on the choice of transport:
477
551
 
478
- ### stdio
552
+ ### Streamable HTTP
553
+
554
+ For remote servers, use the Streamable HTTP transport.
555
+
556
+ #### Without Session Management (Recommended)
479
557
 
480
- For command-line tools and direct integrations:
558
+ For most use cases where session management isn't needed:
481
559
 
482
560
  ```typescript
483
561
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
484
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
562
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
563
+ import express from 'express';
564
+ import { z } from 'zod';
485
565
 
566
+ const app = express();
567
+ app.use(express.json());
568
+
569
+ // Create the MCP server once (can be reused across requests)
486
570
  const server = new McpServer({
487
571
  name: 'example-server',
488
572
  version: '1.0.0'
489
573
  });
490
574
 
491
- // ... set up server resources, tools, and prompts ...
575
+ // Set up your tools, resources, and prompts
576
+ server.registerTool(
577
+ 'echo',
578
+ {
579
+ title: 'Echo Tool',
580
+ description: 'Echoes back the provided message',
581
+ inputSchema: { message: z.string() },
582
+ outputSchema: { echo: z.string() }
583
+ },
584
+ async ({ message }) => {
585
+ const output = { echo: `Tool echo: ${message}` };
586
+ return {
587
+ content: [{ type: 'text', text: JSON.stringify(output) }],
588
+ structuredContent: output
589
+ };
590
+ }
591
+ );
492
592
 
493
- const transport = new StdioServerTransport();
494
- await server.connect(transport);
495
- ```
593
+ app.post('/mcp', async (req, res) => {
594
+ // In stateless mode, create a new transport for each request to prevent
595
+ // request ID collisions. Different clients may use the same JSON-RPC request IDs,
596
+ // which would cause responses to be routed to the wrong HTTP connections if
597
+ // the transport state is shared.
496
598
 
497
- ### Streamable HTTP
599
+ try {
600
+ const transport = new StreamableHTTPServerTransport({
601
+ sessionIdGenerator: undefined,
602
+ enableJsonResponse: true
603
+ });
498
604
 
499
- For remote servers, set up a Streamable HTTP transport that handles both client requests and server-to-client notifications.
605
+ res.on('close', () => {
606
+ transport.close();
607
+ });
608
+
609
+ await server.connect(transport);
610
+ await transport.handleRequest(req, res, req.body);
611
+ } catch (error) {
612
+ console.error('Error handling MCP request:', error);
613
+ if (!res.headersSent) {
614
+ res.status(500).json({
615
+ jsonrpc: '2.0',
616
+ error: {
617
+ code: -32603,
618
+ message: 'Internal server error'
619
+ },
620
+ id: null
621
+ });
622
+ }
623
+ }
624
+ });
625
+
626
+ const port = parseInt(process.env.PORT || '3000');
627
+ app.listen(port, () => {
628
+ console.log(`MCP Server running on http://localhost:${port}/mcp`);
629
+ }).on('error', error => {
630
+ console.error('Server error:', error);
631
+ process.exit(1);
632
+ });
633
+ ```
500
634
 
501
635
  #### With Session Management
502
636
 
503
- In some cases, servers need to be stateful. This is achieved by [session management](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#session-management).
637
+ In some cases, servers need stateful sessions. This can be achieved by [session management](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#session-management) in the MCP protocol.
504
638
 
505
639
  ```typescript
506
640
  import express from 'express';
@@ -591,8 +725,6 @@ app.delete('/mcp', handleSessionRequest);
591
725
  app.listen(3000);
592
726
  ```
593
727
 
594
- > [!TIP] When using this in a remote environment, make sure to allow the header parameter `mcp-session-id` in CORS. Otherwise, it may result in a `Bad Request: No valid session ID provided` error. Read the following section for examples.
595
-
596
728
  #### CORS Configuration for Browser-Based Clients
597
729
 
598
730
  If you'd like your server to be accessible by browser-based MCP clients, you'll need to configure CORS headers. The `Mcp-Session-Id` header must be exposed for browser clients to access it:
@@ -617,100 +749,6 @@ This configuration is necessary because:
617
749
  - Browsers restrict access to response headers unless explicitly exposed via CORS
618
750
  - Without this configuration, browser-based clients won't be able to read the session ID from initialization responses
619
751
 
620
- #### Without Session Management (Stateless)
621
-
622
- For simpler use cases where session management isn't needed:
623
-
624
- ```typescript
625
- const app = express();
626
- app.use(express.json());
627
-
628
- app.post('/mcp', async (req: Request, res: Response) => {
629
- // In stateless mode, create a new instance of transport and server for each request
630
- // to ensure complete isolation. A single instance would cause request ID collisions
631
- // when multiple clients connect concurrently.
632
-
633
- try {
634
- const server = getServer();
635
- const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({
636
- sessionIdGenerator: undefined
637
- });
638
- res.on('close', () => {
639
- console.log('Request closed');
640
- transport.close();
641
- server.close();
642
- });
643
- await server.connect(transport);
644
- await transport.handleRequest(req, res, req.body);
645
- } catch (error) {
646
- console.error('Error handling MCP request:', error);
647
- if (!res.headersSent) {
648
- res.status(500).json({
649
- jsonrpc: '2.0',
650
- error: {
651
- code: -32603,
652
- message: 'Internal server error'
653
- },
654
- id: null
655
- });
656
- }
657
- }
658
- });
659
-
660
- // SSE notifications not supported in stateless mode
661
- app.get('/mcp', async (req: Request, res: Response) => {
662
- console.log('Received GET MCP request');
663
- res.writeHead(405).end(
664
- JSON.stringify({
665
- jsonrpc: '2.0',
666
- error: {
667
- code: -32000,
668
- message: 'Method not allowed.'
669
- },
670
- id: null
671
- })
672
- );
673
- });
674
-
675
- // Session termination not needed in stateless mode
676
- app.delete('/mcp', async (req: Request, res: Response) => {
677
- console.log('Received DELETE MCP request');
678
- res.writeHead(405).end(
679
- JSON.stringify({
680
- jsonrpc: '2.0',
681
- error: {
682
- code: -32000,
683
- message: 'Method not allowed.'
684
- },
685
- id: null
686
- })
687
- );
688
- });
689
-
690
- // Start the server
691
- const PORT = 3000;
692
- setupServer()
693
- .then(() => {
694
- app.listen(PORT, error => {
695
- if (error) {
696
- console.error('Failed to start server:', error);
697
- process.exit(1);
698
- }
699
- console.log(`MCP Stateless Streamable HTTP Server listening on port ${PORT}`);
700
- });
701
- })
702
- .catch(error => {
703
- console.error('Failed to set up the server:', error);
704
- process.exit(1);
705
- });
706
- ```
707
-
708
- This stateless approach is useful for:
709
-
710
- - Simple API wrappers
711
- - RESTful scenarios where each request is independent
712
- - Horizontally scaled deployments without shared session state
713
-
714
752
  #### DNS Rebinding Protection
715
753
 
716
754
  The Streamable HTTP transport includes DNS rebinding protection to prevent security vulnerabilities. By default, this protection is **disabled** for backwards compatibility.
@@ -727,6 +765,25 @@ const transport = new StreamableHTTPServerTransport({
727
765
  });
728
766
  ```
729
767
 
768
+ ### stdio
769
+
770
+ For local integrations spawned by another process, you can use the stdio transport:
771
+
772
+ ```typescript
773
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
774
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
775
+
776
+ const server = new McpServer({
777
+ name: 'example-server',
778
+ version: '1.0.0'
779
+ });
780
+
781
+ // ... set up server resources, tools, and prompts ...
782
+
783
+ const transport = new StdioServerTransport();
784
+ await server.connect(transport);
785
+ ```
786
+
730
787
  ### Testing and Debugging
731
788
 
732
789
  To test your server, you can use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector). See its README for more information.
@@ -746,6 +803,23 @@ const server = new McpServer({
746
803
  version: '1.0.0'
747
804
  });
748
805
 
806
+ server.registerTool(
807
+ 'echo',
808
+ {
809
+ title: 'Echo Tool',
810
+ description: 'Echoes back the provided message',
811
+ inputSchema: { message: z.string() },
812
+ outputSchema: { echo: z.string() }
813
+ },
814
+ async ({ message }) => {
815
+ const output = { echo: `Tool echo: ${message}` };
816
+ return {
817
+ content: [{ type: 'text', text: JSON.stringify(output) }],
818
+ structuredContent: output
819
+ };
820
+ }
821
+ );
822
+
749
823
  server.registerResource(
750
824
  'echo',
751
825
  new ResourceTemplate('echo://{message}', { list: undefined }),
@@ -763,18 +837,6 @@ server.registerResource(
763
837
  })
764
838
  );
765
839
 
766
- server.registerTool(
767
- 'echo',
768
- {
769
- title: 'Echo Tool',
770
- description: 'Echoes back the provided message',
771
- inputSchema: { message: z.string() }
772
- },
773
- async ({ message }) => ({
774
- content: [{ type: 'text', text: `Tool echo: ${message}` }]
775
- })
776
- );
777
-
778
840
  server.registerPrompt(
779
841
  'echo',
780
842
  {
@@ -851,19 +913,25 @@ server.registerTool(
851
913
  {
852
914
  title: 'SQL Query',
853
915
  description: 'Execute SQL queries on the database',
854
- inputSchema: { sql: z.string() }
916
+ inputSchema: { sql: z.string() },
917
+ outputSchema: {
918
+ rows: z.array(z.record(z.any())),
919
+ rowCount: z.number()
920
+ }
855
921
  },
856
922
  async ({ sql }) => {
857
923
  const db = getDb();
858
924
  try {
859
925
  const results = await db.all(sql);
926
+ const output = { rows: results, rowCount: results.length };
860
927
  return {
861
928
  content: [
862
929
  {
863
930
  type: 'text',
864
- text: JSON.stringify(results, null, 2)
931
+ text: JSON.stringify(output, null, 2)
865
932
  }
866
- ]
933
+ ],
934
+ structuredContent: output
867
935
  };
868
936
  } catch (err: unknown) {
869
937
  const error = err as Error;
@@ -889,8 +957,10 @@ server.registerTool(
889
957
 
890
958
  If you want to offer an initial set of tools/prompts/resources, but later add additional ones based on user action or external state change, you can add/update/remove them _after_ the Server is connected. This will automatically emit the corresponding `listChanged` notifications:
891
959
 
892
- ```ts
960
+ ```typescript
893
961
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
962
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
963
+ import express from 'express';
894
964
  import { z } from 'zod';
895
965
 
896
966
  const server = new McpServer({
@@ -898,23 +968,64 @@ const server = new McpServer({
898
968
  version: '1.0.0'
899
969
  });
900
970
 
901
- const listMessageTool = server.tool('listMessages', { channel: z.string() }, async ({ channel }) => ({
902
- content: [{ type: 'text', text: await listMessages(channel) }]
903
- }));
971
+ const listMessageTool = server.registerTool(
972
+ 'listMessages',
973
+ {
974
+ title: 'List Messages',
975
+ description: 'List messages in a channel',
976
+ inputSchema: { channel: z.string() },
977
+ outputSchema: { messages: z.array(z.string()) }
978
+ },
979
+ async ({ channel }) => {
980
+ const messages = await listMessages(channel);
981
+ const output = { messages };
982
+ return {
983
+ content: [{ type: 'text', text: JSON.stringify(output) }],
984
+ structuredContent: output
985
+ };
986
+ }
987
+ );
904
988
 
905
- const putMessageTool = server.tool('putMessage', { channel: z.string(), message: z.string() }, async ({ channel, message }) => ({
906
- content: [{ type: 'text', text: await putMessage(channel, message) }]
907
- }));
989
+ const putMessageTool = server.registerTool(
990
+ 'putMessage',
991
+ {
992
+ title: 'Put Message',
993
+ description: 'Send a message to a channel',
994
+ inputSchema: { channel: z.string(), message: z.string() },
995
+ outputSchema: { success: z.boolean() }
996
+ },
997
+ async ({ channel, message }) => {
998
+ await putMessage(channel, message);
999
+ const output = { success: true };
1000
+ return {
1001
+ content: [{ type: 'text', text: JSON.stringify(output) }],
1002
+ structuredContent: output
1003
+ };
1004
+ }
1005
+ );
908
1006
  // Until we upgrade auth, `putMessage` is disabled (won't show up in listTools)
909
1007
  putMessageTool.disable();
910
1008
 
911
- const upgradeAuthTool = server.tool(
1009
+ const upgradeAuthTool = server.registerTool(
912
1010
  'upgradeAuth',
913
- { permission: z.enum(['write', 'admin']) },
1011
+ {
1012
+ title: 'Upgrade Authorization',
1013
+ description: 'Upgrade user authorization level',
1014
+ inputSchema: { permission: z.enum(['write', 'admin']) },
1015
+ outputSchema: {
1016
+ success: z.boolean(),
1017
+ newPermission: z.string()
1018
+ }
1019
+ },
914
1020
  // Any mutations here will automatically emit `listChanged` notifications
915
1021
  async ({ permission }) => {
916
1022
  const { ok, err, previous } = await upgradeAuthAndStoreToken(permission);
917
- if (!ok) return { content: [{ type: 'text', text: `Error: ${err}` }] };
1023
+ if (!ok) {
1024
+ return {
1025
+ content: [{ type: 'text', text: `Error: ${err}` }],
1026
+ isError: true
1027
+ };
1028
+ }
918
1029
 
919
1030
  // If we previously had read-only access, 'putMessage' is now available
920
1031
  if (previous === 'read') {
@@ -931,12 +1042,37 @@ const upgradeAuthTool = server.tool(
931
1042
  // If we're now an admin, we no longer have anywhere to upgrade to, so fully remove that tool
932
1043
  upgradeAuthTool.remove();
933
1044
  }
1045
+
1046
+ const output = { success: true, newPermission: permission };
1047
+ return {
1048
+ content: [{ type: 'text', text: JSON.stringify(output) }],
1049
+ structuredContent: output
1050
+ };
934
1051
  }
935
1052
  );
936
1053
 
937
- // Connect as normal
938
- const transport = new StdioServerTransport();
939
- await server.connect(transport);
1054
+ // Connect with HTTP transport
1055
+ const app = express();
1056
+ app.use(express.json());
1057
+
1058
+ app.post('/mcp', async (req, res) => {
1059
+ const transport = new StreamableHTTPServerTransport({
1060
+ sessionIdGenerator: undefined,
1061
+ enableJsonResponse: true
1062
+ });
1063
+
1064
+ res.on('close', () => {
1065
+ transport.close();
1066
+ });
1067
+
1068
+ await server.connect(transport);
1069
+ await transport.handleRequest(req, res, req.body);
1070
+ });
1071
+
1072
+ const port = parseInt(process.env.PORT || '3000');
1073
+ app.listen(port, () => {
1074
+ console.log(`MCP Server running on http://localhost:${port}/mcp`);
1075
+ });
940
1076
  ```
941
1077
 
942
1078
  ### Improving Network Efficiency with Notification Debouncing
@@ -1043,12 +1179,27 @@ MCP servers can request additional information from users through the elicitatio
1043
1179
 
1044
1180
  ```typescript
1045
1181
  // Server-side: Restaurant booking tool that asks for alternatives
1046
- server.tool(
1182
+ server.registerTool(
1047
1183
  'book-restaurant',
1048
1184
  {
1049
- restaurant: z.string(),
1050
- date: z.string(),
1051
- partySize: z.number()
1185
+ title: 'Book Restaurant',
1186
+ description: 'Book a table at a restaurant',
1187
+ inputSchema: {
1188
+ restaurant: z.string(),
1189
+ date: z.string(),
1190
+ partySize: z.number()
1191
+ },
1192
+ outputSchema: {
1193
+ success: z.boolean(),
1194
+ booking: z
1195
+ .object({
1196
+ restaurant: z.string(),
1197
+ date: z.string(),
1198
+ partySize: z.number()
1199
+ })
1200
+ .optional(),
1201
+ alternatives: z.array(z.string()).optional()
1202
+ }
1052
1203
  },
1053
1204
  async ({ restaurant, date, partySize }) => {
1054
1205
  // Check availability
@@ -1080,35 +1231,44 @@ server.tool(
1080
1231
 
1081
1232
  if (result.action === 'accept' && result.content?.checkAlternatives) {
1082
1233
  const alternatives = await findAlternatives(restaurant, date, partySize, result.content.flexibleDates as string);
1234
+ const output = { success: false, alternatives };
1083
1235
  return {
1084
1236
  content: [
1085
1237
  {
1086
1238
  type: 'text',
1087
- text: `Found these alternatives: ${alternatives.join(', ')}`
1239
+ text: JSON.stringify(output)
1088
1240
  }
1089
- ]
1241
+ ],
1242
+ structuredContent: output
1090
1243
  };
1091
1244
  }
1092
1245
 
1246
+ const output = { success: false };
1093
1247
  return {
1094
1248
  content: [
1095
1249
  {
1096
1250
  type: 'text',
1097
- text: 'No booking made. Original date not available.'
1251
+ text: JSON.stringify(output)
1098
1252
  }
1099
- ]
1253
+ ],
1254
+ structuredContent: output
1100
1255
  };
1101
1256
  }
1102
1257
 
1103
1258
  // Book the table
1104
1259
  await makeBooking(restaurant, date, partySize);
1260
+ const output = {
1261
+ success: true,
1262
+ booking: { restaurant, date, partySize }
1263
+ };
1105
1264
  return {
1106
1265
  content: [
1107
1266
  {
1108
1267
  type: 'text',
1109
- text: `Booked table for ${partySize} at ${restaurant} on ${date}`
1268
+ text: JSON.stringify(output)
1110
1269
  }
1111
- ]
1270
+ ],
1271
+ structuredContent: output
1112
1272
  };
1113
1273
  }
1114
1274
  );