mcp-proxy 5.6.0 → 5.7.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.
package/jsr.json CHANGED
@@ -3,5 +3,5 @@
3
3
  "include": ["src/index.ts", "src/bin/mcp-proxy.ts"],
4
4
  "license": "MIT",
5
5
  "name": "@punkpeye/mcp-proxy",
6
- "version": "5.6.0"
6
+ "version": "5.7.0"
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-proxy",
3
- "version": "5.6.0",
3
+ "version": "5.7.0",
4
4
  "main": "dist/index.js",
5
5
  "scripts": {
6
6
  "build": "tsdown",
@@ -38,14 +38,14 @@
38
38
  ]
39
39
  },
40
40
  "devDependencies": {
41
- "@eslint/js": "^9.33.0",
42
- "@modelcontextprotocol/sdk": "^1.17.3",
41
+ "@eslint/js": "^9.36.0",
42
+ "@modelcontextprotocol/sdk": "^1.18.1",
43
43
  "@sebbo2002/semantic-release-jsr": "^3.0.1",
44
44
  "@tsconfig/node22": "^22.0.2",
45
45
  "@types/express": "^5.0.3",
46
- "@types/node": "^24.3.0",
46
+ "@types/node": "^24.5.2",
47
47
  "@types/yargs": "^17.0.33",
48
- "eslint": "^9.33.0",
48
+ "eslint": "^9.36.0",
49
49
  "eslint-config-prettier": "^10.1.8",
50
50
  "eslint-plugin-perfectionist": "^4.15.0",
51
51
  "eventsource": "^4.0.0",
@@ -54,11 +54,11 @@
54
54
  "jiti": "^2.5.1",
55
55
  "jsr": "^0.13.5",
56
56
  "prettier": "^3.6.2",
57
- "semantic-release": "^24.2.7",
58
- "tsdown": "^0.14.2",
59
- "tsx": "^4.20.4",
57
+ "semantic-release": "^24.2.8",
58
+ "tsdown": "^0.15.2",
59
+ "tsx": "^4.20.5",
60
60
  "typescript": "^5.9.2",
61
- "typescript-eslint": "^8.39.1",
61
+ "typescript-eslint": "^8.44.0",
62
62
  "vitest": "^3.2.4",
63
63
  "yargs": "^18.0.0"
64
64
  },
@@ -66,6 +66,11 @@ const argv = await yargs(hideBin(process.argv))
66
66
  describe: "The port to listen on",
67
67
  type: "number",
68
68
  },
