redis-mcp-transport
Version:
Redis-based transport layer for Model Context Protocol with SSE support
1 lines • 8.15 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","sources":["../src/index.ts"],"sourcesContent":["import { IncomingMessage, ServerResponse } from \"node:http\";\nimport { createClient } from 'redis';\nimport { randomUUID } from \"node:crypto\";\nimport contentType from \"content-type\";\nimport getRawBody from \"raw-body\";\nimport { JSONRPCMessage, JSONRPCMessageSchema } from \"@modelcontextprotocol/sdk/types.js\";\nimport { Transport } from \"@modelcontextprotocol/sdk/shared/transport\";\n\n// 订阅端实现\nexport class RedisMcpTransport implements Transport {\n onmessage?: ((message: JSONRPCMessage) => void) | undefined; \n onerror?: ((error: Error) => void) | undefined;\n _endpoint: string;\n _redisClient: any;\n _isStarted: boolean;\n _sseResponse: ServerResponse | undefined;\n _sessionId: string;\n _subscriberClient: any;\n constructor(endpoint: string, resOrSessionId: ServerResponse | string, redisURL: string) {\n this._endpoint = endpoint;\n this._redisClient = createClient({ url: redisURL });\n this._redisClient.on('error', (err: any) => console.error('Redis Client Error', err));\n this._isStarted = false;\n if (typeof resOrSessionId === 'string') {\n this._sessionId = resOrSessionId as string;\n } else {\n this._sseResponse = resOrSessionId as ServerResponse;\n this._sessionId = randomUUID()\n }\n }\n\n async start(): Promise<any> {\n if (!this._isStarted) {\n await this._redisClient.connect();\n this._isStarted = true;\n }\n if (this._sseResponse) {\n // 创建单独的订阅客户端\n // 订阅客户端直接操作this._ssrResponse不走onmessage逻辑\n this._subscriberClient = this._redisClient.duplicate();\n await this._subscriberClient.connect();\n const channel = this.getChannelName();\n\n console.debug('Subscribing to channel:', channel);\n await this._subscriberClient.subscribe(channel, (message: string) => {\n try {\n const data = JSON.parse(message);\n this._sseResponse?.write(`event: message\\ndata: ${JSON.stringify(data)}\\n\\n`);\n } catch (error) {\n console.error('Error handling SSE message:', error);\n }\n });\n this._sseResponse.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n this._sseResponse.write(`event: endpoint\\ndata: ${encodeURI(this._endpoint)}?sessionId=${this._sessionId}\\n\\n`); \n }\n }\n\n /**\n * 实现与 SSEServerTransport 兼容的 handlePostMessage 方法\n * @param {Object} req - HTTP 请求对象(不使用,保持接口一致)\n * @param {Object} res - HTTP 响应对象(不使用,保持接口一致)\n * @returns {Promise<void>}\n */\n async handlePostMessage(req: IncomingMessage, res: ServerResponse): Promise<void> {\n // 发布客户端处理handlePostMessage,走onmessage逻辑会触发transport的send方法,始终往redis channel发布消息\n try {\n // @ts-ignore\n let message = req.body;\n if (!message) {\n const contentTypeHeader = req.headers['content-type'];\n const ct = contentType.parse(contentTypeHeader as string);\n if (ct.type !== 'application/json') {\n throw new Error('Unsupported content type');\n }\n let body = await getRawBody(req, { encoding: ct.parameters.charset || 'utf-8' });\n message = JSON.parse(body);\n }\n // const message: any = JSON.parse(body);\n const parsedMessage = JSONRPCMessageSchema.parse(message);\n this.onmessage?.(parsedMessage);\n } catch (error) {\n console.error('Error in handlePostMessage:', error);\n throw error;\n }\n }\n\n // 走一遍server的onmessage逻辑会触发transport的send方法\n async send(message: JSONRPCMessage): Promise<void> {\n if (!this._isStarted) {\n await this._redisClient.connect();\n this._isStarted = true;\n }\n const channel = this.getChannelName();\n await this._redisClient.publish(channel, JSON.stringify(message));\n await this.close();\n }\n\n async close() {\n if (this._subscriberClient) {\n const channel = this.getChannelName();\n await this._subscriberClient.unsubscribe(channel);\n await this._subscriberClient.disconnect();\n }\n if (this._isStarted) {\n await this._redisClient.disconnect();\n this._isStarted = false;\n }\n }\n\n getSessionId() {\n return this._sessionId;\n }\n getChannelName() {\n return `sse:channel:${this._sessionId}`;\n }\n}\n\nexport default RedisMcpTransport;\n"],"names":[],"mappings":"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"}