mcp-proxy 2.12.1 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/InMemoryEventStore.ts","../src/proxyServer.ts","../src/startHTTPStreamServer.ts","../src/startSSEServer.ts"],"sourcesContent":["/**\n * This is a copy of the InMemoryEventStore from the typescript-sdk\n * https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/inMemoryEventStore.ts\n */\n\nimport type { JSONRPCMessage } from \"@modelcontextprotocol/sdk/types.js\";\nimport type { EventStore } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\n\n/**\n * Simple in-memory implementation of the EventStore interface for resumability\n * This is primarily intended for examples and testing, not for production use\n * where a persistent storage solution would be more appropriate.\n */\nexport class InMemoryEventStore implements EventStore {\n private events: Map<string, { streamId: string; message: JSONRPCMessage }> =\n new Map();\n\n /**\n * Generates a unique event ID for a given stream ID\n */\n private generateEventId(streamId: string): string {\n return `${streamId}_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;\n }\n\n /**\n * Extracts the stream ID from an event ID\n */\n private getStreamIdFromEventId(eventId: string): string {\n const parts = eventId.split(\"_\");\n return parts.length > 0 ? parts[0] : \"\";\n }\n\n /**\n * Stores an event with a generated event ID\n * Implements EventStore.storeEvent\n */\n async storeEvent(streamId: string, message: JSONRPCMessage): Promise<string> {\n const eventId = this.generateEventId(streamId);\n this.events.set(eventId, { streamId, message });\n return eventId;\n }\n\n /**\n * Replays events that occurred after a specific event ID\n * Implements EventStore.replayEventsAfter\n */\n async replayEventsAfter(\n lastEventId: string,\n {\n send,\n }: { send: (eventId: string, message: JSONRPCMessage) => Promise<void> }\n ): Promise<string> {\n if (!lastEventId || !this.events.has(lastEventId)) {\n return \"\";\n }\n\n // Extract the stream ID from the event ID\n const streamId = this.getStreamIdFromEventId(lastEventId);\n if (!streamId) {\n return \"\";\n }\n\n let foundLastEvent = false;\n\n // Sort events by eventId for chronological ordering\n const sortedEvents = [...this.events.entries()].sort((a, b) =>\n a[0].localeCompare(b[0])\n );\n\n for (const [\n eventId,\n { streamId: eventStreamId, message },\n ] of sortedEvents) {\n // Only include events from the same stream\n if (eventStreamId !== streamId) {\n continue;\n }\n\n // Start sending events after we find the lastEventId\n if (eventId === lastEventId) {\n foundLastEvent = true;\n continue;\n }\n\n if (foundLastEvent) {\n await send(eventId, message);\n }\n }\n return streamId;\n }\n}\n","import { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport {\n CallToolRequestSchema,\n CompleteRequestSchema,\n GetPromptRequestSchema,\n ListPromptsRequestSchema,\n ListResourcesRequestSchema,\n ListResourceTemplatesRequestSchema,\n ListToolsRequestSchema,\n LoggingMessageNotificationSchema,\n ReadResourceRequestSchema,\n SubscribeRequestSchema,\n UnsubscribeRequestSchema,\n ResourceUpdatedNotificationSchema,\n ServerCapabilities,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\n\nexport const proxyServer = async ({\n server,\n client,\n serverCapabilities,\n}: {\n server: Server;\n client: Client;\n serverCapabilities: ServerCapabilities;\n}) => {\n if (serverCapabilities?.logging) {\n server.setNotificationHandler(\n LoggingMessageNotificationSchema,\n async (args) => {\n return client.notification(args);\n },\n );\n }\n\n if (serverCapabilities?.prompts) {\n server.setRequestHandler(GetPromptRequestSchema, async (args) => {\n return client.getPrompt(args.params);\n });\n\n server.setRequestHandler(ListPromptsRequestSchema, async (args) => {\n return client.listPrompts(args.params);\n });\n }\n\n if (serverCapabilities?.resources) {\n server.setRequestHandler(ListResourcesRequestSchema, async (args) => {\n return client.listResources(args.params);\n });\n\n server.setRequestHandler(\n ListResourceTemplatesRequestSchema,\n async (args) => {\n return client.listResourceTemplates(args.params);\n },\n );\n\n server.setRequestHandler(ReadResourceRequestSchema, async (args) => {\n return client.readResource(args.params);\n });\n\n if (serverCapabilities?.resources.subscribe) {\n server.setNotificationHandler(\n ResourceUpdatedNotificationSchema,\n async (args) => {\n return client.notification(args);\n },\n );\n\n server.setRequestHandler(SubscribeRequestSchema, async (args) => {\n return client.subscribeResource(args.params);\n });\n\n server.setRequestHandler(UnsubscribeRequestSchema, async (args) => {\n return client.unsubscribeResource(args.params);\n });\n }\n }\n\n if (serverCapabilities?.tools) {\n server.setRequestHandler(CallToolRequestSchema, async (args) => {\n return client.callTool(args.params);\n });\n\n server.setRequestHandler(ListToolsRequestSchema, async (args) => {\n return client.listTools(args.params);\n });\n }\n\n server.setRequestHandler(CompleteRequestSchema, async (args) => {\n return client.complete(args.params);\n });\n};\n","import {\n EventStore,\n StreamableHTTPServerTransport,\n} from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport { randomUUID } from \"node:crypto\";\nimport http from \"http\";\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { isInitializeRequest } from \"@modelcontextprotocol/sdk/types.js\";\nimport { InMemoryEventStore } from \"./InMemoryEventStore.js\";\n\nexport type SSEServer = {\n close: () => Promise<void>;\n};\n\ntype ServerLike = {\n connect: Server[\"connect\"];\n close: Server[\"close\"];\n};\n\nexport const startHTTPStreamServer = async <T extends ServerLike>({\n port,\n createServer,\n endpoint,\n eventStore,\n onConnect,\n onClose,\n onUnhandledRequest,\n}: {\n port: number;\n endpoint: string;\n createServer: (request: http.IncomingMessage) => Promise<T>;\n eventStore?: EventStore;\n onConnect?: (server: T) => void;\n onClose?: (server: T) => void;\n onUnhandledRequest?: (\n req: http.IncomingMessage,\n res: http.ServerResponse\n ) => Promise<void>;\n}): Promise<SSEServer> => {\n const activeTransports: Record<\n string,\n {\n transport: StreamableHTTPServerTransport;\n server: T;\n }\n > = {};\n\n /**\n * @author https://dev.classmethod.jp/articles/mcp-sse/\n */\n const httpServer = http.createServer(async (req, res) => {\n if (req.headers.origin) {\n try {\n const origin = new URL(req.headers.origin);\n\n res.setHeader(\"Access-Control-Allow-Origin\", origin.origin);\n res.setHeader(\"Access-Control-Allow-Credentials\", \"true\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"*\");\n } catch (error) {\n console.error(\"Error parsing origin:\", error);\n }\n }\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (req.method === \"GET\" && req.url === `/ping`) {\n res.writeHead(200).end(\"pong\");\n return;\n }\n\n if (\n req.method === \"POST\" &&\n new URL(req.url!, \"http://localhost\").pathname === endpoint\n ) {\n try {\n const sessionId = Array.isArray(req.headers[\"mcp-session-id\"])\n ? req.headers[\"mcp-session-id\"][0]\n : req.headers[\"mcp-session-id\"];\n let transport: StreamableHTTPServerTransport;\n let server: T;\n\n const body = await getBody(req);\n\n if (sessionId && activeTransports[sessionId]) {\n transport = activeTransports[sessionId].transport;\n server = activeTransports[sessionId].server;\n } else if (!sessionId && isInitializeRequest(body)) {\n // Create a new transport for the session\n transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: randomUUID,\n eventStore: eventStore || new InMemoryEventStore(),\n onsessioninitialized: (_sessionId) => {\n // add only when the id Sesison id is generated\n activeTransports[_sessionId] = {\n transport,\n server,\n };\n },\n });\n\n // Handle the server close event\n transport.onclose = async () => {\n const sid = transport.sessionId;\n if (sid && activeTransports[sid]) {\n onClose?.(server);\n try {\n await server.close();\n } catch (error) {\n console.error(\"Error closing server:\", error);\n }\n delete activeTransports[sid];\n }\n };\n\n // Create the server\n try {\n server = await createServer(req);\n } catch (error) {\n if (error instanceof Response) {\n res.writeHead(error.status).end(error.statusText);\n return;\n }\n res.writeHead(500).end(\"Error creating server\");\n return;\n }\n\n server.connect(transport);\n onConnect?.(server);\n\n await transport.handleRequest(req, res, body);\n return;\n } else {\n // Error if the server is not created but the request is not an initialize request\n res.setHeader(\"Content-Type\", \"application/json\");\n res.writeHead(400).end(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: -32000,\n message: \"Bad Request: No valid session ID provided\",\n },\n id: null,\n })\n );\n\n return;\n }\n\n // Handle ther request if the server is already created\n await transport.handleRequest(req, res, body);\n } catch (error) {\n console.error(\"Error handling request:\", error);\n res.setHeader(\"Content-Type\", \"application/json\");\n res.writeHead(500).end(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: { code: -32603, message: \"Internal Server Error\" },\n id: null,\n })\n );\n }\n return;\n }\n\n if (\n req.method === \"GET\" &&\n new URL(req.url!, \"http://localhost\").pathname === endpoint\n ) {\n const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n const activeTransport:\n | {\n transport: StreamableHTTPServerTransport;\n server: T;\n }\n | undefined = sessionId ? activeTransports[sessionId] : undefined;\n\n if (!sessionId) {\n res.writeHead(400).end(\"No sessionId\");\n return;\n }\n\n if (!activeTransport) {\n res.writeHead(400).end(\"No active transport\");\n return;\n }\n\n const lastEventId = req.headers[\"last-event-id\"] as string | undefined;\n if (lastEventId) {\n console.log(`Client reconnecting with Last-Event-ID: ${lastEventId}`);\n } else {\n console.log(`Establishing new SSE stream for session ${sessionId}`);\n }\n\n await activeTransport.transport.handleRequest(req, res);\n return;\n }\n\n if (\n req.method === \"DELETE\" &&\n new URL(req.url!, \"http://localhost\").pathname === endpoint\n ) {\n console.log(\"received delete request\");\n const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n if (!sessionId) {\n res.writeHead(400).end(\"Invalid or missing sessionId\");\n return;\n }\n\n console.log(\"received delete request for session\", sessionId);\n\n const { transport, server } = activeTransports[sessionId];\n if (!transport) {\n res.writeHead(400).end(\"No active transport\");\n return;\n }\n\n try {\n await transport.handleRequest(req, res);\n onClose?.(server);\n } catch (error) {\n console.error(\"Error handling delete request:\", error);\n res.writeHead(500).end(\"Error handling delete request\");\n }\n\n return;\n }\n\n if (onUnhandledRequest) {\n await onUnhandledRequest(req, res);\n } else {\n res.writeHead(404).end();\n }\n });\n\n await new Promise((resolve) => {\n httpServer.listen(port, \"::\", () => {\n resolve(undefined);\n });\n });\n\n return {\n close: async () => {\n for (const transport of Object.values(activeTransports)) {\n await transport.transport.close();\n }\n\n return new Promise((resolve, reject) => {\n httpServer.close((error) => {\n if (error) {\n reject(error);\n\n return;\n }\n\n resolve();\n });\n });\n },\n };\n};\n\nfunction getBody(request: http.IncomingMessage) {\n return new Promise((resolve) => {\n const bodyParts: Buffer[] = [];\n let body: string;\n request\n .on(\"data\", (chunk) => {\n bodyParts.push(chunk);\n })\n .on(\"end\", () => {\n body = Buffer.concat(bodyParts).toString();\n resolve(JSON.parse(body));\n });\n });\n}\n","import { SSEServerTransport } from \"@modelcontextprotocol/sdk/server/sse.js\";\nimport http from \"http\";\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\n\nexport type SSEServer = {\n close: () => Promise<void>;\n};\n\ntype ServerLike = {\n connect: Server[\"connect\"];\n close: Server[\"close\"];\n};\n\nexport const startSSEServer = async <T extends ServerLike>({\n port,\n createServer,\n endpoint,\n onConnect,\n onClose,\n onUnhandledRequest,\n}: {\n port: number;\n endpoint: string;\n createServer: (request: http.IncomingMessage) => Promise<T>;\n onConnect?: (server: T) => void;\n onClose?: (server: T) => void;\n onUnhandledRequest?: (\n req: http.IncomingMessage,\n res: http.ServerResponse,\n ) => Promise<void>;\n}): Promise<SSEServer> => {\n const activeTransports: Record<string, SSEServerTransport> = {};\n\n /**\n * @author https://dev.classmethod.jp/articles/mcp-sse/\n */\n const httpServer = http.createServer(async (req, res) => {\n if (req.headers.origin) {\n try {\n const origin = new URL(req.headers.origin);\n\n res.setHeader(\"Access-Control-Allow-Origin\", origin.origin);\n res.setHeader(\"Access-Control-Allow-Credentials\", \"true\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"*\");\n } catch (error) {\n console.error(\"Error parsing origin:\", error);\n }\n }\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (req.method === \"GET\" && req.url === `/ping`) {\n res.writeHead(200).end(\"pong\");\n\n return;\n }\n\n if (req.method === \"GET\" && new URL(req.url!, \"http://localhost\").pathname === endpoint) {\n const transport = new SSEServerTransport(\"/messages\", res);\n\n let server: T;\n\n try {\n server = await createServer(req);\n } catch (error) {\n if (error instanceof Response) {\n res.writeHead(error.status).end(error.statusText);\n\n return;\n }\n\n res.writeHead(500).end(\"Error creating server\");\n\n return;\n }\n\n activeTransports[transport.sessionId] = transport;\n\n let closed = false;\n\n res.on(\"close\", async () => {\n closed = true;\n\n try {\n await server.close();\n } catch (error) {\n console.error(\"Error closing server:\", error);\n }\n\n delete activeTransports[transport.sessionId];\n\n onClose?.(server);\n });\n\n try {\n await server.connect(transport);\n\n await transport.send({\n jsonrpc: \"2.0\",\n method: \"sse/connection\",\n params: { message: \"SSE Connection established\" },\n });\n\n onConnect?.(server);\n } catch (error) {\n if (!closed) {\n console.error(\"Error connecting to server:\", error);\n\n res.writeHead(500).end(\"Error connecting to server\");\n }\n }\n\n return;\n }\n\n if (req.method === \"POST\" && req.url?.startsWith(\"/messages\")) {\n const sessionId = new URL(\n req.url,\n \"https://example.com\",\n ).searchParams.get(\"sessionId\");\n\n if (!sessionId) {\n res.writeHead(400).end(\"No sessionId\");\n\n return;\n }\n\n const activeTransport: SSEServerTransport | undefined =\n activeTransports[sessionId];\n\n if (!activeTransport) {\n res.writeHead(400).end(\"No active transport\");\n\n return;\n }\n\n await activeTransport.handlePostMessage(req, res);\n\n return;\n }\n\n if (onUnhandledRequest) {\n await onUnhandledRequest(req, res);\n } else {\n res.writeHead(404).end();\n }\n });\n\n await new Promise((resolve) => {\n httpServer.listen(port, \"::\", () => {\n resolve(undefined);\n });\n });\n\n return {\n close: async () => {\n for (const transport of Object.values(activeTransports)) {\n await transport.close();\n }\n\n return new Promise((resolve, reject) => {\n httpServer.close((error) => {\n if (error) {\n reject(error);\n\n return;\n }\n\n resolve();\n });\n });\n },\n };\n};\n"],"mappings":"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","names":["http"]}
package/dist/index.d.ts CHANGED
@@ -1,26 +1,43 @@
1
- import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
2
1
  import { JSONRPCMessage, ServerCapabilities } from '@modelcontextprotocol/sdk/types.js';
