claude-mem 3.3.7 → 3.3.9

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.
Files changed (96) hide show
  1. package/README.md +183 -46
  2. package/dist/bin/cli.d.ts +2 -0
  3. package/dist/bin/cli.js +179 -0
  4. package/dist/commands/compress.d.ts +2 -0
  5. package/dist/commands/compress.js +27 -0
  6. package/dist/commands/hooks.d.ts +19 -0
  7. package/dist/commands/hooks.js +131 -0
  8. package/dist/commands/install.d.ts +2 -0
  9. package/dist/commands/install.js +836 -0
  10. package/dist/commands/load-context.d.ts +2 -0
  11. package/dist/commands/load-context.js +151 -0
  12. package/dist/commands/logs.d.ts +2 -0
  13. package/dist/commands/logs.js +76 -0
  14. package/dist/commands/migrate-to-jsonl.d.ts +5 -0
  15. package/dist/commands/migrate-to-jsonl.js +99 -0
  16. package/dist/commands/restore.d.ts +1 -0
  17. package/dist/commands/restore.js +20 -0
  18. package/dist/commands/status.d.ts +1 -0
  19. package/dist/commands/status.js +136 -0
  20. package/dist/commands/trash-empty.d.ts +3 -0
  21. package/dist/commands/trash-empty.js +56 -0
  22. package/dist/commands/trash-view.d.ts +1 -0
  23. package/dist/commands/trash-view.js +101 -0
  24. package/dist/commands/trash.d.ts +6 -0
  25. package/dist/commands/trash.js +49 -0
  26. package/dist/commands/uninstall.d.ts +2 -0
  27. package/dist/commands/uninstall.js +107 -0
  28. package/dist/constants.d.ts +271 -0
  29. package/dist/constants.js +199 -0
  30. package/dist/core/compression/TranscriptCompressor.d.ts +79 -0
  31. package/dist/core/compression/TranscriptCompressor.js +585 -0
  32. package/dist/core/orchestration/PromptOrchestrator.d.ts +165 -0
  33. package/dist/core/orchestration/PromptOrchestrator.js +182 -0
  34. package/dist/lib/time-utils.d.ts +5 -0
  35. package/dist/lib/time-utils.js +70 -0
  36. package/dist/prompts/constants.d.ts +126 -0
  37. package/dist/prompts/constants.js +161 -0
  38. package/dist/prompts/index.d.ts +10 -0
  39. package/dist/prompts/index.js +11 -0
  40. package/dist/prompts/templates/analysis/AnalysisTemplates.d.ts +13 -0
  41. package/dist/prompts/templates/analysis/AnalysisTemplates.js +94 -0
  42. package/dist/prompts/templates/context/ContextTemplates.d.ts +119 -0
  43. package/dist/prompts/templates/context/ContextTemplates.js +399 -0
  44. package/dist/prompts/templates/hooks/HookTemplates.d.ts +175 -0
  45. package/dist/prompts/templates/hooks/HookTemplates.js +394 -0
  46. package/dist/prompts/templates/hooks/HookTemplates.test.d.ts +7 -0
  47. package/dist/prompts/templates/hooks/HookTemplates.test.js +127 -0
  48. package/dist/shared/config.d.ts +4 -0
  49. package/dist/shared/config.js +41 -0
  50. package/dist/shared/error-handler.d.ts +22 -0
  51. package/dist/shared/error-handler.js +142 -0
  52. package/dist/shared/logger.d.ts +19 -0
  53. package/dist/shared/logger.js +51 -0
  54. package/dist/shared/paths.d.ts +28 -0
  55. package/dist/shared/paths.js +100 -0
  56. package/dist/shared/settings.d.ts +41 -0
  57. package/dist/shared/settings.js +81 -0
  58. package/dist/shared/types.d.ts +145 -0
  59. package/dist/shared/types.js +78 -0
  60. package/docs/STATUS.md +155 -0
  61. package/docs/chroma-backend-migration.md +161 -0
  62. package/docs/landing-page-outline.md +287 -0
  63. package/docs/multi-platform-builds.md +96 -0
  64. package/docs/plans/cloud-service-plan.md +274 -0
  65. package/docs/plans/fix-response-format-issue.md +61 -0
  66. package/docs/plans/restructure-session-hook-output.md +102 -0
  67. package/docs/plans/session-start-hook-investigation.md +45 -0
  68. package/docs/plans/src-reorganization-plan.md +181 -0
  69. package/docs/plans/terminal-effects-decision.md +22 -0
  70. package/docs/plans/terminal-effects-integration.md +82 -0
  71. package/docs/plans/trash-bin-feature-plan.md +240 -0
  72. package/docs/plans/trash-bin-minimal-plan.md +102 -0
  73. package/docs/reference/bun-single-executable.md +584 -0
  74. package/docs/reference/cc-output-styles.md +99 -0
  75. package/docs/reference/chroma-mcp-project-memory-example.md +80 -0
  76. package/docs/reference/chroma-mcp-team-example.md +92 -0
  77. package/docs/reference/claude-code/cc-hooks.md +787 -0
  78. package/docs/reference/claude-code/cc-status-line.md +202 -0
  79. package/docs/reference/claude-code/hook-configuration.md +173 -0
  80. package/docs/reference/claude-code/hook-responses.md +127 -0
  81. package/docs/reference/claude-code/hooks.md +175 -0
  82. package/docs/reference/claude-code/mcp-configuration.md +133 -0
  83. package/docs/reference/claude-code/session-start-hook.md +82 -0
  84. package/docs/reference/load-context-format-example.md +33 -0
  85. package/docs/reference/mcp-sdk/mcp-typescript-sdk-readme.md +1323 -0
  86. package/docs/reference/mcp-sdk/server-implementation.md +286 -0
  87. package/docs/reference/mcp-sdk/stdio-transport.md +345 -0
  88. package/docs/todos/fix-response-format-tasks.md +43 -0
  89. package/docs/todos/implementation-todos.md +280 -0
  90. package/docs/todos/restructure-hook-output-tasks.md +103 -0
  91. package/docs/todos/session-start-hook-fix.md +26 -0
  92. package/docs/todos/terminal-effects-tasks.md +42 -0
  93. package/docs/todos/trash-bin-implementation-todos.md +348 -0
  94. package/docs/todos/trash-bin-minimal-todos.md +27 -0
  95. package/package.json +56 -6
  96. package/claude-mem +0 -0
