diff --git a/package.json b/package.json index bc7be8c..f9cc78c 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "author": "", "license": "ISC", "dependencies": { - "@modelcontextprotocol/sdk": "^1.9.0", + "@modelcontextprotocol/sdk": "^1.10.2", "@radix-ui/react-accordion": "^1.2.3", "@radix-ui/react-slot": "^1.1.2", "@shadcn/ui": "^0.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 241923e..e1c5a23 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@modelcontextprotocol/sdk': - specifier: ^1.9.0 - version: 1.9.0 + specifier: ^1.10.2 + version: 1.10.2 '@radix-ui/react-accordion': specifier: ^1.2.3 version: 1.2.3(@types/react-dom@19.0.4(@types/react@19.0.12))(@types/react@19.0.12)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -867,8 +867,8 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@modelcontextprotocol/sdk@1.9.0': - resolution: {integrity: sha512-Jq2EUCQpe0iyO5FGpzVYDNFR6oR53AIrwph9yWl7uSc7IWUMsrmpmSaTGra5hQNunXpM+9oit85p924jWuHzUA==} + '@modelcontextprotocol/sdk@1.10.2': + resolution: {integrity: sha512-rb6AMp2DR4SN+kc6L1ta2NCpApyA9WYNx3CrTSZvGxq9wH71bRur+zRqPfg0vQ9mjywR7qZdX2RGHOPq3ss+tA==} engines: {node: '>=18'} '@next/env@15.2.4': @@ -4268,7 +4268,7 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@modelcontextprotocol/sdk@1.9.0': + '@modelcontextprotocol/sdk@1.10.2': dependencies: content-type: 1.0.5 cors: 2.8.5 diff --git a/src/server.ts b/src/server.ts index 6ade189..af9bdc4 100644 --- a/src/server.ts +++ b/src/server.ts @@ -4,7 +4,12 @@ import path from 'path'; import { initMcpServer } from './services/mcpService.js'; import { initMiddlewares } from './middlewares/index.js'; import { initRoutes } from './routes/index.js'; -import { handleSseConnection, handleSseMessage } from './services/sseService.js'; +import { + handleSseConnection, + handleSseMessage, + handleMcpPostRequest, + handleMcpOtherRequest, +} from './services/sseService.js'; import { migrateUserData } from './utils/migration.js'; import { initializeDefaultUser } from './models/User.js'; @@ -34,6 +39,9 @@ export class AppServer { console.log('MCP server initialized successfully'); this.app.get('/sse/:group?', (req, res) => handleSseConnection(req, res)); this.app.post('/messages', handleSseMessage); + this.app.post('/mcp/:group?', handleMcpPostRequest); + this.app.get('/mcp/:group?', handleMcpOtherRequest); + this.app.delete('/mcp/:group?', handleMcpOtherRequest); }) .catch((error) => { console.error('Error initializing MCP server:', error); diff --git a/src/services/sseService.ts b/src/services/sseService.ts index aec8574..a887b44 100644 --- a/src/services/sseService.ts +++ b/src/services/sseService.ts @@ -1,9 +1,13 @@ import { Request, Response } from 'express'; +import { randomUUID } from 'node:crypto'; +import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; +import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; +import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js'; import { getMcpServer } from './mcpService.js'; import { loadSettings } from '../config/index.js'; -const transports: { [sessionId: string]: { transport: SSEServerTransport; group: string } } = {}; +const transports: { [sessionId: string]: { transport: Transport; group: string } } = {}; export const getGroup = (sessionId: string): string => { return transports[sessionId]?.group || ''; @@ -44,13 +48,72 @@ export const handleSseMessage = async (req: Request, res: Response): Promise => { + console.log('Handling MCP post request'); + const sessionId = req.headers['mcp-session-id'] as string | undefined; + const group = req.params.group; + const settings = loadSettings(); + const routingConfig = settings.systemConfig?.routing || { + enableGlobalRoute: true, + enableGroupNameRoute: true, + }; + if (!group && !routingConfig.enableGlobalRoute) { + res.status(403).send('Global routes are disabled. Please specify a group ID.'); + return; + } + + let transport: StreamableHTTPServerTransport; + if (sessionId && transports[sessionId]) { + transport = transports[sessionId].transport as StreamableHTTPServerTransport; + } else if (!sessionId && isInitializeRequest(req.body)) { + transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: () => randomUUID(), + onsessioninitialized: (sessionId) => { + transports[sessionId] = { transport, group }; + }, + }); + + transport.onclose = () => { + if (transport.sessionId) { + delete transports[transport.sessionId]; + } + }; + + await getMcpServer().connect(transport); + } else { + res.status(400).json({ + jsonrpc: '2.0', + error: { + code: -32000, + message: 'Bad Request: No valid session ID provided', + }, + id: null, + }); + return; + } + + await transport.handleRequest(req, res, req.body); +}; + +export const handleMcpOtherRequest = async (req: Request, res: Response) => { + console.log('Handling MCP other request'); + const sessionId = req.headers['mcp-session-id'] as string | undefined; + if (!sessionId || !transports[sessionId]) { + res.status(400).send('Invalid or missing session ID'); + return; + } + + const { transport } = transports[sessionId]; + await (transport as StreamableHTTPServerTransport).handleRequest(req, res); +}; + export const getConnectionCount = (): number => { return Object.keys(transports).length; };