2
+ import { EventStore } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
3
3
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
4
4
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
5
5
  import http from 'http';
6
+ import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
6
7
 
7
- type TransportEvent = {
8
- type: "close";
9
- } | {
10
- type: "onclose";
11
- } | {
12
- type: "onerror";
13
- error: Error;
14
- } | {
15
- type: "onmessage";
16
- message: JSONRPCMessage;
17
- } | {
18
- type: "send";
19
- message: JSONRPCMessage;
20
- } | {
21
- type: "start";
22
- };
23
- declare const tapTransport: (transport: Transport, eventHandler: (event: TransportEvent) => void) => Transport;
8
+ /**
9
+ * This is a copy of the InMemoryEventStore from the typescript-sdk
10
+ * https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/inMemoryEventStore.ts
11
+ */
12
+
13
+ /**
14
+ * Simple in-memory implementation of the EventStore interface for resumability
15
+ * This is primarily intended for examples and testing, not for production use
16
+ * where a persistent storage solution would be more appropriate.
17
+ */
18
+ declare class InMemoryEventStore implements EventStore {
19
+ private events;
20
+ /**
21
+ * Generates a unique event ID for a given stream ID
22
+ */
23
+ private generateEventId;
24
+ /**
25
+ * Extracts the stream ID from an event ID
26
+ */
27
+ private getStreamIdFromEventId;
28
+ /**
29
+ * Stores an event with a generated event ID
30
+ * Implements EventStore.storeEvent
31
+ */
32
+ storeEvent(streamId: string, message: JSONRPCMessage): Promise<string>;
33
+ /**
34
+ * Replays events that occurred after a specific event ID
35
+ * Implements EventStore.replayEventsAfter
36
+ */
37
+ replayEventsAfter(lastEventId: string, { send, }: {
38
+ send: (eventId: string, message: JSONRPCMessage) => Promise<void>;
39
+ }): Promise<string>;
40
+ }
24
41
 
