Skip to content

Commit 015a9d9

Browse files
refactor: make example servers stateless
Address PR feedback: - Remove session store and session management - Use stateless mode (sessionIdGenerator: undefined) - Each request creates a fresh server instance - Remove user-controlled format string in error message - Simplify handler logic significantly Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 56a0835 commit 015a9d9

1 file changed

Lines changed: 10 additions & 57 deletions

File tree

src/modules/example-apps/index.ts

Lines changed: 10 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@
33
*
44
* Each example MCP App server is mounted at its own path, sharing the same
55
* OAuth authentication as the main MCP server.
6+
*
7+
* These servers run in STATELESS mode - each request creates a fresh server
8+
* instance without maintaining session state across requests.
69
*/
710

811
import { Router, Request, Response, NextFunction } from 'express';
912
import cors from 'cors';
1013
import { BearerAuthMiddlewareOptions, requireBearerAuth } from '@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js';
1114
import { getOAuthProtectedResourceMetadataUrl } from '@modelcontextprotocol/sdk/server/auth/router.js';
1215
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
13-
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
14-
import { randomUUID } from 'crypto';
1516
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1617
import { ITokenValidator } from '../../interfaces/auth-validator.js';
1718
import { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js';
@@ -59,13 +60,6 @@ export interface ExampleAppsConfig {
5960
baseUri: string;
6061
}
6162

62-
// Session store: maps sessionId to { transport, server } per slug
63-
const sessions = new Map<string, {
64-
transport: StreamableHTTPServerTransport;
65-
server: McpServer;
66-
slug: string;
67-
}>();
68-
6963
export class ExampleAppsModule {
7064
private router: Router;
7165

@@ -83,7 +77,7 @@ export class ExampleAppsModule {
8377
private setupRouter(): Router {
8478
const router = Router();
8579

86-
// CORS configuration
80+
// CORS configuration - intentionally permissive for public MCP reference server
8781
const corsOptions = {
8882
origin: true,
8983
methods: ['GET', 'POST', 'DELETE'],
@@ -106,72 +100,31 @@ export class ExampleAppsModule {
106100
};
107101
const bearerAuth = requireBearerAuth(bearerAuthOptions);
108102

109-
// Handler for /:slug/mcp
103+
// Handler for /:slug/mcp - stateless: each request creates a fresh server
110104
const handleExampleMcp = async (req: Request, res: Response) => {
111105
const { slug } = req.params;
112106
const createServer = EXAMPLE_SERVERS[slug];
113107

114108
if (!createServer) {
115109
res.status(404).json({
116110
jsonrpc: "2.0",
117-
error: { code: -32001, message: `Unknown example server: ${slug}` },
111+
error: { code: -32001, message: "Unknown example server" },
118112
id: null,
119113
});
120114
return;
121115
}
122116

123-
let transport: StreamableHTTPServerTransport | undefined;
124-
125117
try {
126-
const sessionId = req.headers['mcp-session-id'] as string | undefined;
127-
128-
// Check for existing session
129-
if (sessionId) {
130-
const session = sessions.get(sessionId);
131-
if (session && session.slug === slug) {
132-
await session.transport.handleRequest(req, res, req.body);
133-
return;
134-
} else if (session) {
135-
res.status(400).json({
136-
jsonrpc: "2.0",
137-
error: { code: -32000, message: "Session belongs to different server" },
138-
id: null,
139-
});
140-
return;
141-
}
142-
}
143-
144-
// New session - must be initialize request
145-
if (!isInitializeRequest(req.body)) {
146-
res.status(400).json({
147-
jsonrpc: "2.0",
148-
error: { code: -32000, message: "Bad request: not initialized" },
149-
id: null,
150-
});
151-
return;
152-
}
153-
154-
// Create new server instance
118+
// Create fresh server and transport for each request (stateless mode)
155119
const server = createServer();
156-
const newSessionId = randomUUID();
157-
158-
transport = new StreamableHTTPServerTransport({
159-
sessionIdGenerator: () => newSessionId,
160-
onsessioninitialized: (id) => {
161-
sessions.set(id, { transport: transport!, server, slug });
162-
},
120+
const transport = new StreamableHTTPServerTransport({
121+
sessionIdGenerator: undefined, // Stateless: no session management
163122
});
164123

165-
transport.onclose = () => {
166-
if (transport?.sessionId) {
167-
sessions.delete(transport.sessionId);
168-
}
169-
};
170-
171124
await server.connect(transport);
172125
await transport.handleRequest(req, res, req.body);
173126
} catch (error) {
174-
console.error(`Error handling ${slug} MCP request:`, error);
127+
console.error('Error handling MCP request:', error);
175128
if (!res.headersSent) {
176129
res.status(500).json({
177130
jsonrpc: "2.0",

0 commit comments

Comments
 (0)