From c1dc6663ef9911784dd87a59b5667b19f131e9cf Mon Sep 17 00:00:00 2001 From: Enrico Toniato <2827496+tonxxd@users.noreply.github.com> Date: Tue, 14 Oct 2025 16:12:06 +0200 Subject: [PATCH 1/2] fix status dot --- .../client/components/InspectorDashboard.tsx | 15 +++++++---- .../src/client/components/Layout.tsx | 6 ++--- .../src/client/context/McpContext.tsx | 26 ++++++++++++++----- packages/mcp-use/src/server/mcp-server.ts | 8 ++++-- pnpm-lock.yaml | 6 +++++ 5 files changed, 45 insertions(+), 16 deletions(-) diff --git a/packages/inspector/src/client/components/InspectorDashboard.tsx b/packages/inspector/src/client/components/InspectorDashboard.tsx index 40046c70..f44c80d0 100644 --- a/packages/inspector/src/client/components/InspectorDashboard.tsx +++ b/packages/inspector/src/client/components/InspectorDashboard.tsx @@ -124,8 +124,13 @@ export function InspectorDashboard() { }, {} as Record), } + // Map UI transport type to actual transport type + // "SSE" in UI means "Streamable HTTP" which uses 'http' transport + // "WebSocket" in UI means "WebSocket" which uses 'sse' transport + const actualTransportType = transportType === 'SSE' ? 'http' : 'sse' + // For now, use URL as both ID and name - this will need proper implementation - addConnection(url, url, proxyConfig) + addConnection(url, url, proxyConfig, actualTransportType) } const handleClearAllConnections = () => { @@ -222,7 +227,7 @@ export function InspectorDashboard() { > v - {typeof window !== 'undefined' && (window as any).__INSPECTOR_VERSION__ || '1.0.0'} + {(typeof window !== 'undefined' && (window as any).__INSPECTOR_VERSION__) || '1.0.0'} @@ -268,7 +273,7 @@ export function InspectorDashboard() {

{connection.name}