@@ -0,0 +1,1323 @@
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
+
3
+ ## Table of Contents
4
+
5
+ - [Overview](#overview)
6
+ - [Installation](#installation)
7
+ - [Quickstart](#quick-start)
8
+ - [What is MCP?](#what-is-mcp)
9
+ - [Core Concepts](#core-concepts)
10
+ - [Server](#server)
11
+ - [Resources](#resources)
12
+ - [Tools](#tools)
13
+ - [Prompts](#prompts)
14
+ - [Completions](#completions)
15
+ - [Sampling](#sampling)
16
+ - [Running Your Server](#running-your-server)
17
+ - [stdio](#stdio)
18
+ - [Streamable HTTP](#streamable-http)
19
+ - [Testing and Debugging](#testing-and-debugging)
20
+ - [Examples](#examples)
21
+ - [Echo Server](#echo-server)
22
+ - [SQLite Explorer](#sqlite-explorer)
23
+ - [Advanced Usage](#advanced-usage)
24
+ - [Dynamic Servers](#dynamic-servers)
25
+ - [Low-Level Server](#low-level-server)
26
+ - [Writing MCP Clients](#writing-mcp-clients)
27
+ - [Proxy Authorization Requests Upstream](#proxy-authorization-requests-upstream)
28
+ - [Backwards Compatibility](#backwards-compatibility)
29
+ - [Documentation](#documentation)
30
+ - [Contributing](#contributing)
31
+ - [License](#license)
32
+
33
+ ## Overview
34
+
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:
36
+
37
+ - Build MCP clients that can connect to any MCP server
38
+ - Create MCP servers that expose resources, prompts and tools
39
+ - Use standard transports like stdio and Streamable HTTP
40
+ - Handle all MCP protocol messages and lifecycle events
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ npm install @modelcontextprotocol/sdk
46
+ ```
47
+
48
+ > ⚠️ MCP requires Node.js v18.x or higher to work fine.
49
+
50
+ ## Quick Start
51
+
52
+ Let's create a simple MCP server that exposes a calculator tool and some data:
53
+
54
+ ```typescript
55
+ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
56
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
57
+ import { z } from "zod";
58
+
59
+ // Create an MCP server
60
+ const server = new McpServer({
61
+ name: "demo-server",
62
+ version: "1.0.0"
63
+ });
64
+
65
+ // Add an addition tool
66
+ server.registerTool("add",
67
+ {
68
+ title: "Addition Tool",
69
+ description: "Add two numbers",
70
+ inputSchema: { a: z.number(), b: z.number() }
71
+ },
72
+ async ({ a, b }) => ({
73
+ content: [{ type: "text", text: String(a + b) }]
74
+ })
75
+ );
76
+
77
+ // Add a dynamic greeting resource
78
+ server.registerResource(
79
+ "greeting",
80
+ new ResourceTemplate("greeting://{name}", { list: undefined }),
81
+ {
82
+ title: "Greeting Resource", // Display name for UI
83
+ description: "Dynamic greeting generator"
84
+ },
85
+ async (uri, { name }) => ({
86
+ contents: [{
87
+ uri: uri.href,
88
+ text: `Hello, ${name}!`
89
+ }]
90
+ })
91
+ );
92
+
93
+ // Start receiving messages on stdin and sending messages on stdout
94
+ const transport = new StdioServerTransport();
95
+ await server.connect(transport);
96
+ ```
97
+
98
+ ## What is MCP?
99
+
100
+ 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:
101
+
102
+ - Expose data through **Resources** (think of these sort of like GET endpoints; they are used to load information into the LLM's context)
103
+ - Provide functionality through **Tools** (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect)
104
+ - Define interaction patterns through **Prompts** (reusable templates for LLM interactions)
105
+ - And more!
106
+
107
+ ## Core Concepts
108
+
109
+ ### Server
110
+
111
+ The McpServer is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing:
112
+
113
+ ```typescript
114
+ const server = new McpServer({
115
+ name: "my-app",
116
+ version: "1.0.0"
117
+ });
118
+ ```
119
+
120
+ ### Resources
121
+
122
+ 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:
123
+
124
+ ```typescript
125
+ // Static resource
126
+ server.registerResource(
127
+ "config",
128
+ "config://app",
129
+ {
130
+ title: "Application Config",
131
+ description: "Application configuration data",
132
+ mimeType: "text/plain"
133
+ },
134
+ async (uri) => ({
135
+ contents: [{
136
+ uri: uri.href,
137
+ text: "App configuration here"
138
+ }]
139
+ })
140
+ );
141
+
142
+ // Dynamic resource with parameters
143
+ server.registerResource(
144
+ "user-profile",
145
+ new ResourceTemplate("users://{userId}/profile", { list: undefined }),
146
+ {
147
+ title: "User Profile",
148
+ description: "User profile information"
149
+ },
150
+ async (uri, { userId }) => ({
151
+ contents: [{
152
+ uri: uri.href,
153
+ text: `Profile data for user ${userId}`
154
+ }]
155
+ })
156
+ );
157
+
158
+ // Resource with context-aware completion
159
+ server.registerResource(
160
+ "repository",
161
+ new ResourceTemplate("github://repos/{owner}/{repo}", {
162
+ list: undefined,
163
+ complete: {
164
+ // Provide intelligent completions based on previously resolved parameters
165
+ repo: (value, context) => {
166
+ if (context?.arguments?.["owner"] === "org1") {
167
+ return ["project1", "project2", "project3"].filter(r => r.startsWith(value));
168
+ }
169
+ return ["default-repo"].filter(r => r.startsWith(value));
170
+ }
171
+ }
172
+ }),
173
+ {
174
+ title: "GitHub Repository",
175
+ description: "Repository information"
176
+ },
177
+ async (uri, { owner, repo }) => ({
178
+ contents: [{
179
+ uri: uri.href,
180
+ text: `Repository: ${owner}/${repo}`
181
+ }]
182
+ })
183
+ );
184
+ ```
185
+
186
+ ### Tools
187
+
188
+ Tools let LLMs take actions through your server. Unlike resources, tools are expected to perform computation and have side effects:
189
+
190
+ ```typescript
191
+ // Simple tool with parameters
192
+ server.registerTool(
193
+ "calculate-bmi",
194
+ {
195
+ title: "BMI Calculator",
196
+ description: "Calculate Body Mass Index",
197
+ inputSchema: {
198
+ weightKg: z.number(),
199
+ heightM: z.number()
200
+ }
201
+ },
202
+ async ({ weightKg, heightM }) => ({
203
+ content: [{
204
+ type: "text",
205
+ text: String(weightKg / (heightM * heightM))
206
+ }]
207
+ })
208
+ );
209
+
210
+ // Async tool with external API call
211
+ server.registerTool(
212
+ "fetch-weather",
213
+ {
214
+ title: "Weather Fetcher",
215
+ description: "Get weather data for a city",
216
+ inputSchema: { city: z.string() }
217
+ },
218
+ async ({ city }) => {
219
+ const response = await fetch(`https://api.weather.com/${city}`);
220
+ const data = await response.text();
221
+ return {
222
+ content: [{ type: "text", text: data }]
223
+ };
224
+ }
225
+ );
226
+
227
+ // Tool that returns ResourceLinks
228
+ server.registerTool(
229
+ "list-files",
230
+ {
231
+ title: "List Files",
232
+ description: "List project files",
233
+ inputSchema: { pattern: z.string() }
234
+ },
235
+ async ({ pattern }) => ({
236
+ content: [
237
+ { type: "text", text: `Found files matching "${pattern}":` },
238
+ // ResourceLinks let tools return references without file content
239
+ {
240
+ type: "resource_link",
241
+ uri: "file:///project/README.md",
242
+ name: "README.md",
243
+ mimeType: "text/markdown",
244
+ description: 'A README file'
245
+ },
246
+ {
247
+ type: "resource_link",
248
+ uri: "file:///project/src/index.ts",
249
+ name: "index.ts",
250
+ mimeType: "text/typescript",
251
+ description: 'An index file'
252
+ }
253
+ ]
254
+ })
255
+ );
256
+ ```
257
+
258
+ #### ResourceLinks
259
+
260
+ 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.
261
+
262
+ ### Prompts
263
+
264
+ Prompts are reusable templates that help LLMs interact with your server effectively:
265
+
266
+ ```typescript
267
+ import { completable } from "@modelcontextprotocol/sdk/server/completable.js";
268
+
269
+ server.registerPrompt(
270
+ "review-code",
271
+ {
272
+ title: "Code Review",
273
+ description: "Review code for best practices and potential issues",
274
+ argsSchema: { code: z.string() }
275
+ },
276
+ ({ code }) => ({
277
+ messages: [{
278
+ role: "user",
279
+ content: {
280
+ type: "text",
281
+ text: `Please review this code:\n\n${code}`
282
+ }
283
+ }]
284
+ })
285
+ );
286
+
287
+ // Prompt with context-aware completion
288
+ server.registerPrompt(
289
+ "team-greeting",
290
+ {
291
+ title: "Team Greeting",
292
+ description: "Generate a greeting for team members",
293
+ argsSchema: {
294
+ department: completable(z.string(), (value) => {
295
+ // Department suggestions
296
+ return ["engineering", "sales", "marketing", "support"].filter(d => d.startsWith(value));
297
+ }),
298
+ name: completable(z.string(), (value, context) => {
299
+ // Name suggestions based on selected department
300
+ const department = context?.arguments?.["department"];
301
+ if (department === "engineering") {
302
+ return ["Alice", "Bob", "Charlie"].filter(n => n.startsWith(value));
303
+ } else if (department === "sales") {
304
+ return ["David", "Eve", "Frank"].filter(n => n.startsWith(value));
305
+ } else if (department === "marketing") {
306
+ return ["Grace", "Henry", "Iris"].filter(n => n.startsWith(value));
307
+ }
308
+ return ["Guest"].filter(n => n.startsWith(value));
309
+ })
310
+ }
311
+ },
312
+ ({ department, name }) => ({
313
+ messages: [{
314
+ role: "assistant",
315
+ content: {
316
+ type: "text",
317
+ text: `Hello ${name}, welcome to the ${department} team!`
318
+ }
319
+ }]
320
+ })
321
+ );
322
+ ```
323
+
324
+ ### Completions
325
+
326
+ MCP supports argument completions to help users fill in prompt arguments and resource template parameters. See the examples above for [resource completions](#resources) and [prompt completions](#prompts).
327
+
328
+ #### Client Usage
329
+
330
+ ```typescript
331
+ // Request completions for any argument
332
+ const result = await client.complete({
333
+ ref: {
334
+ type: "ref/prompt", // or "ref/resource"
335
+ name: "example" // or uri: "template://..."
336
+ },
337
+ argument: {
338
+ name: "argumentName",
339
+ value: "partial" // What the user has typed so far
340
+ },
341
+ context: { // Optional: Include previously resolved arguments
342
+ arguments: {
343
+ previousArg: "value"
344
+ }
345
+ }
346
+ });
347
+
348
+ ```
349
+
350
+ ### Display Names and Metadata
351
+
352
+ 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.
353
+
354
+ **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.
355
+
356
+ #### Title Precedence for Tools
357
+
358
+ For tools specifically, there are two ways to specify a title:
359
+ - `title` field in the tool configuration
360
+ - `annotations.title` field (when using the older `tool()` method with annotations)
361
+
362
+ The precedence order is: `title` → `annotations.title` → `name`
363
+
364
+ ```typescript
365
+ // Using registerTool (recommended)
366
+ server.registerTool("my_tool", {
367
+ title: "My Tool", // This title takes precedence
368
+ annotations: {
369
+ title: "Annotation Title" // This is ignored if title is set
370
+ }
371
+ }, handler);
372
+
373
+ // Using tool with annotations (older API)
374
+ server.tool("my_tool", "description", {
375
+ title: "Annotation Title" // This is used as title
376
+ }, handler);
377
+ ```
378
+
379
+ When building clients, use the provided utility to get the appropriate display name:
380
+
381
+ ```typescript
382
+ import { getDisplayName } from "@modelcontextprotocol/sdk/shared/metadataUtils.js";
383
+
384
+ // Automatically handles the precedence: title → annotations.title → name
385
+ const displayName = getDisplayName(tool);
386
+ ```
387
+
388
+ ### Sampling
389
+
390
+ MCP servers can request LLM completions from connected clients that support sampling.
391
+
392
+ ```typescript
393
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
394
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
395
+ import { z } from "zod";
396
+
397
+ const mcpServer = new McpServer({
398
+ name: "tools-with-sample-server",
399
+ version: "1.0.0",
400
+ });
401
+
402
+ // Tool that uses LLM sampling to summarize any text
403
+ mcpServer.registerTool(
404
+ "summarize",
405
+ {
406
+ description: "Summarize any text using an LLM",
407
+ inputSchema: {
408
+ text: z.string().describe("Text to summarize"),
409
+ },
410
+ },
411
+ async ({ text }) => {
412
+ // Call the LLM through MCP sampling
413
+ const response = await mcpServer.server.createMessage({
414
+ messages: [
415
+ {
416
+ role: "user",
417
+ content: {
418
+ type: "text",
419
+ text: `Please summarize the following text concisely:\n\n${text}`,
420
+ },
421
+ },
422
+ ],
423
+ maxTokens: 500,
424
+ });
425
+
426
+ return {
427
+ content: [
428
+ {
429
+ type: "text",
430
+ text: response.content.type === "text" ? response.content.text : "Unable to generate summary",
431
+ },
432
+ ],
433
+ };
434
+ }
435
+ );
436
+
437
+ async function main() {
438
+ const transport = new StdioServerTransport();
439
+ await mcpServer.connect(transport);
440
+ console.log("MCP server is running...");
441
+ }
442
+
443
+ main().catch((error) => {
444
+ console.error("Server error:", error);
445
+ process.exit(1);
446
+ });
447
+ ```
448
+
449
+
450
+ ## Running Your Server
451
+
452
+ 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:
453
+
454
+ ### stdio
455
+
456
+ For command-line tools and direct integrations:
457
+
458
+ ```typescript
459
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
460
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
461
+
462
+ const server = new McpServer({
463
+ name: "example-server",
464
+ version: "1.0.0"
465
+ });
466
+
467
+ // ... set up server resources, tools, and prompts ...
468
+
469
+ const transport = new StdioServerTransport();
470
+ await server.connect(transport);
471
+ ```
472
+
473
+ ### Streamable HTTP
474
+
475
+ For remote servers, set up a Streamable HTTP transport that handles both client requests and server-to-client notifications.
476
+
477
+ #### With Session Management
478
+
479
+ 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).
480
+
481
+ ```typescript
482
+ import express from "express";
483
+ import { randomUUID } from "node:crypto";
484
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
485
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
486
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js"
487
+
488
+
489
+
490
+ const app = express();
491
+ app.use(express.json());
492
+
493
+ // Map to store transports by session ID
494
+ const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
495
+
496
+ // Handle POST requests for client-to-server communication
497
+ app.post('/mcp', async (req, res) => {
498
+ // Check for existing session ID
499
+ const sessionId = req.headers['mcp-session-id'] as string | undefined;
500
+ let transport: StreamableHTTPServerTransport;
501
+
502
+ if (sessionId && transports[sessionId]) {
503
+ // Reuse existing transport
504
+ transport = transports[sessionId];
505
+ } else if (!sessionId && isInitializeRequest(req.body)) {
506
+ // New initialization request
507
+ transport = new StreamableHTTPServerTransport({
508
+ sessionIdGenerator: () => randomUUID(),
509
+ onsessioninitialized: (sessionId) => {
510
+ // Store the transport by session ID
511
+ transports[sessionId] = transport;
512
+ },
513
+ // DNS rebinding protection is disabled by default for backwards compatibility. If you are running this server
514
+ // locally, make sure to set:
515
+ // enableDnsRebindingProtection: true,
516
+ // allowedHosts: ['127.0.0.1'],
517
+ });
518
+
519
+ // Clean up transport when closed
520
+ transport.onclose = () => {
521
+ if (transport.sessionId) {
522
+ delete transports[transport.sessionId];
523
+ }
524
+ };
525
+ const server = new McpServer({
526
+ name: "example-server",
527
+ version: "1.0.0"
528
+ });
529
+
530
+ // ... set up server resources, tools, and prompts ...
531
+
532
+ // Connect to the MCP server
533
+ await server.connect(transport);
534
+ } else {
535
+ // Invalid request
536
+ res.status(400).json({
537
+ jsonrpc: '2.0',
538
+ error: {
539
+ code: -32000,
540
+ message: 'Bad Request: No valid session ID provided',
541
+ },
542
+ id: null,
543
+ });
544
+ return;
545
+ }
546
+
547
+ // Handle the request
548
+ await transport.handleRequest(req, res, req.body);
549
+ });
550
+
551
+ // Reusable handler for GET and DELETE requests
552
+ const handleSessionRequest = async (req: express.Request, res: express.Response) => {
553
+ const sessionId = req.headers['mcp-session-id'] as string | undefined;
554
+ if (!sessionId || !transports[sessionId]) {
555
+ res.status(400).send('Invalid or missing session ID');
556
+ return;
557
+ }
558
+
559
+ const transport = transports[sessionId];
560
+ await transport.handleRequest(req, res);
561
+ };
562
+
563
+ // Handle GET requests for server-to-client notifications via SSE
564
+ app.get('/mcp', handleSessionRequest);
565
+
566
+ // Handle DELETE requests for session termination
567
+ app.delete('/mcp', handleSessionRequest);
568
+
569
+ app.listen(3000);
570
+ ```
571
+
572
+ > [!TIP]
573
+ > 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.
574
+
575
+
576
+ #### CORS Configuration for Browser-Based Clients
577
+
578
+ 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:
579
+
580
+ ```typescript
581
+ import cors from 'cors';
582
+
583
+ // Add CORS middleware before your MCP routes
584
+ app.use(cors({
585
+ origin: '*', // Configure appropriately for production, for example:
586
+ // origin: ['https://your-remote-domain.com', 'https://your-other-remote-domain.com'],
587
+ exposedHeaders: ['Mcp-Session-Id'],
588
+ allowedHeaders: ['Content-Type', 'mcp-session-id'],
589
+ }));
590
+ ```
591
+
592
+ This configuration is necessary because:
593
+ - The MCP streamable HTTP transport uses the `Mcp-Session-Id` header for session management
594
+ - Browsers restrict access to response headers unless explicitly exposed via CORS
595
+ - Without this configuration, browser-based clients won't be able to read the session ID from initialization responses
596
+
597
+ #### Without Session Management (Stateless)
598
+
599
+ For simpler use cases where session management isn't needed:
600
+
601
+ ```typescript
602
+ const app = express();
603
+ app.use(express.json());
604
+
605
+ app.post('/mcp', async (req: Request, res: Response) => {
606
+ // In stateless mode, create a new instance of transport and server for each request
607
+ // to ensure complete isolation. A single instance would cause request ID collisions
608
+ // when multiple clients connect concurrently.
609
+
610
+ try {
611
+ const server = getServer();
612
+ const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({
613
+ sessionIdGenerator: undefined,
614
+ });
615
+ res.on('close', () => {
616
+ console.log('Request closed');
617
+ transport.close();
618
+ server.close();
619
+ });
620
+ await server.connect(transport);
621
+ await transport.handleRequest(req, res, req.body);
622
+ } catch (error) {
623
+ console.error('Error handling MCP request:', error);
624
+ if (!res.headersSent) {
625
+ res.status(500).json({
626
+ jsonrpc: '2.0',
627
+ error: {
628
+ code: -32603,
629
+ message: 'Internal server error',
630
+ },
631
+ id: null,
632
+ });
633
+ }
634
+ }
635
+ });
636
+
637
+ // SSE notifications not supported in stateless mode
638
+ app.get('/mcp', async (req: Request, res: Response) => {
639
+ console.log('Received GET MCP request');
640
+ res.writeHead(405).end(JSON.stringify({
641
+ jsonrpc: "2.0",
642
+ error: {
643
+ code: -32000,
644
+ message: "Method not allowed."
645
+ },
646
+ id: null
647
+ }));
648
+ });
649
+
650
+ // Session termination not needed in stateless mode
651
+ app.delete('/mcp', async (req: Request, res: Response) => {
652
+ console.log('Received DELETE MCP request');
653
+ res.writeHead(405).end(JSON.stringify({
654
+ jsonrpc: "2.0",
655
+ error: {
656
+ code: -32000,
657
+ message: "Method not allowed."
658
+ },
659
+ id: null
660
+ }));
661
+ });
662
+
663
+
664
+ // Start the server
665
+ const PORT = 3000;
666
+ setupServer().then(() => {
667
+ app.listen(PORT, (error) => {
668
+ if (error) {
669
+ console.error('Failed to start server:', error);
670
+ process.exit(1);
671
+ }
672
+ console.log(`MCP Stateless Streamable HTTP Server listening on port ${PORT}`);
673
+ });
674
+ }).catch(error => {
675
+ console.error('Failed to set up the server:', error);
676
+ process.exit(1);
677
+ });
678
+
679
+ ```
680
+
681
+ This stateless approach is useful for:
682
+
683
+ - Simple API wrappers
684
+ - RESTful scenarios where each request is independent
685
+ - Horizontally scaled deployments without shared session state
686
+
687
+ #### DNS Rebinding Protection
688
+
689
+ The Streamable HTTP transport includes DNS rebinding protection to prevent security vulnerabilities. By default, this protection is **disabled** for backwards compatibility.
690
+
691
+ **Important**: If you are running this server locally, enable DNS rebinding protection:
692
+
693
+ ```typescript
694
+ const transport = new StreamableHTTPServerTransport({
695
+ sessionIdGenerator: () => randomUUID(),
696
+ enableDnsRebindingProtection: true,
697
+
698
+ allowedHosts: ['127.0.0.1', ...],
699
+ allowedOrigins: ['https://yourdomain.com', 'https://www.yourdomain.com']
700
+ });
701
+ ```
702
+
703
+ ### Testing and Debugging
704
+
705
+ To test your server, you can use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector). See its README for more information.
706
+
707
+ ## Examples
708
+
709
+ ### Echo Server
710
+
711
+ A simple server demonstrating resources, tools, and prompts:
712
+
713
+ ```typescript
714
+ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
715
+ import { z } from "zod";
716
+
717
+ const server = new McpServer({
718
+ name: "echo-server",
719
+ version: "1.0.0"
720
+ });
721
+
722
+ server.registerResource(
723
+ "echo",
724
+ new ResourceTemplate("echo://{message}", { list: undefined }),
725
+ {
726
+ title: "Echo Resource",
727
+ description: "Echoes back messages as resources"
728
+ },
729
+ async (uri, { message }) => ({
730
+ contents: [{
731
+ uri: uri.href,
732
+ text: `Resource echo: ${message}`
733
+ }]
734
+ })
735
+ );
736
+
737
+ server.registerTool(
738
+ "echo",
739
+ {
740
+ title: "Echo Tool",
741
+ description: "Echoes back the provided message",
742
+ inputSchema: { message: z.string() }
743
+ },
744
+ async ({ message }) => ({
745
+ content: [{ type: "text", text: `Tool echo: ${message}` }]
746
+ })
747
+ );
748
+
749
+ server.registerPrompt(
750
+ "echo",
751
+ {
752
+ title: "Echo Prompt",
753
+ description: "Creates a prompt to process a message",
754
+ argsSchema: { message: z.string() }
755
+ },
756
+ ({ message }) => ({
757
+ messages: [{
758
+ role: "user",
759
+ content: {
760
+ type: "text",
761
+ text: `Please process this message: ${message}`
762
+ }
763
+ }]
764
+ })
765
+ );
766
+ ```
767
+
768
+ ### SQLite Explorer
769
+
770
+ A more complex example showing database integration:
771
+
772
+ ```typescript
773
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
774
+ import sqlite3 from "sqlite3";
775
+ import { promisify } from "util";
776
+ import { z } from "zod";
777
+
778
+ const server = new McpServer({
779
+ name: "sqlite-explorer",
780
+ version: "1.0.0"
781
+ });
782
+
783
+ // Helper to create DB connection
784
+ const getDb = () => {
785
+ const db = new sqlite3.Database("database.db");
786
+ return {
787
+ all: promisify<string, any[]>(db.all.bind(db)),
788
+ close: promisify(db.close.bind(db))
789
+ };
790
+ };
791
+
792
+ server.registerResource(
793
+ "schema",
794
+ "schema://main",
795
+ {
796
+ title: "Database Schema",
797
+ description: "SQLite database schema",
798
+ mimeType: "text/plain"
799
+ },
800
+ async (uri) => {
801
+ const db = getDb();
802
+ try {
803
+ const tables = await db.all(
804
+ "SELECT sql FROM sqlite_master WHERE type='table'"
805
+ );
806
+ return {
807
+ contents: [{
808
+ uri: uri.href,
809
+ text: tables.map((t: {sql: string}) => t.sql).join("\n")
810
+ }]
811
+ };
812
+ } finally {
813
+ await db.close();
814
+ }
815
+ }
816
+ );
817
+
818
+ server.registerTool(
819
+ "query",
820
+ {
821
+ title: "SQL Query",
822
+ description: "Execute SQL queries on the database",
823
+ inputSchema: { sql: z.string() }
824
+ },
825
+ async ({ sql }) => {
826
+ const db = getDb();
827
+ try {
828
+ const results = await db.all(sql);
829
+ return {
830
+ content: [{
831
+ type: "text",
832
+ text: JSON.stringify(results, null, 2)
833
+ }]
834
+ };
835
+ } catch (err: unknown) {
836
+ const error = err as Error;
837
+ return {
838
+ content: [{
839
+ type: "text",
840
+ text: `Error: ${error.message}`
841
+ }],
842
+ isError: true
843
+ };
844
+ } finally {
845
+ await db.close();
846
+ }
847
+ }
848
+ );
849
+ ```
850
+
851
+ ## Advanced Usage
852
+
853
+ ### Dynamic Servers
854
+
855
+ 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:
856
+
857
+ ```ts
858
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
859
+ import { z } from "zod";
860
+
861
+ const server = new McpServer({
862
+ name: "Dynamic Example",
863
+ version: "1.0.0"
864
+ });
865
+
866
+ const listMessageTool = server.tool(
867
+ "listMessages",
868
+ { channel: z.string() },
869
+ async ({ channel }) => ({
870
+ content: [{ type: "text", text: await listMessages(channel) }]
871
+ })
872
+ );
873
+
874
+ const putMessageTool = server.tool(
875
+ "putMessage",
876
+ { channel: z.string(), message: z.string() },
877
+ async ({ channel, message }) => ({
878
+ content: [{ type: "text", text: await putMessage(channel, message) }]
879
+ })
880
+ );
881
+ // Until we upgrade auth, `putMessage` is disabled (won't show up in listTools)
882
+ putMessageTool.disable()
883
+
884
+ const upgradeAuthTool = server.tool(
885
+ "upgradeAuth",
886
+ { permission: z.enum(["write", "admin"])},
887
+ // Any mutations here will automatically emit `listChanged` notifications
888
+ async ({ permission }) => {
889
+ const { ok, err, previous } = await upgradeAuthAndStoreToken(permission)
890
+ if (!ok) return {content: [{ type: "text", text: `Error: ${err}` }]}
891
+
892
+ // If we previously had read-only access, 'putMessage' is now available
893
+ if (previous === "read") {
894
+ putMessageTool.enable()
895
+ }
896
+
897
+ if (permission === 'write') {
898
+ // If we've just upgraded to 'write' permissions, we can still call 'upgradeAuth'
899
+ // but can only upgrade to 'admin'.
900
+ upgradeAuthTool.update({
901
+ paramsSchema: { permission: z.enum(["admin"]) }, // change validation rules
902
+ })
903
+ } else {
904
+ // If we're now an admin, we no longer have anywhere to upgrade to, so fully remove that tool
905
+ upgradeAuthTool.remove()
906
+ }
907
+ }
908
+ )
909
+
910
+ // Connect as normal
911
+ const transport = new StdioServerTransport();
912
+ await server.connect(transport);
913
+ ```
914
+
915
+ ### Improving Network Efficiency with Notification Debouncing
916
+
917
+ When performing bulk updates that trigger notifications (e.g., enabling or disabling multiple tools in a loop), the SDK can send a large number of messages in a short period. To improve performance and reduce network traffic, you can enable notification debouncing.
918
+
919
+ This feature coalesces multiple, rapid calls for the same notification type into a single message. For example, if you disable five tools in a row, only one `notifications/tools/list_changed` message will be sent instead of five.
920
+
921
+ > [!IMPORTANT]
922
+ > This feature is designed for "simple" notifications that do not carry unique data in their parameters. To prevent silent data loss, debouncing is **automatically bypassed** for any notification that contains a `params` object or a `relatedRequestId`. Such notifications will always be sent immediately.
923
+
924
+ This is an opt-in feature configured during server initialization.
925
+
926
+ ```typescript
927
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
928
+
929
+ const server = new McpServer(
930
+ {
931
+ name: "efficient-server",
932
+ version: "1.0.0"
933
+ },
934
+ {
935
+ // Enable notification debouncing for specific methods
936
+ debouncedNotificationMethods: [
937
+ 'notifications/tools/list_changed',
938
+ 'notifications/resources/list_changed',
939
+ 'notifications/prompts/list_changed'
940
+ ]
941
+ }
942
+ );
943
+
944
+ // Now, any rapid changes to tools, resources, or prompts will result
945
+ // in a single, consolidated notification for each type.
946
+ server.registerTool("tool1", ...).disable();
947
+ server.registerTool("tool2", ...).disable();
948
+ server.registerTool("tool3", ...).disable();
949
+ // Only one 'notifications/tools/list_changed' is sent.
950
+ ```
951
+
952
+ ### Low-Level Server
953
+
954
+ For more control, you can use the low-level Server class directly:
955
+
956
+ ```typescript
957
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
958
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
959
+ import {
960
+ ListPromptsRequestSchema,
961
+ GetPromptRequestSchema
962
+ } from "@modelcontextprotocol/sdk/types.js";
963
+
964
+ const server = new Server(
965
+ {
966
+ name: "example-server",
967
+ version: "1.0.0"
968
+ },
969
+ {
970
+ capabilities: {
971
+ prompts: {}
972
+ }
973
+ }
974
+ );
975
+
976
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
977
+ return {
978
+ prompts: [{
979
+ name: "example-prompt",
980
+ description: "An example prompt template",
981
+ arguments: [{
982
+ name: "arg1",
983
+ description: "Example argument",
984
+ required: true
985
+ }]
986
+ }]
987
+ };
988
+ });
989
+
990
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
991
+ if (request.params.name !== "example-prompt") {
992
+ throw new Error("Unknown prompt");
993
+ }
994
+ return {
995
+ description: "Example prompt",
996
+ messages: [{
997
+ role: "user",
998
+ content: {
999
+ type: "text",
1000
+ text: "Example prompt text"
1001
+ }
1002
+ }]
1003
+ };
1004
+ });
1005
+
1006
+ const transport = new StdioServerTransport();
1007
+ await server.connect(transport);
1008
+ ```
1009
+
1010
+ ### Eliciting User Input
1011
+
1012
+ MCP servers can request additional information from users through the elicitation feature. This is useful for interactive workflows where the server needs user input or confirmation:
1013
+
1014
+ ```typescript
1015
+ // Server-side: Restaurant booking tool that asks for alternatives
1016
+ server.tool(
1017
+ "book-restaurant",
1018
+ {
1019
+ restaurant: z.string(),
1020
+ date: z.string(),
1021
+ partySize: z.number()
1022
+ },
1023
+ async ({ restaurant, date, partySize }) => {
1024
+ // Check availability
1025
+ const available = await checkAvailability(restaurant, date, partySize);
1026
+
1027
+ if (!available) {
1028
+ // Ask user if they want to try alternative dates
1029
+ const result = await server.server.elicitInput({
1030
+ message: `No tables available at ${restaurant} on ${date}. Would you like to check alternative dates?`,
1031
+ requestedSchema: {
1032
+ type: "object",
1033
+ properties: {
1034
+ checkAlternatives: {
1035
+ type: "boolean",
1036
+ title: "Check alternative dates",
1037
+ description: "Would you like me to check other dates?"
1038
+ },
1039
+ flexibleDates: {
1040
+ type: "string",
1041
+ title: "Date flexibility",
1042
+ description: "How flexible are your dates?",
1043
+ enum: ["next_day", "same_week", "next_week"],
1044
+ enumNames: ["Next day", "Same week", "Next week"]
1045
+ }
1046
+ },
1047
+ required: ["checkAlternatives"]
1048
+ }
1049
+ });
1050
+
1051
+ if (result.action === "accept" && result.content?.checkAlternatives) {
1052
+ const alternatives = await findAlternatives(
1053
+ restaurant,
1054
+ date,
1055
+ partySize,
1056
+ result.content.flexibleDates as string
1057
+ );
1058
+ return {
1059
+ content: [{
1060
+ type: "text",
1061
+ text: `Found these alternatives: ${alternatives.join(", ")}`
1062
+ }]
1063
+ };
1064
+ }
1065
+
1066
+ return {
1067
+ content: [{
1068
+ type: "text",
1069
+ text: "No booking made. Original date not available."
1070
+ }]
1071
+ };
1072
+ }
1073
+
1074
+ // Book the table
1075
+ await makeBooking(restaurant, date, partySize);
1076
+ return {
1077
+ content: [{
1078
+ type: "text",
1079
+ text: `Booked table for ${partySize} at ${restaurant} on ${date}`
1080
+ }]
1081
+ };
1082
+ }
1083
+ );
1084
+ ```
1085
+
1086
+ Client-side: Handle elicitation requests
1087
+
1088
+ ```typescript
1089
+ // This is a placeholder - implement based on your UI framework
1090
+ async function getInputFromUser(message: string, schema: any): Promise<{
1091
+ action: "accept" | "decline" | "cancel";
1092
+ data?: Record<string, any>;
1093
+ }> {
1094
+ // This should be implemented depending on the app
1095
+ throw new Error("getInputFromUser must be implemented for your platform");
1096
+ }
1097
+
1098
+ client.setRequestHandler(ElicitRequestSchema, async (request) => {
1099
+ const userResponse = await getInputFromUser(
1100
+ request.params.message,
1101
+ request.params.requestedSchema
1102
+ );
1103
+
1104
+ return {
1105
+ action: userResponse.action,
1106
+ content: userResponse.action === "accept" ? userResponse.data : undefined
1107
+ };
1108
+ });
1109
+ ```
1110
+
1111
+ **Note**: Elicitation requires client support. Clients must declare the `elicitation` capability during initialization.
1112
+
1113
+ ### Writing MCP Clients
1114
+
1115
+ The SDK provides a high-level client interface:
1116
+
1117
+ ```typescript
1118
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
1119
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
1120
+
1121
+ const transport = new StdioClientTransport({
1122
+ command: "node",
1123
+ args: ["server.js"]
1124
+ });
1125
+
1126
+ const client = new Client(
1127
+ {
1128
+ name: "example-client",
1129
+ version: "1.0.0"
1130
+ }
1131
+ );
1132
+
1133
+ await client.connect(transport);
1134
+
1135
+ // List prompts
1136
+ const prompts = await client.listPrompts();
1137
+
1138
+ // Get a prompt
1139
+ const prompt = await client.getPrompt({
1140
+ name: "example-prompt",
1141
+ arguments: {
1142
+ arg1: "value"
1143
+ }
1144
+ });
1145
+
1146
+ // List resources
1147
+ const resources = await client.listResources();
1148
+
1149
+ // Read a resource
1150
+ const resource = await client.readResource({
1151
+ uri: "file:///example.txt"
1152
+ });
1153
+
1154
+ // Call a tool
1155
+ const result = await client.callTool({
1156
+ name: "example-tool",
1157
+ arguments: {
1158
+ arg1: "value"
1159
+ }
1160
+ });
1161
+
1162
+ ```
1163
+
1164
+ ### Proxy Authorization Requests Upstream
1165
+
1166
+ You can proxy OAuth requests to an external authorization provider:
1167
+
1168
+ ```typescript
1169
+ import express from 'express';
1170
+ import { ProxyOAuthServerProvider } from '@modelcontextprotocol/sdk/server/auth/providers/proxyProvider.js';
1171
+ import { mcpAuthRouter } from '@modelcontextprotocol/sdk/server/auth/router.js';
1172
+
1173
+ const app = express();
1174
+
1175
+ const proxyProvider = new ProxyOAuthServerProvider({
1176
+ endpoints: {
1177
+ authorizationUrl: "https://auth.external.com/oauth2/v1/authorize",
1178
+ tokenUrl: "https://auth.external.com/oauth2/v1/token",
1179
+ revocationUrl: "https://auth.external.com/oauth2/v1/revoke",
1180
+ },
1181
+ verifyAccessToken: async (token) => {
1182
+ return {
1183
+ token,
1184
+ clientId: "123",
1185
+ scopes: ["openid", "email", "profile"],
1186
+ }
1187
+ },
1188
+ getClient: async (client_id) => {
1189
+ return {
1190
+ client_id,
1191
+ redirect_uris: ["http://localhost:3000/callback"],
1192
+ }
1193
+ }
1194
+ })
1195
+
1196
+ app.use(mcpAuthRouter({
1197
+ provider: proxyProvider,
1198
+ issuerUrl: new URL("http://auth.external.com"),
1199
+ baseUrl: new URL("http://mcp.example.com"),
1200
+ serviceDocumentationUrl: new URL("https://docs.example.com/"),
1201
+ }))
1202
+ ```
1203
+
1204
+ This setup allows you to:
1205
+
1206
+ - Forward OAuth requests to an external provider
1207
+ - Add custom token validation logic
1208
+ - Manage client registrations
1209
+ - Provide custom documentation URLs
1210
+ - Maintain control over the OAuth flow while delegating to an external provider
1211
+
1212
+ ### Backwards Compatibility
1213
+
1214
+ Clients and servers with StreamableHttp transport can maintain [backwards compatibility](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#backwards-compatibility) with the deprecated HTTP+SSE transport (from protocol version 2024-11-05) as follows
1215
+
1216
+ #### Client-Side Compatibility
1217
+
1218
+ For clients that need to work with both Streamable HTTP and older SSE servers:
1219
+
1220
+ ```typescript
1221
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
1222
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
1223
+ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
1224
+ let client: Client|undefined = undefined
1225
+ const baseUrl = new URL(url);
1226
+ try {
1227
+ client = new Client({
1228
+ name: 'streamable-http-client',
1229
+ version: '1.0.0'
1230
+ });
1231
+ const transport = new StreamableHTTPClientTransport(
1232
+ new URL(baseUrl)
1233
+ );
1234
+ await client.connect(transport);
1235
+ console.log("Connected using Streamable HTTP transport");
1236
+ } catch (error) {
1237
+ // If that fails with a 4xx error, try the older SSE transport
1238
+ console.log("Streamable HTTP connection failed, falling back to SSE transport");
1239
+ client = new Client({
1240
+ name: 'sse-client',
1241
+ version: '1.0.0'
1242
+ });
1243
+ const sseTransport = new SSEClientTransport(baseUrl);
1244
+ await client.connect(sseTransport);
1245
+ console.log("Connected using SSE transport");
1246
+ }
1247
+ ```
1248
+
1249
+ #### Server-Side Compatibility
1250
+
1251
+ For servers that need to support both Streamable HTTP and older clients:
1252
+
1253
+ ```typescript
1254
+ import express from "express";
1255
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1256
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
1257
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
1258
+
1259
+ const server = new McpServer({
1260
+ name: "backwards-compatible-server",
1261
+ version: "1.0.0"
1262
+ });
1263
+
1264
+ // ... set up server resources, tools, and prompts ...
1265
+
1266
+ const app = express();
1267
+ app.use(express.json());
1268
+
1269
+ // Store transports for each session type
1270
+ const transports = {
1271
+ streamable: {} as Record<string, StreamableHTTPServerTransport>,
1272
+ sse: {} as Record<string, SSEServerTransport>
1273
+ };
1274
+
1275
+ // Modern Streamable HTTP endpoint
1276
+ app.all('/mcp', async (req, res) => {
1277
+ // Handle Streamable HTTP transport for modern clients
1278
+ // Implementation as shown in the "With Session Management" example
1279
+ // ...
1280
+ });
1281
+
1282
+ // Legacy SSE endpoint for older clients
1283
+ app.get('/sse', async (req, res) => {
1284
+ // Create SSE transport for legacy clients
1285
+ const transport = new SSEServerTransport('/messages', res);
1286
+ transports.sse[transport.sessionId] = transport;
1287
+
1288
+ res.on("close", () => {
1289
+ delete transports.sse[transport.sessionId];
1290
+ });
1291
+
1292
+ await server.connect(transport);
1293
+ });
1294
+
1295
+ // Legacy message endpoint for older clients
1296
+ app.post('/messages', async (req, res) => {
1297
+ const sessionId = req.query.sessionId as string;
1298
+ const transport = transports.sse[sessionId];
1299
+ if (transport) {
1300
+ await transport.handlePostMessage(req, res, req.body);
1301
+ } else {
1302
+ res.status(400).send('No transport found for sessionId');
1303
+ }
1304
+ });
1305
+
1306
+ app.listen(3000);
1307
+ ```
1308
+
1309
+ **Note**: The SSE transport is now deprecated in favor of Streamable HTTP. New implementations should use Streamable HTTP, and existing SSE implementations should plan to migrate.
1310
+
1311
+ ## Documentation
1312
+
1313
+ - [Model Context Protocol documentation](https://modelcontextprotocol.io)
1314
+ - [MCP Specification](https://spec.modelcontextprotocol.io)
1315
+ - [Example Servers](https://github.com/modelcontextprotocol/servers)
1316
+
1317
+ ## Contributing
1318
+
1319
+ Issues and pull requests are welcome on GitHub at <https://github.com/modelcontextprotocol/typescript-sdk>.
1320
+
1321
+ ## License
1322
+
1323
+ This project is licensed under the MIT License—see the [LICENSE](LICENSE) file for details.