44 * This example demonstrates using the McpServer.builder() fluent API
55 * to create and configure an MCP server with:
66 * - Tools, resources, and prompts registration
7- * - Middleware (logging, rate limiting, custom metrics)
7+ * - Middleware (logging, custom metrics)
88 * - Per-tool middleware (authorization)
99 * - Error handlers (onError, onProtocolError)
10- * - Session management with SessionStore
1110 * - Context helpers (logging, notifications)
1211 *
1312 * Run with: npx tsx src/simpleStreamableHttpBuilder.ts
@@ -17,14 +16,8 @@ import { randomUUID } from 'node:crypto';
1716
1817import { createMcpExpressApp } from '@modelcontextprotocol/express' ;
1918import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node' ;
20- import type { CallToolResult , GetPromptResult , ReadResourceResult , ToolMiddleware } from '@modelcontextprotocol/server' ;
21- import {
22- createLoggingMiddleware ,
23- createSessionStore ,
24- isInitializeRequest ,
25- McpServer ,
26- text
27- } from '@modelcontextprotocol/server' ;
19+ import type { CallToolResult , GetPromptResult , ReadResourceResult , ToolMiddleware } from '@modelcontextprotocol/server' ;
20+ import { createLoggingMiddleware , isInitializeRequest , McpServer , text } from '@modelcontextprotocol/server' ;
2821import type { Request , Response } from 'express' ;
2922import * as z from 'zod/v4' ;
3023
@@ -68,7 +61,7 @@ const adminAuthMiddleware: ToolMiddleware = async (ctx, next) => {
6861} ;
6962
7063// ═══════════════════════════════════════════════════════════════════════════
71- // Session Store Setup
64+ // Session Management
7265// ═══════════════════════════════════════════════════════════════════════════
7366
7467/**
@@ -80,25 +73,9 @@ interface SessionData {
8073}
8174
8275/**
83- * Create session store with lifecycle events and timeout.
84- * This replaces the manual session map management.
76+ * Simple Map-based session storage.
8577 */
86- const sessionStore = createSessionStore < SessionData > ( {
87- sessionTimeout : 30 * 60 * 1000 , // 30 minutes
88- maxSessions : 100 ,
89- cleanupInterval : 60_000 , // Check for expired sessions every minute
90- events : {
91- onSessionCreated : ( id ) => {
92- console . log ( `[SESSION] Created: ${ id } ` ) ;
93- } ,
94- onSessionDestroyed : ( id ) => {
95- console . log ( `[SESSION] Destroyed: ${ id } ` ) ;
96- } ,
97- onSessionUpdated : ( id ) => {
98- console . log ( `[SESSION] Updated: ${ id } ` ) ;
99- }
100- }
101- } ) ;
78+ const sessions = new Map < string , SessionData > ( ) ;
10279
10380// ═══════════════════════════════════════════════════════════════════════════
10481// Server Factory
@@ -140,12 +117,10 @@ const getServer = () => {
140117 )
141118
142119 // ─── Tool-Specific Middleware ───
143- . useToolMiddleware (
144- async ( ctx , next ) => {
145- console . log ( `Tool '${ ctx . name } ' called` ) ;
146- return next ( ) ;
147- }
148- )
120+ . useToolMiddleware ( async ( ctx , next ) => {
121+ console . log ( `Tool '${ ctx . name } ' called` ) ;
122+ return next ( ) ;
123+ } )
149124
150125 // Custom metrics middleware
151126 . useToolMiddleware ( metricsMiddleware )
@@ -199,7 +174,7 @@ const getServer = () => {
199174 }
200175 } ,
201176 async function ( { name } , ctx ) : Promise < CallToolResult > {
202- const sleep = ( ms : number ) => new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ;
177+ const sleep = ( ms : number ) => new Promise ( resolve => setTimeout ( resolve , ms ) ) ;
203178
204179 // Use context logging helper
205180 await ctx . loggingNotification . debug ( `Starting multi-greet for ${ name } ` ) ;
@@ -304,7 +279,10 @@ const getServer = () => {
304279 }
305280 } ,
306281 async ( { errorType } ) : Promise < CallToolResult > => {
307- const error = errorType === 'application' ? new Error ( 'This is a test application error' ) : new Error ( 'Validation failed: invalid input format' ) ;
282+ const error =
283+ errorType === 'application'
284+ ? new Error ( 'This is a test application error' )
285+ : new Error ( 'Validation failed: invalid input format' ) ;
308286 throw error ;
309287 }
310288 )
@@ -330,23 +308,23 @@ const getServer = () => {
330308 }
331309 )
332310
333- // Resource demonstrating session info
311+ // Resource demonstrating server info
334312 . resource (
335- 'session -info' ,
336- 'https://example.com/session /info' ,
313+ 'server -info' ,
314+ 'https://example.com/server /info' ,
337315 {
338- title : 'Session Information' ,
339- description : 'Returns current session statistics'
316+ title : 'Server Information' ,
317+ description : 'Returns current server statistics'
340318 } ,
341319 async ( ) : Promise < ReadResourceResult > => {
342320 const stats = {
343- activeSessions : sessionStore . size ( ) ,
344- sessionIds : sessionStore . keys ( )
321+ activeSessions : sessions . size ,
322+ uptime : process . uptime ( )
345323 } ;
346324 return {
347325 contents : [
348326 {
349- uri : 'https://example.com/session /info' ,
327+ uri : 'https://example.com/server /info' ,
350328 mimeType : 'application/json' ,
351329 text : JSON . stringify ( stats , null , 2 )
352330 }
@@ -396,14 +374,14 @@ const app = createMcpExpressApp();
396374
397375/**
398376 * MCP POST endpoint handler.
399- * Uses SessionStore for session management.
377+ * Uses a simple Map for session management.
400378 */
401379const mcpPostHandler = async ( req : Request , res : Response ) => {
402380 const sessionId = req . headers [ 'mcp-session-id' ] as string | undefined ;
403381
404382 try {
405383 // Check for existing session
406- const session = sessionId ? sessionStore . get ( sessionId ) : undefined ;
384+ const session = sessionId ? sessions . get ( sessionId ) : undefined ;
407385
408386 if ( session ) {
409387 // Reuse existing transport
@@ -420,9 +398,9 @@ const mcpPostHandler = async (req: Request, res: Response) => {
420398 const transport = new NodeStreamableHTTPServerTransport ( {
421399 sessionIdGenerator : ( ) => randomUUID ( ) ,
422400 eventStore,
423- onsessioninitialized : ( sid ) => {
424- // Store session with SessionStore
425- sessionStore . set ( sid , {
401+ onsessioninitialized : sid => {
402+ // Store session
403+ sessions . set ( sid , {
426404 transport,
427405 createdAt : new Date ( )
428406 } ) ;
@@ -433,7 +411,7 @@ const mcpPostHandler = async (req: Request, res: Response) => {
433411 transport . onclose = ( ) => {
434412 const sid = transport . sessionId ;
435413 if ( sid ) {
436- sessionStore . delete ( sid ) ;
414+ sessions . delete ( sid ) ;
437415 }
438416 } ;
439417
@@ -476,7 +454,7 @@ app.post('/mcp', mcpPostHandler);
476454 */
477455const mcpGetHandler = async ( req : Request , res : Response ) => {
478456 const sessionId = req . headers [ 'mcp-session-id' ] as string | undefined ;
479- const session = sessionId ? sessionStore . get ( sessionId ) : undefined ;
457+ const session = sessionId ? sessions . get ( sessionId ) : undefined ;
480458
481459 if ( ! session ) {
482460 res . status ( 400 ) . send ( 'Invalid or missing session ID' ) ;
@@ -500,7 +478,7 @@ app.get('/mcp', mcpGetHandler);
500478 */
501479const mcpDeleteHandler = async ( req : Request , res : Response ) => {
502480 const sessionId = req . headers [ 'mcp-session-id' ] as string | undefined ;
503- const session = sessionId ? sessionStore . get ( sessionId ) : undefined ;
481+ const session = sessionId ? sessions . get ( sessionId ) : undefined ;
504482
505483 if ( ! session ) {
506484 res . status ( 400 ) . send ( 'Invalid or missing session ID' ) ;
@@ -525,7 +503,7 @@ app.delete('/mcp', mcpDeleteHandler);
525503// Server Startup
526504// ═══════════════════════════════════════════════════════════════════════════
527505
528- app . listen ( PORT , ( error ) => {
506+ app . listen ( PORT , error => {
529507 if ( error ) {
530508 console . error ( 'Failed to start server:' , error ) ;
531509 // eslint-disable-next-line unicorn/no-process-exit
@@ -540,10 +518,9 @@ app.listen(PORT, (error) => {
540518 console . log ( 'Features demonstrated:' ) ;
541519 console . log ( ' - Builder pattern for server configuration' ) ;
542520 console . log ( ' - Universal middleware (logging)' ) ;
543- console . log ( ' - Tool-specific middleware (rate limiting, metrics)' ) ;
521+ console . log ( ' - Tool-specific middleware (metrics)' ) ;
544522 console . log ( ' - Per-tool middleware (authorization)' ) ;
545523 console . log ( ' - Error handlers (onError, onProtocolError)' ) ;
546- console . log ( ' - Session management with SessionStore' ) ;
547524 console . log ( ' - Context helpers (logging, notifications)' ) ;
548525 console . log ( '═══════════════════════════════════════════════════════════════' ) ;
549526} ) ;
@@ -555,23 +532,18 @@ app.listen(PORT, (error) => {
555532process . on ( 'SIGINT' , async ( ) => {
556533 console . log ( '\n[SHUTDOWN] Received SIGINT, shutting down...' ) ;
557534
558- // Close all sessions using SessionStore
559- const sessionIds = sessionStore . keys ( ) ;
560- for ( const sid of sessionIds ) {
535+ // Close all sessions
536+ for ( const [ sid , session ] of sessions ) {
561537 try {
562- const session = sessionStore . get ( sid ) ;
563- if ( session ) {
564- console . log ( `[SHUTDOWN] Closing session ${ sid } ` ) ;
565- await session . transport . close ( ) ;
566- }
538+ console . log ( `[SHUTDOWN] Closing session ${ sid } ` ) ;
539+ await session . transport . close ( ) ;
567540 } catch ( error ) {
568541 console . error ( `[SHUTDOWN] Error closing session ${ sid } :` , error ) ;
569542 }
570543 }
571544
572- // Clear the session store (also stops cleanup timer)
573- sessionStore . clear ( ) ;
574- ( sessionStore as { dispose ?: ( ) => void } ) . dispose ?.( ) ;
545+ // Clear the sessions map
546+ sessions . clear ( ) ;
575547
576548 console . log ( '[SHUTDOWN] Complete' ) ;
577549 process . exit ( 0 ) ;
0 commit comments