1212 - [ Prompts] ( #prompts )
1313- [ Running Your Server] ( #running-your-server )
1414 - [ stdio] ( #stdio )
15- - [ HTTP with SSE ] ( #http-with-sse )
15+ - [ Streamable HTTP ] ( #streamable-http )
1616 - [ Testing and Debugging] ( #testing-and-debugging )
1717- [ Examples] ( #examples )
1818 - [ Echo Server] ( #echo-server )
2222 - [ Writing MCP Clients] ( #writing-mcp-clients )
2323 - [ Server Capabilities] ( #server-capabilities )
2424 - [ Proxy OAuth Server] ( #proxy-authorization-requests-upstream )
25+ - [ Backwards Compatibility] ( #backwards-compatibility )
2526
2627## Overview
2728
2829The 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:
2930
3031- Build MCP clients that can connect to any MCP server
3132- Create MCP servers that expose resources, prompts and tools
32- - Use standard transports like stdio and SSE
33+ - Use standard transports like stdio and Streamable HTTP
3334- Handle all MCP protocol messages and lifecycle events
3435
3536## Installation
@@ -207,14 +208,18 @@ const transport = new StdioServerTransport();
207208await server .connect (transport );
208209```
209210
210- ### HTTP with SSE
211+ ### Streamable HTTP
211212
212- For remote servers, start a web server with a Server-Sent Events (SSE) endpoint, and a separate endpoint for the client to send its messages to:
213+ For remote servers, set up a Streamable HTTP transport that handles both client requests and server-to-client notifications.
214+
215+ #### With Session Management
213216
214217``` typescript
215- import express , { Request , Response } from " express" ;
218+ import express from " express" ;
219+ import { randomUUID } from " node:crypto" ;
216220import { McpServer } from " @modelcontextprotocol/sdk/server/mcp.js" ;
217- import { SSEServerTransport } from " @modelcontextprotocol/sdk/server/sse.js" ;
221+ import { StreamableHTTPServerTransport } from " @modelcontextprotocol/sdk/server/streamableHttp.js" ;
222+ import { InMemoryEventStore } from " @modelcontextprotocol/sdk/inMemory.js" ;
218223
219224const server = new McpServer ({
220225 name: " example-server" ,
@@ -224,33 +229,128 @@ const server = new McpServer({
224229// ... set up server resources, tools, and prompts ...
225230
226231const app = express ();
232+ app .use (express .json ());
233+
234+ // Map to store transports by session ID
235+ const transports: { [sessionId : string ]: StreamableHTTPServerTransport } = {};
236+
237+ // Handle POST requests for client-to-server communication
238+ app .post (' /mcp' , async (req , res ) => {
239+ // Check for existing session ID
240+ const sessionId = req .headers [' mcp-session-id' ] as string | undefined ;
241+ let transport: StreamableHTTPServerTransport ;
242+
243+ if (sessionId && transports [sessionId ]) {
244+ // Reuse existing transport
245+ transport = transports [sessionId ];
246+ } else if (! sessionId && isInitializeRequest (req .body )) {
247+ // New initialization request
248+ const eventStore = new InMemoryEventStore ();
249+ transport = new StreamableHTTPServerTransport ({
250+ sessionIdGenerator : () => randomUUID (),
251+ eventStore , // Enable resumability
252+ onsessioninitialized : (sessionId ) => {
253+ // Store the transport by session ID
254+ transports [sessionId ] = transport ;
255+ }
256+ });
227257
228- // to support multiple simultaneous connections we have a lookup object from
229- // sessionId to transport
230- const transports: {[sessionId : string ]: SSEServerTransport } = {};
258+ // Clean up transport when closed
259+ transport .onclose = () => {
260+ if (transport .sessionId ) {
261+ delete transports [transport .sessionId ];
262+ }
263+ };
231264
232- app .get (" /sse" , async (_ : Request , res : Response ) => {
233- const transport = new SSEServerTransport (' /messages' , res );
234- transports [transport .sessionId ] = transport ;
235- res .on (" close" , () => {
236- delete transports [transport .sessionId ];
237- });
238- await server .connect (transport );
265+ // Connect to the MCP server
266+ await server .connect (transport );
267+ } else {
268+ // Invalid request
269+ res .status (400 ).json ({
270+ jsonrpc: ' 2.0' ,
271+ error: {
272+ code: - 32000 ,
273+ message: ' Bad Request: No valid session ID provided' ,
274+ },
275+ id: null ,
276+ });
277+ return ;
278+ }
279+
280+ // Handle the request
281+ await transport .handleRequest (req , res , req .body );
239282});
240283
241- app .post (" /messages" , async (req : Request , res : Response ) => {
242- const sessionId = req .query .sessionId as string ;
284+ // Handle GET requests for server-to-client notifications via SSE
285+ app .get (' /mcp' , async (req , res ) => {
286+ const sessionId = req .headers [' mcp-session-id' ] as string | undefined ;
287+ if (! sessionId || ! transports [sessionId ]) {
288+ res .status (400 ).send (' Invalid or missing session ID' );
289+ return ;
290+ }
291+
292+ // Support resumability with Last-Event-ID header
243293 const transport = transports [sessionId ];
244- if (transport ) {
245- await transport .handlePostMessage (req , res );
246- } else {
247- res .status (400 ).send (' No transport found for sessionId' );
294+ await transport .handleRequest (req , res );
295+ });
296+
297+ // Handle DELETE requests for session termination
298+ app .delete (' /mcp' , async (req , res ) => {
299+ const sessionId = req .headers [' mcp-session-id' ] as string | undefined ;
300+ if (! sessionId || ! transports [sessionId ]) {
301+ res .status (400 ).send (' Invalid or missing session ID' );
302+ return ;
248303 }
304+
305+ const transport = transports [sessionId ];
306+ await transport .handleRequest (req , res );
249307});
250308
251- app .listen (3001 );
309+ app .listen (3000 );
252310```
253311
312+ #### Without Session Management (Stateless)
313+
314+ For simpler use cases where session management isn't needed:
315+
316+ ``` typescript
317+ import express from " express" ;
318+ import { McpServer } from " @modelcontextprotocol/sdk/server/mcp.js" ;
319+ import { StreamableHTTPServerTransport } from " @modelcontextprotocol/sdk/server/streamableHttp.js" ;
320+
321+ const server = new McpServer ({
322+ name: " stateless-server" ,
323+ version: " 1.0.0"
324+ });
325+
326+ // ... set up server resources, tools, and prompts ...
327+
328+ const app = express ();
329+ app .use (express .json ());
330+
331+ // Handle all MCP requests (GET, POST, DELETE) at a single endpoint
332+ app .all (' /mcp' , async (req , res ) => {
333+ // Create a transport with sessionIdGenerator set to return undefined
334+ // This disables session tracking completely
335+ const transport = new StreamableHTTPServerTransport ({
336+ sessionIdGenerator : () => undefined ,
337+ req ,
338+ res
339+ });
340+
341+ // Connect to server and handle the request
342+ await server .connect (transport );
343+ await transport .handleRequest (req , res );
344+ });
345+
346+ app .listen (3000 );
347+ ```
348+
349+ This stateless approach is useful for:
350+ - Simple API wrappers
351+ - RESTful scenarios where each request is independent
352+ - Horizontally scaled deployments without shared session state
353+
254354### Testing and Debugging
255355
256356To test your server, you can use the [ MCP Inspector] ( https://github.com/modelcontextprotocol/inspector ) . See its README for more information.
@@ -596,6 +696,101 @@ This setup allows you to:
596696- Provide custom documentation URLs
597697- Maintain control over the OAuth flow while delegating to an external provider
598698
699+ ### Backwards Compatibility
700+
701+ The SDK provides support for backwards compatibility between different protocol versions:
702+
703+ #### Client-Side Compatibility
704+
705+ For clients that need to work with both Streamable HTTP and older SSE servers:
706+
707+ ``` typescript
708+ import { Client } from " @modelcontextprotocol/sdk/client/index.js" ;
709+ import { StreamableHTTPClientTransport } from " @modelcontextprotocol/sdk/client/streamableHttp.js" ;
710+ import { SSEClientTransport } from " @modelcontextprotocol/sdk/client/sse.js" ;
711+
712+ // First try connecting with Streamable HTTP transport
713+ try {
714+ const transport = new StreamableHTTPClientTransport (
715+ new URL (" http://localhost:3000/mcp" )
716+ );
717+ await client .connect (transport );
718+ console .log (" Connected using Streamable HTTP transport" );
719+ } catch (error ) {
720+ // If that fails with a 4xx error, try the older SSE transport
721+ console .log (" Streamable HTTP connection failed, falling back to SSE transport" );
722+ const sseTransport = new SSEClientTransport ({
723+ sseUrl: new URL (" http://localhost:3000/sse" ),
724+ postUrl: new URL (" http://localhost:3000/messages" )
725+ });
726+ await client .connect (sseTransport );
727+ console .log (" Connected using SSE transport" );
728+ }
729+ ```
730+
731+ #### Server-Side Compatibility
732+
733+ For servers that need to support both Streamable HTTP and older clients:
734+
735+ ``` typescript
736+ import express from " express" ;
737+ import { McpServer } from " @modelcontextprotocol/sdk/server/mcp.js" ;
738+ import { StreamableHTTPServerTransport } from " @modelcontextprotocol/sdk/server/streamableHttp.js" ;
739+ import { SSEServerTransport } from " @modelcontextprotocol/sdk/server/sse.js" ;
740+ import { InMemoryEventStore } from " @modelcontextprotocol/sdk/inMemory.js" ;
741+
742+ const server = new McpServer ({
743+ name: " backwards-compatible-server" ,
744+ version: " 1.0.0"
745+ });
746+
747+ // ... set up server resources, tools, and prompts ...
748+
749+ const app = express ();
750+ app .use (express .json ());
751+
752+ // Store transports for each session type
753+ const transports = {
754+ streamable: {} as Record <string , StreamableHTTPServerTransport >,
755+ sse: {} as Record <string , SSEServerTransport >
756+ };
757+
758+ // Modern Streamable HTTP endpoint
759+ app .all (' /mcp' , async (req , res ) => {
760+ // Handle Streamable HTTP transport for modern clients
761+ // Implementation as shown in the "With Session Management" example
762+ // ...
763+ });
764+
765+ // Legacy SSE endpoint for older clients
766+ app .get (' /sse' , async (req , res ) => {
767+ // Create SSE transport for legacy clients
768+ const transport = new SSEServerTransport (' /messages' , res );
769+ transports .sse [transport .sessionId ] = transport ;
770+
771+ res .on (" close" , () => {
772+ delete transports .sse [transport .sessionId ];
773+ });
774+
775+ await server .connect (transport );
776+ });
777+
778+ // Legacy message endpoint for older clients
779+ app .post (' /messages' , async (req , res ) => {
780+ const sessionId = req .query .sessionId as string ;
781+ const transport = transports .sse [sessionId ];
782+ if (transport ) {
783+ await transport .handlePostMessage (req , res );
784+ } else {
785+ res .status (400 ).send (' No transport found for sessionId' );
786+ }
787+ });
788+
789+ app .listen (3000 );
790+ ```
791+
792+ ** 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.
793+
599794## Documentation
600795
601796- [ Model Context Protocol documentation] ( https://modelcontextprotocol.io )
0 commit comments