UNPKG

mcp-proxy

Version:

A TypeScript SSE proxy for MCP servers that use stdio transport.

230 lines (203 loc) 5.99 kB
import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; import { CallToolResultSchema, LoggingMessageNotificationSchema, } from "@modelcontextprotocol/sdk/types.js"; import { EventSource } from "eventsource"; import { ChildProcess, fork } from "node:child_process"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { ServerType } from "./startStdioServer.js"; if (!("EventSource" in global)) { // @ts-expect-error - figure out how to use --experimental-eventsource with vitest global.EventSource = EventSource; } describe("startStdioServer.test.ts", () => { let proc: ChildProcess; beforeEach(async () => { const serverPath = require.resolve( "@modelcontextprotocol/sdk/examples/server/sseAndStreamableHttpCompatibleServer.js", ); proc = fork(serverPath, [], { stdio: "pipe", }); await new Promise((resolve) => { proc.stdout?.on("data", (data) => { console.log(data.toString()); data .toString() .includes("Backwards compatible MCP server listening on port"); resolve(null); }); }); }); afterEach(async () => { proc.kill(); }); it("proxies messages between stdio and sse servers", async () => { const stdioTransport = new StdioClientTransport({ args: [ "src/fixtures/simple-stdio-proxy-server.ts", JSON.stringify({ serverType: ServerType.SSE, url: "http://127.0.0.1:3000/sse", }), ], command: "tsx", }); const stdioClient = new Client( { name: "mcp-proxy", version: "1.0.0", }, { capabilities: {}, }, ); let notificationCount = 0; stdioClient.setNotificationHandler( LoggingMessageNotificationSchema, (notification) => { console.log( `Notification: ${notification.params.level} - ${notification.params.data}`, ); notificationCount++; }, ); await stdioClient.connect(stdioTransport); const result = await stdioClient.listTools(); expect(result).toEqual({ tools: [ { description: "Starts sending periodic notifications for testing resumability", inputSchema: { $schema: "http://json-schema.org/draft-07/schema#", additionalProperties: false, properties: { count: { default: 50, description: "Number of notifications to send (0 for 100)", type: "number", }, interval: { default: 100, description: "Interval in milliseconds between notifications", type: "number", }, }, type: "object", }, name: "start-notification-stream", }, ], }); const request = { method: "tools/call", params: { arguments: { count: 2, // Send 5 notifications interval: 1000, // 1 second between notifications }, name: "start-notification-stream", }, }; const notificationResult = await stdioClient.request( request, CallToolResultSchema, ); expect(notificationResult).toEqual({ content: [ { text: "Started sending periodic notifications every 1000ms", type: "text", }, ], }); expect(notificationCount).toEqual(2); await stdioClient.close(); }); it("proxies messages between stdio and stream able servers", async () => { const stdioTransport = new StdioClientTransport({ args: [ "src/fixtures/simple-stdio-proxy-server.ts", JSON.stringify({ serverType: ServerType.HTTPStream, url: "http://127.0.0.1:3000/mcp", }), ], command: "tsx", }); const stdioClient = new Client( { name: "mcp-proxy", version: "1.0.0", }, { capabilities: {}, }, ); let notificationCount = 0; stdioClient.setNotificationHandler( LoggingMessageNotificationSchema, (notification) => { console.log( `Notification: ${notification.params.level} - ${notification.params.data}`, ); notificationCount++; }, ); await stdioClient.connect(stdioTransport); const result = await stdioClient.listTools(); expect(result).toEqual({ tools: [ { description: "Starts sending periodic notifications for testing resumability", inputSchema: { $schema: "http://json-schema.org/draft-07/schema#", additionalProperties: false, properties: { count: { default: 50, description: "Number of notifications to send (0 for 100)", type: "number", }, interval: { default: 100, description: "Interval in milliseconds between notifications", type: "number", }, }, type: "object", }, name: "start-notification-stream", }, ], }); const request = { method: "tools/call", params: { arguments: { count: 2, // Send 5 notifications interval: 1000, // 1 second between notifications }, name: "start-notification-stream", }, }; const notificationResult = await stdioClient.request( request, CallToolResultSchema, ); expect(notificationResult).toEqual({ content: [ { text: "Started sending periodic notifications every 1000ms", type: "text", }, ], }); expect(notificationCount).toEqual(2); await stdioClient.close(); }); });