69
+ requestTimeout: {
70
+ default: 300000,
71
+ describe: "The timeout (in milliseconds) for requests to the MCP server (default: 5 minutes)",
72
+ type: "number",
73
+ },
69
74
  server: {
70
75
  choices: ["sse", "stream"],
71
76
  describe:
@@ -156,6 +161,7 @@ const proxy = async () => {
156
161
 
157
162
  proxyServer({
158
163
  client,
164
+ requestTimeout: argv.requestTimeout,
159
165
  server,
160
166
  serverCapabilities,
161
167
  });
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * A test fixture that simulates a slow MCP server for testing timeout functionality.
4
+ * This server intentionally delays responses to test timeout behavior.
5
+ */
6
+
7
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
8
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
9
+ import { setTimeout as delay } from "node:timers/promises";
10
+
11
+ const server = new Server(
12
+ {
13
+ name: "slow-test-server",
14
+ version: "1.0.0",
15
+ },
16
+ {
17
+ capabilities: {
18
+ resources: {},
19
+ tools: {},
20
+ },
21
+ },
22
+ );
23
+
24
+ // Configure delay via environment variable or default to 2 seconds
25
+ const RESPONSE_DELAY = parseInt(process.env.RESPONSE_DELAY || "2000", 10);
26
+
27
+ import {
28
+ CallToolRequestSchema,
29
+ ListResourcesRequestSchema,
30
+ ListToolsRequestSchema,
31
+ ReadResourceRequestSchema,
32
+ } from "@modelcontextprotocol/sdk/types.js";
33
+
34
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
35
+ await delay(RESPONSE_DELAY);
36
+ return {
37
+ resources: [
38
+ {
39
+ name: "Slow Resource",
40
+ uri: "file:///slow.txt",
41
+ },
42
+ ],
43
+ };
44
+ });
45
+
46
+ server.setRequestHandler(ReadResourceRequestSchema, async ({ params }) => {
47
+ await delay(RESPONSE_DELAY);
48
+ return {
49
+ contents: [
50
+ {
51
+ text: `Content from slow server after ${RESPONSE_DELAY}ms delay`,
52
+ uri: params.uri,
53
+ },
54
+ ],
55
+ };
56
+ });
57
+
58
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
59
+ await delay(RESPONSE_DELAY);
60
+ return {
61
+ tools: [
62
+ {
63
+ description: "A slow test tool",
64
+ inputSchema: {
65
+ properties: {
66
+ input: {
67
+ type: "string",
68
+ },
69
+ },
70
+ type: "object",
71
+ },
72
+ name: "slowTool",
73
+ },
74
+ ],
75
+ };
76
+ });
77
+
78
+ server.setRequestHandler(CallToolRequestSchema, async ({ params }) => {
79
+ await delay(RESPONSE_DELAY);
80
+ return {
81
+ content: [
82
+ {
83
+ text: `Tool response after ${RESPONSE_DELAY}ms delay: ${params.arguments?.input}`,
84
+ type: "text" as const,
85
+ },
86
+ ],
87
+ };
88
+ });
89
+
90
+ const transport = new StdioServerTransport();
91
+ await server.connect(transport);
@@ -0,0 +1,148 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
4
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
+ import { McpError } from "@modelcontextprotocol/sdk/types.js";
6
+ import { EventSource } from "eventsource";
7
+ import { getRandomPort } from "get-port-please";
8
+ import { describe, expect, it } from "vitest";
9
+
10
+ import { proxyServer } from "./proxyServer.js";
11
+ import { startHTTPServer } from "./startHTTPServer.js";
12
+
13
+ if (!("EventSource" in global)) {
14
+ // @ts-expect-error - figure out how to use --experimental-eventsource with vitest
15
+ global.EventSource = EventSource;
16
+ }
17
+
18
+ interface TestConfig {
19
+ requestTimeout?: number;
20
+ serverDelay?: string;
21
+ serverFixture?: string;
22
+ }
23
+
24
+ interface TestEnvironment {
25
+ cleanup: () => Promise<void>;
26
+ httpServer: { close: () => Promise<void> };
27
+ stdioClient: Client;
28
+ streamClient: Client;
29
+ }
30
+
31
+ async function createTestEnvironment(config: TestConfig = {}): Promise<TestEnvironment> {
32
+ const {
33
+ requestTimeout,
34
+ serverDelay,
35
+ serverFixture = "simple-stdio-server.ts"
36
+ } = config;
37
+
38
+ const stdioTransport = new StdioClientTransport({
39
+ args: [`src/fixtures/${serverFixture}`],
40
+ command: "tsx",
41
+ env: serverDelay ? { ...process.env, RESPONSE_DELAY: serverDelay } as Record<string, string> : process.env as Record<string, string>,
42
+ });
43
+
44
+ const stdioClient = new Client(
45
+ { name: "mcp-proxy-test", version: "1.0.0" },
46
+ { capabilities: {} }
47
+ );
48
+
49
+ await stdioClient.connect(stdioTransport);
50
+
51
+ const serverVersion = stdioClient.getServerVersion() as { name: string; version: string };
52
+ const serverCapabilities = stdioClient.getServerCapabilities() as { capabilities: Record<string, unknown> };
53
+ const port = await getRandomPort();
54
+
55
+ const httpServer = await startHTTPServer({
56
+ createServer: async () => {
57
+ const mcpServer = new Server(serverVersion, { capabilities: serverCapabilities });
58
+ await proxyServer({
59
+ client: stdioClient,
60
+ requestTimeout,
61
+ server: mcpServer,
62
+ serverCapabilities,
63
+ });
64
+ return mcpServer;
65
+ },
66
+ port,
67
+ });
68
+
69
+ const streamClient = new Client(
70
+ { name: "stream-client", version: "1.0.0" },
71
+ { capabilities: {} }
72
+ );
73
+
74
+ const transport = new StreamableHTTPClientTransport(new URL(`http://localhost:${port}/mcp`));
75
+ await streamClient.connect(transport);
76
+
77
+ return {
78
+ cleanup: async () => {
79
+ await streamClient.close();
80
+ await stdioClient.close();
81
+ },
82
+ httpServer,
83
+ stdioClient,
84
+ streamClient
85
+ };
86
+ }
87
+
88
+ describe("proxyServer timeout functionality", () => {
89
+ it("should respect custom timeout settings", async () => {
90
+ const { cleanup, streamClient } = await createTestEnvironment({
91
+ requestTimeout: 1000,
92
+ serverDelay: "500",
93
+ serverFixture: "slow-stdio-server.ts"
94
+ });
95
+
96
+ // This should succeed as timeout (1s) > delay (500ms)
97
+ const result = await streamClient.listResources();
98
+ expect(result.resources).toHaveLength(1);
99
+ expect(result.resources[0].name).toBe("Slow Resource");
100
+
101
+ await cleanup();
102
+ }, 10000);
103
+
104
+ it("should timeout when request takes longer than configured timeout", async () => {
105
+ const { cleanup, streamClient } = await createTestEnvironment({
106
+ requestTimeout: 500,
107
+ serverDelay: "1000",
108
+ serverFixture: "slow-stdio-server.ts"
109
+ });
110
+
111
+ // This should throw a timeout error as delay (1s) > timeout (500ms)
112
+ await expect(streamClient.listResources()).rejects.toThrow(McpError);
113
+
114
+ await cleanup();
115
+ }, 10000);
116
+
117
+ it("should use default SDK timeout when no custom timeout is provided", async () => {
118
+ const { cleanup, streamClient } = await createTestEnvironment();
119
+
120
+ // This should succeed with default timeout
121
+ const result = await streamClient.listResources();
122
+ expect(result.resources).toBeDefined();
123
+
124
+ await cleanup();
125
+ }, 10000);
126
+
127
+ it("should handle resource reads with custom timeout", async () => {
128
+ const { cleanup, streamClient } = await createTestEnvironment({
129
+ requestTimeout: 600,
130
+ serverDelay: "300",
131
+ serverFixture: "slow-stdio-server.ts"
132
+ });
133
+
134
+ // First get the resources
135
+ const resources = await streamClient.listResources();
136
+ expect(resources.resources).toHaveLength(1);
137
+
138
+ // Resource read should succeed as timeout (600ms) > delay (300ms)
139
+ const resourceContent = await streamClient.readResource({
140
+ uri: resources.resources[0].uri,
141
+ });
142
+
143
+ expect(resourceContent.contents).toBeDefined();
144
+ expect(resourceContent.contents[0].text).toContain("300ms delay");
145
+
146
+ await cleanup();
147
+ }, 10000);
148
+ });
@@ -18,10 +18,12 @@ import {
18
18
 
19
19
  export const proxyServer = async ({
20
20
  client,
21
+ requestTimeout,
21
22
  server,
22
23
  serverCapabilities,
23
24
  }: {
24
25
  client: Client;
26
+ requestTimeout?: number;
25
27
  server: Server;
26
28
  serverCapabilities: ServerCapabilities;
27
29
  }): Promise<void> => {
@@ -42,28 +44,43 @@ export const proxyServer = async ({
42
44
 
43
45
  if (serverCapabilities?.prompts) {
44
46
  server.setRequestHandler(GetPromptRequestSchema, async (args) => {
45
- return client.getPrompt(args.params);
47
+ return client.getPrompt(
48
+ args.params,
49
+ requestTimeout ? { timeout: requestTimeout } : undefined,
50
+ );
46
51
  });
47
52
 
48
53
  server.setRequestHandler(ListPromptsRequestSchema, async (args) => {
49
- return client.listPrompts(args.params);
54
+ return client.listPrompts(
55
+ args.params,
56
+ requestTimeout ? { timeout: requestTimeout } : undefined,
57
+ );
50
58
  });
51
59
  }
52
60
 
53
61
  if (serverCapabilities?.resources) {
54
62
  server.setRequestHandler(ListResourcesRequestSchema, async (args) => {
55
- return client.listResources(args.params);
63
+ return client.listResources(
64
+ args.params,
65
+ requestTimeout ? { timeout: requestTimeout } : undefined,
66
+ );
56
67
  });
57
68
 
58
69
  server.setRequestHandler(
59
70
  ListResourceTemplatesRequestSchema,
60
71
  async (args) => {
61
- return client.listResourceTemplates(args.params);
72
+ return client.listResourceTemplates(
73
+ args.params,
74
+ requestTimeout ? { timeout: requestTimeout } : undefined,
75
+ );
62
76
  },
63
77
  );
64
78
 
65
79
  server.setRequestHandler(ReadResourceRequestSchema, async (args) => {
66
- return client.readResource(args.params);
80
+ return client.readResource(
81
+ args.params,
82
+ requestTimeout ? { timeout: requestTimeout } : undefined,
83
+ );
67
84
  });
68
85
 
69
86
  if (serverCapabilities?.resources.subscribe) {
@@ -75,26 +92,42 @@ export const proxyServer = async ({
75
92
  );
76
93
 
77
94
  server.setRequestHandler(SubscribeRequestSchema, async (args) => {
78
- return client.subscribeResource(args.params);
95
+ return client.subscribeResource(
96
+ args.params,
97
+ requestTimeout ? { timeout: requestTimeout } : undefined,
98
+ );
79
99
  });
80
100
 
81
101
  server.setRequestHandler(UnsubscribeRequestSchema, async (args) => {
82
- return client.unsubscribeResource(args.params);
102
+ return client.unsubscribeResource(
103
+ args.params,
104
+ requestTimeout ? { timeout: requestTimeout } : undefined,
105
+ );
83
106
  });
84
107
  }
85
108
  }
86
109
 
87
110
  if (serverCapabilities?.tools) {
88
111
  server.setRequestHandler(CallToolRequestSchema, async (args) => {
89
- return client.callTool(args.params);
112
+ return client.callTool(
113
+ args.params,
114
+ undefined,
115
+ requestTimeout ? { timeout: requestTimeout } : undefined,
116
+ );
90
117
  });
91
118
 
92
119
  server.setRequestHandler(ListToolsRequestSchema, async (args) => {
93
- return client.listTools(args.params);
120
+ return client.listTools(
121
+ args.params,
122
+ requestTimeout ? { timeout: requestTimeout } : undefined,
123
+ );
94
124
  });
95
125
  }
96
126
 
97
127
  server.setRequestHandler(CompleteRequestSchema, async (args) => {
98
- return client.complete(args.params);
128
+ return client.complete(
129
+ args.params,
130
+ requestTimeout ? { timeout: requestTimeout } : undefined,
131
+ );
99
132
  });
100
133
  };