mcp-proxy
Version:
A TypeScript SSE proxy for MCP servers that use stdio transport.
230 lines (203 loc) • 5.99 kB
text/typescript
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();
});
});