25
42
  declare const proxyServer: ({ server, client, serverCapabilities, }: {
26
43
  server: Server;
@@ -28,6 +45,23 @@ declare const proxyServer: ({ server, client, serverCapabilities, }: {
28
45
  serverCapabilities: ServerCapabilities;
29
46
  }) => Promise<void>;
30
47
 
48
+ type SSEServer$1 = {
49
+ close: () => Promise<void>;
50
+ };
51
+ type ServerLike$1 = {
52
+ connect: Server["connect"];
53
+ close: Server["close"];
54
+ };
55
+ declare const startHTTPStreamServer: <T extends ServerLike$1>({ port, createServer, endpoint, eventStore, onConnect, onClose, onUnhandledRequest, }: {
56
+ port: number;
57
+ endpoint: string;
58
+ createServer: (request: http.IncomingMessage) => Promise<T>;
59
+ eventStore?: EventStore;
60
+ onConnect?: (server: T) => void;
61
+ onClose?: (server: T) => void;
62
+ onUnhandledRequest?: (req: http.IncomingMessage, res: http.ServerResponse) => Promise<void>;
63
+ }) => Promise<SSEServer$1>;
64
+
31
65
  type SSEServer = {
32
66
  close: () => Promise<void>;
33
67
  };
@@ -44,4 +78,22 @@ declare const startSSEServer: <T extends ServerLike>({ port, createServer, endpo
44
78
  onUnhandledRequest?: (req: http.IncomingMessage, res: http.ServerResponse) => Promise<void>;
45
79
  }) => Promise<SSEServer>;
46
80
 
47
- export { proxyServer, startSSEServer, tapTransport };
81
+ type TransportEvent = {
82
+ type: "close";
83
+ } | {
84
+ type: "onclose";
85
+ } | {
86
+ type: "onerror";
87
+ error: Error;
88
+ } | {
89
+ type: "onmessage";
90
+ message: JSONRPCMessage;
91
+ } | {
92
+ type: "send";
93
+ message: JSONRPCMessage;
94
+ } | {
95
+ type: "start";
96
+ };
97
+ declare const tapTransport: (transport: Transport, eventHandler: (event: TransportEvent) => void) => Transport;
98
+
99
+ export { InMemoryEventStore, proxyServer, startHTTPStreamServer, startSSEServer, tapTransport };
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import {
2
+ InMemoryEventStore,
2
3
  proxyServer,
4
+ startHTTPStreamServer,
3
5
  startSSEServer
4
- } from "./chunk-HTMOSQ6H.js";
6
+ } from "./chunk-BLDWVJ5G.js";
5
7
 
6
8
  // src/tapTransport.ts
7
9
  var tapTransport = (transport, eventHandler) => {
@@ -53,7 +55,9 @@ var tapTransport = (transport, eventHandler) => {
53
55
  return transport;
54
56
  };
55
57
  export {
58
+ InMemoryEventStore,
56
59
  proxyServer,
60
+ startHTTPStreamServer,
57
61
  startSSEServer,
58
62
  tapTransport
59
63
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/tapTransport.ts"],"sourcesContent":["import { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport { JSONRPCMessage } from \"@modelcontextprotocol/sdk/types.js\";\n\ntype TransportEvent =\n | {\n type: \"close\";\n }\n | {\n type: \"onclose\";\n }\n | {\n type: \"onerror\";\n error: Error;\n }\n | {\n type: \"onmessage\";\n message: JSONRPCMessage;\n }\n | {\n type: \"send\";\n message: JSONRPCMessage;\n }\n | {\n type: \"start\";\n };\n\nexport const tapTransport = (\n transport: Transport,\n eventHandler: (event: TransportEvent) => void,\n) => {\n const originalClose = transport.close.bind(transport);\n const originalOnClose = transport.onclose?.bind(transport);\n const originalOnError = transport.onerror?.bind(transport);\n const originalOnMessage = transport.onmessage?.bind(transport);\n const originalSend = transport.send.bind(transport);\n const originalStart = transport.start.bind(transport);\n\n transport.close = async () => {\n eventHandler({\n type: \"close\",\n });\n\n return originalClose?.();\n };\n\n transport.onclose = async () => {\n eventHandler({\n type: \"onclose\",\n });\n\n return originalOnClose?.();\n };\n\n transport.onerror = async (error: Error) => {\n eventHandler({\n type: \"onerror\",\n error,\n });\n\n return originalOnError?.(error);\n };\n\n transport.onmessage = async (message: JSONRPCMessage) => {\n eventHandler({\n type: \"onmessage\",\n message,\n });\n\n return originalOnMessage?.(message);\n };\n\n transport.send = async (message: JSONRPCMessage) => {\n eventHandler({\n type: \"send\",\n message,\n });\n\n return originalSend?.(message);\n };\n\n transport.start = async () => {\n eventHandler({\n type: \"start\",\n });\n\n return originalStart?.();\n };\n\n return transport;\n};\n"],"mappings":";;;;;;AA0BO,IAAM,eAAe,CAC1B,WACA,iBACG;AACH,QAAM,gBAAgB,UAAU,MAAM,KAAK,SAAS;AACpD,QAAM,kBAAkB,UAAU,SAAS,KAAK,SAAS;AACzD,QAAM,kBAAkB,UAAU,SAAS,KAAK,SAAS;AACzD,QAAM,oBAAoB,UAAU,WAAW,KAAK,SAAS;AAC7D,QAAM,eAAe,UAAU,KAAK,KAAK,SAAS;AAClD,QAAM,gBAAgB,UAAU,MAAM,KAAK,SAAS;AAEpD,YAAU,QAAQ,YAAY;AAC5B,iBAAa;AAAA,MACX,MAAM;AAAA,IACR,CAAC;AAED,WAAO,gBAAgB;AAAA,EACzB;AAEA,YAAU,UAAU,YAAY;AAC9B,iBAAa;AAAA,MACX,MAAM;AAAA,IACR,CAAC;AAED,WAAO,kBAAkB;AAAA,EAC3B;AAEA,YAAU,UAAU,OAAO,UAAiB;AAC1C,iBAAa;AAAA,MACX,MAAM;AAAA,MACN;AAAA,IACF,CAAC;AAED,WAAO,kBAAkB,KAAK;AAAA,EAChC;AAEA,YAAU,YAAY,OAAO,YAA4B;AACvD,iBAAa;AAAA,MACX,MAAM;AAAA,MACN;AAAA,IACF,CAAC;AAED,WAAO,oBAAoB,OAAO;AAAA,EACpC;AAEA,YAAU,OAAO,OAAO,YAA4B;AAClD,iBAAa;AAAA,MACX,MAAM;AAAA,MACN;AAAA,IACF,CAAC;AAED,WAAO,eAAe,OAAO;AAAA,EAC/B;AAEA,YAAU,QAAQ,YAAY;AAC5B,iBAAa;AAAA,MACX,MAAM;AAAA,IACR,CAAC;AAED,WAAO,gBAAgB;AAAA,EACzB;AAEA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/tapTransport.ts"],"sourcesContent":["import { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport { JSONRPCMessage } from \"@modelcontextprotocol/sdk/types.js\";\n\ntype TransportEvent =\n | {\n type: \"close\";\n }\n | {\n type: \"onclose\";\n }\n | {\n type: \"onerror\";\n error: Error;\n }\n | {\n type: \"onmessage\";\n message: JSONRPCMessage;\n }\n | {\n type: \"send\";\n message: JSONRPCMessage;\n }\n | {\n type: \"start\";\n };\n\nexport const tapTransport = (\n transport: Transport,\n eventHandler: (event: TransportEvent) => void,\n) => {\n const originalClose = transport.close.bind(transport);\n const originalOnClose = transport.onclose?.bind(transport);\n const originalOnError = transport.onerror?.bind(transport);\n const originalOnMessage = transport.onmessage?.bind(transport);\n const originalSend = transport.send.bind(transport);\n const originalStart = transport.start.bind(transport);\n\n transport.close = async () => {\n eventHandler({\n type: \"close\",\n });\n\n return originalClose?.();\n };\n\n transport.onclose = async () => {\n eventHandler({\n type: \"onclose\",\n });\n\n return originalOnClose?.();\n };\n\n transport.onerror = async (error: Error) => {\n eventHandler({\n type: \"onerror\",\n error,\n });\n\n return originalOnError?.(error);\n };\n\n transport.onmessage = async (message: JSONRPCMessage) => {\n eventHandler({\n type: \"onmessage\",\n message,\n });\n\n return originalOnMessage?.(message);\n };\n\n transport.send = async (message: JSONRPCMessage) => {\n eventHandler({\n type: \"send\",\n message,\n });\n\n return originalSend?.(message);\n };\n\n transport.start = async () => {\n eventHandler({\n type: \"start\",\n });\n\n return originalStart?.();\n };\n\n return transport;\n};\n"],"mappings":";;;;;;;;AA0BO,IAAM,eAAe,CAC1B,WACA,iBACG;AACH,QAAM,gBAAgB,UAAU,MAAM,KAAK,SAAS;AACpD,QAAM,kBAAkB,UAAU,SAAS,KAAK,SAAS;AACzD,QAAM,kBAAkB,UAAU,SAAS,KAAK,SAAS;AACzD,QAAM,oBAAoB,UAAU,WAAW,KAAK,SAAS;AAC7D,QAAM,eAAe,UAAU,KAAK,KAAK,SAAS;AAClD,QAAM,gBAAgB,UAAU,MAAM,KAAK,SAAS;AAEpD,YAAU,QAAQ,YAAY;AAC5B,iBAAa;AAAA,MACX,MAAM;AAAA,IACR,CAAC;AAED,WAAO,gBAAgB;AAAA,EACzB;AAEA,YAAU,UAAU,YAAY;AAC9B,iBAAa;AAAA,MACX,MAAM;AAAA,IACR,CAAC;AAED,WAAO,kBAAkB;AAAA,EAC3B;AAEA,YAAU,UAAU,OAAO,UAAiB;AAC1C,iBAAa;AAAA,MACX,MAAM;AAAA,MACN;AAAA,IACF,CAAC;AAED,WAAO,kBAAkB,KAAK;AAAA,EAChC;AAEA,YAAU,YAAY,OAAO,YAA4B;AACvD,iBAAa;AAAA,MACX,MAAM;AAAA,MACN;AAAA,IACF,CAAC;AAED,WAAO,oBAAoB,OAAO;AAAA,EACpC;AAEA,YAAU,OAAO,OAAO,YAA4B;AAClD,iBAAa;AAAA,MACX,MAAM;AAAA,MACN;AAAA,IACF,CAAC;AAED,WAAO,eAAe,OAAO;AAAA,EAC/B;AAEA,YAAU,QAAQ,YAAY;AAC5B,iBAAa;AAAA,MACX,MAAM;AAAA,IACR,CAAC;AAED,WAAO,gBAAgB;AAAA,EACzB;AAEA,SAAO;AACT;","names":[]}
package/eslint.config.js CHANGED
@@ -1,3 +1,10 @@
1
1
  import perfectionist from "eslint-plugin-perfectionist";
2
2
 
3
- export default [perfectionist.configs["recommended-alphabetical"]];
3
+ export default [
4
+ perfectionist.configs["recommended-alphabetical"],
5
+ {
6
+ ignores: [
7
+ '**/dist/'
8
+ ],
9
+ },
10
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-proxy",
3
- "version": "2.12.1",
3
+ "version": "2.13.0",
4
4
  "main": "dist/index.js",
5
5
  "scripts": {
6
6
  "build": "tsup",
@@ -22,8 +22,8 @@
22
22
  "module": "dist/index.js",
23
23
  "types": "dist/index.d.ts",
24
24
  "dependencies": {
25
- "@modelcontextprotocol/sdk": "^1.6.0",
26
- "eventsource": "^3.0.5",
25
+ "@modelcontextprotocol/sdk": "^1.10.1",
26
+ "eventsource": "^3.0.6",
27
27
  "yargs": "^17.7.2"
28
28
  },
29
29
  "repository": {
@@ -42,19 +42,19 @@
42
42
  ]
43
43
  },
44
44
  "devDependencies": {
45
- "@sebbo2002/semantic-release-jsr": "^2.0.4",
46
- "@tsconfig/node22": "^22.0.0",
47
- "@types/node": "^22.13.5",
45
+ "@sebbo2002/semantic-release-jsr": "^2.0.5",
46
+ "@tsconfig/node22": "^22.0.1",
47
+ "@types/node": "^22.14.1",
48
48
  "@types/yargs": "^17.0.33",
49
- "eslint": "^9.21.0",
50
- "eslint-plugin-perfectionist": "^4.9.0",
49
+ "eslint": "^9.25.0",
50
+ "eslint-plugin-perfectionist": "^4.11.0",
51
51
  "get-port-please": "^3.1.2",
52
- "prettier": "^3.5.2",
52
+ "prettier": "^3.5.3",
53
53
  "semantic-release": "^24.2.3",
54
- "tsup": "^8.3.6",
54
+ "tsup": "^8.4.0",
55
55
  "tsx": "^4.19.3",
56
- "typescript": "^5.7.3",
57
- "vitest": "^3.0.6"
56
+ "typescript": "^5.8.3",
57
+ "vitest": "^3.1.1"
58
58
  },
59
59
  "tsup": {
60
60
  "entry": [
@@ -0,0 +1,91 @@
1
+ /**
2
+ * This is a copy of the InMemoryEventStore from the typescript-sdk
3
+ * https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/inMemoryEventStore.ts
4
+ */
5
+
6
+ import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
7
+ import type { EventStore } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
8
+
9
+ /**
10
+ * Simple in-memory implementation of the EventStore interface for resumability
11
+ * This is primarily intended for examples and testing, not for production use
12
+ * where a persistent storage solution would be more appropriate.
13
+ */
14
+ export class InMemoryEventStore implements EventStore {
15
+ private events: Map<string, { streamId: string; message: JSONRPCMessage }> =
16
+ new Map();
17
+
18
+ /**
19
+ * Generates a unique event ID for a given stream ID
20
+ */
21
+ private generateEventId(streamId: string): string {
22
+ return `${streamId}_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
23
+ }
24
+
25
+ /**
26
+ * Extracts the stream ID from an event ID
27
+ */
28
+ private getStreamIdFromEventId(eventId: string): string {
29
+ const parts = eventId.split("_");
30
+ return parts.length > 0 ? parts[0] : "";
31
+ }
32
+
33
+ /**
34
+ * Stores an event with a generated event ID
35
+ * Implements EventStore.storeEvent
36
+ */
37
+ async storeEvent(streamId: string, message: JSONRPCMessage): Promise<string> {
38
+ const eventId = this.generateEventId(streamId);
39
+ this.events.set(eventId, { streamId, message });
40
+ return eventId;
41
+ }
42
+
43
+ /**
44
+ * Replays events that occurred after a specific event ID
45
+ * Implements EventStore.replayEventsAfter
46
+ */
47
+ async replayEventsAfter(
48
+ lastEventId: string,
49
+ {
50
+ send,
51
+ }: { send: (eventId: string, message: JSONRPCMessage) => Promise<void> }
52
+ ): Promise<string> {
53
+ if (!lastEventId || !this.events.has(lastEventId)) {
54
+ return "";
55
+ }
56
+
57
+ // Extract the stream ID from the event ID
58
+ const streamId = this.getStreamIdFromEventId(lastEventId);
59
+ if (!streamId) {
60
+ return "";
61
+ }
62
+
63
+ let foundLastEvent = false;
64
+
65
+ // Sort events by eventId for chronological ordering
66
+ const sortedEvents = [...this.events.entries()].sort((a, b) =>
67
+ a[0].localeCompare(b[0])
68
+ );
69
+
70
+ for (const [
71
+ eventId,
72
+ { streamId: eventStreamId, message },
73
+ ] of sortedEvents) {
74
+ // Only include events from the same stream
75
+ if (eventStreamId !== streamId) {
76
+ continue;
77
+ }
78
+
79
+ // Start sending events after we find the lastEventId
80
+ if (eventId === lastEventId) {
81
+ foundLastEvent = true;
82
+ continue;
83
+ }
84
+
85
+ if (foundLastEvent) {
86
+ await send(eventId, message);
87
+ }
88
+ }
89
+ return streamId;
90
+ }
91
+ }
@@ -9,7 +9,9 @@ import { setTimeout } from "node:timers";
9
9
  import { StdioClientTransport } from "../StdioClientTransport.js";
10
10
  import util from "node:util";
11
11
  import { startSSEServer } from "../startSSEServer.js";
12
+ import { startHTTPStreamServer } from "../startHTTPStreamServer.js";
12
13
  import { proxyServer } from "../proxyServer.js";
14
+ import { InMemoryEventStore } from "../InMemoryEventStore.js";
13
15
 
14
16
  util.inspect.defaultOptions.depth = 8;
15
17
 
@@ -40,14 +42,19 @@ const argv = await yargs(hideBin(process.argv))
40
42
  },
41
43
  endpoint: {
42
44
  type: "string",
43
- describe: "The endpoint to listen on for SSE",
44
- default: "/sse",
45
+ describe: "The endpoint to listen on",
45
46
  },
46
47
  port: {
47
48
  type: "number",
48
- describe: "The port to listen on for SSE",
49
+ describe: "The port to listen on",
49
50
  default: 8080,
50
51
  },
52
+ server: {
53
+ type: "string",
54
+ describe: "The server type to use (sse or stream)",
55
+ choices: ["sse", "stream"],
56
+ default: "sse",
57
+ },
51
58
  })
52
59
  .help()
53
60
  .parseAsync();
@@ -76,7 +83,7 @@ const proxy = async () => {
76
83
  },
77
84
  {
78
85
  capabilities: {},
79
- },
86
+ }
80
87
  );
81
88
 
82
89
  await connect(client);
@@ -88,25 +95,36 @@ const proxy = async () => {
88
95
 
89
96
  const serverCapabilities = client.getServerCapabilities() as {};
90
97
 
91
- console.info("starting the SSE server on port %d", argv.port);
98
+ console.info("starting the %s server on port %d", argv.server, argv.port);
92
99
 
93
- await startSSEServer({
94
- createServer: async () => {
95
- const server = new Server(serverVersion, {
96
- capabilities: serverCapabilities,
97
- });
100
+ const createServer = async () => {
101
+ const server = new Server(serverVersion, {
102
+ capabilities: serverCapabilities,
103
+ });
98
104
 
99
- proxyServer({
100
- server,
101
- client,
102
- serverCapabilities,
103
- });
105
+ proxyServer({
106
+ server,
107
+ client,
108
+ serverCapabilities,
109
+ });
104
110
 
105
- return server;
106
- },
107
- port: argv.port,
108
- endpoint: argv.endpoint as `/${string}`,
109
- });
111
+ return server;
112
+ };
113
+
114
+ if (argv.server === "sse") {
115
+ await startSSEServer({
116
+ createServer,
117
+ port: argv.port,
118
+ endpoint: argv.endpoint || ("/sse" as `/${string}`),
119
+ });
120
+ } else {
121
+ await startHTTPStreamServer({
122
+ createServer,
123
+ port: argv.port,
124
+ endpoint: argv.endpoint || ("/stream" as `/${string}`),
125
+ eventStore: new InMemoryEventStore(),
126
+ });
127
+ }
110
128
  };
111
129
 
112
130
  const main = async () => {
package/src/index.ts CHANGED
@@ -1,3 +1,5 @@
1
- export { tapTransport } from "./tapTransport.js";
1
+ export { InMemoryEventStore } from "./InMemoryEventStore.js";
2
2
  export { proxyServer } from "./proxyServer.js";
3
+ export { startHTTPStreamServer } from "./startHTTPStreamServer.js";
3
4
  export { startSSEServer } from "./startSSEServer.js";
5
+ export { tapTransport } from "./tapTransport.js";
@@ -0,0 +1,127 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
+ import { it, expect, vi } from "vitest";
5
+ import { startHTTPStreamServer } from "./startHTTPStreamServer.js";
6
+ import { getRandomPort } from "get-port-please";
7
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
8
+ import { EventSource } from "eventsource";
9
+ import { setTimeout as delay } from "node:timers/promises";
10
+ import { proxyServer } from "./proxyServer.js";
11
+
12
+ if (!("EventSource" in global)) {
13
+ // @ts-expect-error - figure out how to use --experimental-eventsource with vitest
14
+ global.EventSource = EventSource;
15
+ }
16
+
17
+ it("proxies messages between HTTP stream and stdio servers", async () => {
18
+ const stdioTransport = new StdioClientTransport({
19
+ command: "tsx",
20
+ args: ["src/simple-stdio-server.ts"],
21
+ });
22
+
23
+ const stdioClient = new Client(
24
+ {
25
+ name: "mcp-proxy",
26
+ version: "1.0.0",
27
+ },
28
+ {
29
+ capabilities: {},
30
+ }
31
+ );
32
+
33
+ await stdioClient.connect(stdioTransport);
34
+
35
+ const serverVersion = stdioClient.getServerVersion() as {
36
+ name: string;
37
+ version: string;
38
+ };
39
+
40
+ const serverCapabilities = stdioClient.getServerCapabilities() as {};
41
+
42
+ const port = await getRandomPort();
43
+
44
+ const onConnect = vi.fn();
45
+ const onClose = vi.fn();
46
+
47
+ await startHTTPStreamServer({
48
+ createServer: async () => {
49
+ const mcpServer = new Server(serverVersion, {
50
+ capabilities: serverCapabilities,
51
+ });
52
+
53
+ await proxyServer({
54
+ server: mcpServer,
55
+ client: stdioClient,
56
+ serverCapabilities,
57
+ });
58
+
59
+ return mcpServer;
60
+ },
61
+ port,
62
+ endpoint: "/stream",
63
+ onConnect,
64
+ onClose,
65
+ });
66
+
67
+ const streamClient = new Client(
68
+ {
69
+ name: "stream-client",
70
+ version: "1.0.0",
71
+ },
72
+ {
73
+ capabilities: {},
74
+ }
75
+ );
76
+
77
+ const transport = new StreamableHTTPClientTransport(
78
+ new URL(`http://localhost:${port}/stream`)
79
+ );
80
+
81
+ await streamClient.connect(transport);
82
+
83
+ const result = await streamClient.listResources();
84
+ expect(result).toEqual({
85
+ resources: [
86
+ {
87
+ uri: "file:///example.txt",
88
+ name: "Example Resource",
89
+ },
90
+ ],
91
+ });
92
+
93
+ expect(
94
+ await streamClient.readResource({ uri: result.resources[0].uri }, {})
95
+ ).toEqual({
96
+ contents: [
97
+ {
98
+ uri: "file:///example.txt",
99
+ mimeType: "text/plain",
100
+ text: "This is the content of the example resource.",
101
+ },
102
+ ],
103
+ });
104
+ expect(await streamClient.subscribeResource({ uri: "xyz" })).toEqual({});
105
+ expect(await streamClient.unsubscribeResource({ uri: "xyz" })).toEqual({});
106
+ expect(await streamClient.listResourceTemplates()).toEqual({
107
+ resourceTemplates: [
108
+ {
109
+ uriTemplate: `file://{filename}`,
110
+ name: "Example resource template",
111
+ description: "Specify the filename to retrieve",
112
+ },
113
+ ],
114
+ });
115
+
116
+ expect(onConnect).toHaveBeenCalled();
117
+ expect(onClose).not.toHaveBeenCalled();
118
+
119
+ // the transport no requires the function terminateSession to be called but the client does not implement it
120
+ // so we need to call it manually
121
+ await transport.terminateSession();
122
+ await streamClient.close();
123
+
124
+ await delay(1000);
125
+
126
+ expect(onClose).toHaveBeenCalled();
127
+ });