- {connection.error + {connection.error && connection.state !== 'ready' ? ( @@ -398,8 +403,8 @@ export function InspectorDashboard() { - Streamable HTTP - WebSocket + Streamable HTTP (Recommended) + Server-Sent Events (SSE)
diff --git a/packages/inspector/src/client/components/Layout.tsx b/packages/inspector/src/client/components/Layout.tsx index 001e3fa6..fea366a1 100644 --- a/packages/inspector/src/client/components/Layout.tsx +++ b/packages/inspector/src/client/components/Layout.tsx @@ -233,7 +233,7 @@ export function Layout({ children }: LayoutProps) { const existing = connections.find(c => c.url === autoConnectUrl) if (!existing) { setIsAutoConnecting(true) - addConnection(autoConnectUrl, 'Local MCP Server') + addConnection(autoConnectUrl, 'Local MCP Server', undefined, 'http') // Navigate immediately but keep loading screen visible a bit longer to avoid flash navigate(`/servers/${encodeURIComponent(autoConnectUrl)}`) setTimeout(() => { @@ -254,7 +254,7 @@ export function Layout({ children }: LayoutProps) { const existing = connections.find(c => c.url === config.autoConnectUrl) if (!existing) { // Auto-connect to the local server - addConnection(config.autoConnectUrl, 'Local MCP Server') + addConnection(config.autoConnectUrl, 'Local MCP Server', undefined, 'http') } } }) @@ -383,7 +383,7 @@ export function Layout({ children }: LayoutProps) { {selectedServer && (
- {selectedServer.error + {selectedServer.error && selectedServer.state !== 'ready' ? ( diff --git a/packages/inspector/src/client/context/McpContext.tsx b/packages/inspector/src/client/context/McpContext.tsx index b3945974..284800ce 100644 --- a/packages/inspector/src/client/context/McpContext.tsx +++ b/packages/inspector/src/client/context/McpContext.tsx @@ -21,7 +21,7 @@ interface MCPConnection { interface McpContextType { connections: MCPConnection[] - addConnection: (url: string, name?: string, proxyConfig?: { proxyAddress?: string, proxyToken?: string, customHeaders?: Record }) => void + addConnection: (url: string, name?: string, proxyConfig?: { proxyAddress?: string, proxyToken?: string, customHeaders?: Record }, transportType?: 'http' | 'sse') => void removeConnection: (id: string) => void getConnection: (id: string) => MCPConnection | undefined } @@ -33,12 +33,14 @@ interface SavedConnection { url: string name: string proxyConfig?: { proxyAddress?: string, proxyToken?: string, customHeaders?: Record } + transportType?: 'http' | 'sse' } -function McpConnectionWrapper({ url, name, proxyConfig, onUpdate, onRemove: _onRemove }: { +function McpConnectionWrapper({ url, name, proxyConfig, transportType, onUpdate, onRemove: _onRemove }: { url: string name: string proxyConfig?: { proxyAddress?: string, proxyToken?: string, customHeaders?: Record } + transportType?: 'http' | 'sse' onUpdate: (connection: MCPConnection) => void onRemove: () => void }) { @@ -79,6 +81,7 @@ function McpConnectionWrapper({ url, name, proxyConfig, onUpdate, onRemove: _onR url: finalUrl, callbackUrl, customHeaders: Object.keys(customHeaders).length > 0 ? customHeaders : undefined, + transportType: transportType || 'http', // Default to 'http' for Streamable HTTP }) const onUpdateRef = useRef(onUpdate) const prevConnectionRef = useRef(null) @@ -194,12 +197,21 @@ export function McpProvider({ children }: { children: ReactNode }) { && typeof conn.id === 'string' && typeof conn.url === 'string' && typeof conn.name === 'string' + }).map((conn: any) => { + // Migrate existing connections to include transportType + if (!conn.transportType) { + conn.transportType = 'http' // Default to 'http' for Streamable HTTP + } + return conn }) : [] - // If we filtered out any invalid connections, update localStorage - if (validConnections.length !== parsed.length) { - console.warn('Cleaned up invalid connections from localStorage') + // If we filtered out any invalid connections or migrated transport types, update localStorage + const hasChanges = validConnections.length !== parsed.length + || validConnections.some((conn: any) => conn.transportType === 'http' && !parsed.find((p: any) => p.id === conn.id && p.transportType)) + + if (hasChanges) { + console.warn('Updated connections in localStorage with transport type migration') localStorage.setItem('mcp-inspector-connections', JSON.stringify(validConnections)) } @@ -218,13 +230,14 @@ export function McpProvider({ children }: { children: ReactNode }) { setConnectionVersion(v => v + 1) }, []) - const addConnection = useCallback((url: string, name?: string, proxyConfig?: { proxyAddress?: string, proxyToken?: string, customHeaders?: Record }) => { + const addConnection = useCallback((url: string, name?: string, proxyConfig?: { proxyAddress?: string, proxyToken?: string, customHeaders?: Record }, transportType?: 'http' | 'sse') => { const connectionName = name || url const newConnection: SavedConnection = { id: url, url, name: connectionName, proxyConfig, + transportType: transportType || 'http', // Default to 'http' for Streamable HTTP } setSavedConnections((prev) => { @@ -289,6 +302,7 @@ export function McpProvider({ children }: { children: ReactNode }) { url={saved.url} name={saved.name} proxyConfig={saved.proxyConfig} + transportType={saved.transportType} onUpdate={updateConnection} onRemove={() => removeConnection(saved.id)} /> diff --git a/packages/mcp-use/src/server/mcp-server.ts b/packages/mcp-use/src/server/mcp-server.ts index 20b6bb50..9ca59382 100644 --- a/packages/mcp-use/src/server/mcp-server.ts +++ b/packages/mcp-use/src/server/mcp-server.ts @@ -46,7 +46,7 @@ export class McpServer { this.app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*') res.header('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS') - res.header('Access-Control-Allow-Headers', 'Content-Type') + res.header('Access-Control-Allow-Headers', 'Content-Type, Accept, Authorization, mcp-protocol-version, mcp-session-id, X-Proxy-Token, X-Target-URL') next() }) @@ -316,10 +316,14 @@ export class McpServer { const { StreamableHTTPServerTransport } = await import('@modelcontextprotocol/sdk/server/streamableHttp.js') // Create StreamableHTTPServerTransport in stateless mode + // Note: Stateless mode means one active connection at a time + // For multiple concurrent inspectors, use the built-in proxy or same-origin connections const httpTransport = new StreamableHTTPServerTransport({ - sessionIdGenerator: undefined // Stateless mode + sessionIdGenerator: undefined, // Stateless mode + enableJsonResponse: true }) + // Connect the MCP server to the transport await this.server.connect(httpTransport) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 10c81d68..2c82159d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -521,6 +521,9 @@ importers: '@mcp-ui/server': specifier: ^5.11.0 version: 5.11.0(patch_hash=5d24b4104a7d6d537b7de1dc183b27e42d9c1b3fde96a28dbeb722713ba85f11) + '@modelcontextprotocol/sdk': + specifier: ^1.0.0 + version: 1.20.0 cors: specifier: ^2.8.5 version: 2.8.5 @@ -530,6 +533,9 @@ importers: mcp-use: specifier: workspace:* version: link:../packages/mcp-use + zod: + specifier: ^3.22.0 + version: 3.25.76 devDependencies: '@mcp-use/cli': specifier: workspace:* From 169967e2c75989097a90796b434f266df8ea2be5 Mon Sep 17 00:00:00 2001 From: Enrico Toniato <2827496+tonxxd@users.noreply.github.com> Date: Tue, 14 Oct 2025 16:18:28 +0200 Subject: [PATCH 2/2] fix: 1 transport per request --- packages/mcp-use/src/server/mcp-server.ts | 56 +++++++++++++++-------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/packages/mcp-use/src/server/mcp-server.ts b/packages/mcp-use/src/server/mcp-server.ts index 9ca59382..7ef56dd9 100644 --- a/packages/mcp-use/src/server/mcp-server.ts +++ b/packages/mcp-use/src/server/mcp-server.ts @@ -296,7 +296,8 @@ export class McpServer { * * Sets up the HTTP transport layer for the MCP server, creating endpoints for * Server-Sent Events (SSE) streaming, POST message handling, and DELETE session cleanup. - * Uses stateless mode for session management, making it suitable for stateless deployments. + * Each request gets its own transport instance to prevent state conflicts between + * concurrent client connections. * * This method is called automatically when the server starts listening and ensures * that MCP clients can communicate with the server over HTTP. @@ -314,34 +315,53 @@ export class McpServer { if (this.mcpMounted) return const { StreamableHTTPServerTransport } = await import('@modelcontextprotocol/sdk/server/streamableHttp.js') - - // Create StreamableHTTPServerTransport in stateless mode - // Note: Stateless mode means one active connection at a time - // For multiple concurrent inspectors, use the built-in proxy or same-origin connections - const httpTransport = new StreamableHTTPServerTransport({ - sessionIdGenerator: undefined, // Stateless mode - enableJsonResponse: true - }) + const endpoint = '/mcp' + + // POST endpoint for messages + // Create a new transport for each request to support multiple concurrent clients + this.app.post(endpoint, express.json(), async (req, res) => { + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: undefined, + enableJsonResponse: true + }) - // Connect the MCP server to the transport - await this.server.connect(httpTransport) + res.on('close', () => { + transport.close() + }) - const endpoint = '/mcp' + await this.server.connect(transport) + await transport.handleRequest(req, res, req.body) + }) // GET endpoint for SSE streaming this.app.get(endpoint, async (req, res) => { - await httpTransport.handleRequest(req, res) - }) + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: undefined, + enableJsonResponse: true + }) - // POST endpoint for messages - this.app.post(endpoint, express.json(), async (req, res) => { - await httpTransport.handleRequest(req, res, req.body) + res.on('close', () => { + transport.close() + }) + + await this.server.connect(transport) + await transport.handleRequest(req, res) }) // DELETE endpoint for session cleanup this.app.delete(endpoint, async (req, res) => { - await httpTransport.handleRequest(req, res) + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: undefined, + enableJsonResponse: true + }) + + res.on('close', () => { + transport.close() + }) + + await this.server.connect(transport) + await transport.handleRequest(req, res) }) this.mcpMounted = true