mcp-proxy 5.5.4 → 5.5.6
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/dist/bin/mcp-proxy.d.ts +1 -0
- package/dist/bin/mcp-proxy.js +5173 -358
- package/dist/bin/mcp-proxy.js.map +1 -1
- package/dist/index.d.ts +100 -74
- package/dist/index.js +1843 -112
- package/dist/index.js.map +1 -1
- package/dist/stdio-CQWnvum1.js +21424 -0
- package/dist/stdio-CQWnvum1.js.map +1 -0
- package/jsr.json +1 -1
- package/package.json +11 -13
- package/src/InMemoryEventStore.test.ts +160 -0
- package/src/InMemoryEventStore.ts +1 -1
- package/dist/chunk-JCZNH6HS.js +0 -535
- package/dist/chunk-JCZNH6HS.js.map +0 -1
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-proxy",
|
|
3
|
-
"version": "5.5.
|
|
3
|
+
"version": "5.5.6",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"scripts": {
|
|
6
|
-
"build": "
|
|
6
|
+
"build": "tsdown",
|
|
7
7
|
"test": "vitest run && tsc && eslint . && jsr publish --dry-run --allow-dirty",
|
|
8
8
|
"format": "eslint --fix ."
|
|
9
9
|
},
|
|
@@ -21,11 +21,6 @@
|
|
|
21
21
|
"description": "A TypeScript SSE proxy for MCP servers that use stdio transport.",
|
|
22
22
|
"module": "dist/index.js",
|
|
23
23
|
"types": "dist/index.d.ts",
|
|
24
|
-
"dependencies": {
|
|
25
|
-
"@modelcontextprotocol/sdk": "^1.17.2",
|
|
26
|
-
"eventsource": "^4.0.0",
|
|
27
|
-
"yargs": "^18.0.0"
|
|
28
|
-
},
|
|
29
24
|
"repository": {
|
|
30
25
|
"url": "https://github.com/punkpeye/mcp-proxy"
|
|
31
26
|
},
|
|
@@ -44,27 +39,30 @@
|
|
|
44
39
|
},
|
|
45
40
|
"devDependencies": {
|
|
46
41
|
"@eslint/js": "^9.33.0",
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.17.3",
|
|
47
43
|
"@sebbo2002/semantic-release-jsr": "^3.0.1",
|
|
48
44
|
"@tsconfig/node22": "^22.0.2",
|
|
49
45
|
"@types/express": "^5.0.3",
|
|
50
|
-
"@types/node": "^24.
|
|
46
|
+
"@types/node": "^24.3.0",
|
|
51
47
|
"@types/yargs": "^17.0.33",
|
|
52
48
|
"eslint": "^9.33.0",
|
|
53
49
|
"eslint-config-prettier": "^10.1.8",
|
|
54
50
|
"eslint-plugin-perfectionist": "^4.15.0",
|
|
51
|
+
"eventsource": "^4.0.0",
|
|
55
52
|
"express": "^5.0.1",
|
|
56
53
|
"get-port-please": "^3.2.0",
|
|
57
54
|
"jiti": "^2.5.1",
|
|
58
55
|
"jsr": "^0.13.5",
|
|
59
56
|
"prettier": "^3.6.2",
|
|
60
57
|
"semantic-release": "^24.2.7",
|
|
61
|
-
"
|
|
62
|
-
"tsx": "^4.20.
|
|
58
|
+
"tsdown": "^0.14.2",
|
|
59
|
+
"tsx": "^4.20.4",
|
|
63
60
|
"typescript": "^5.9.2",
|
|
64
|
-
"typescript-eslint": "^8.39.
|
|
65
|
-
"vitest": "^3.2.4"
|
|
61
|
+
"typescript-eslint": "^8.39.1",
|
|
62
|
+
"vitest": "^3.2.4",
|
|
63
|
+
"yargs": "^18.0.0"
|
|
66
64
|
},
|
|
67
|
-
"
|
|
65
|
+
"tsdown": {
|
|
68
66
|
"entry": [
|
|
69
67
|
"src/index.ts",
|
|
70
68
|
"src/bin/mcp-proxy.ts"
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
|
|
5
|
+
import { InMemoryEventStore } from "./InMemoryEventStore.js";
|
|
6
|
+
|
|
7
|
+
describe("InMemoryEventStore", () => {
|
|
8
|
+
it("stores events and replays them after a specific event ID", async () => {
|
|
9
|
+
const store = new InMemoryEventStore();
|
|
10
|
+
const streamId = "test-stream-123";
|
|
11
|
+
|
|
12
|
+
// Create test messages
|
|
13
|
+
const messages: JSONRPCMessage[] = [
|
|
14
|
+
{ id: 1, jsonrpc: "2.0", method: "initialize" },
|
|
15
|
+
{ id: 2, jsonrpc: "2.0", method: "tools/list" },
|
|
16
|
+
{ id: 3, jsonrpc: "2.0", method: "tools/call", params: { name: "test" } },
|
|
17
|
+
{ id: 3, jsonrpc: "2.0", result: { success: true } },
|
|
18
|
+
{ id: 4, jsonrpc: "2.0", method: "shutdown" },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
// Store all events and keep track of event IDs
|
|
22
|
+
// Add small delays to ensure different timestamps for proper ordering
|
|
23
|
+
const eventIds: string[] = [];
|
|
24
|
+
for (const message of messages) {
|
|
25
|
+
const eventId = await store.storeEvent(streamId, message);
|
|
26
|
+
expect(eventId).toContain(streamId);
|
|
27
|
+
eventIds.push(eventId);
|
|
28
|
+
// Small delay to ensure different timestamps
|
|
29
|
+
await new Promise(resolve => setTimeout(resolve, 1));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Test replaying events after the second event
|
|
33
|
+
const replayedEvents: Array<{ eventId: string; message: JSONRPCMessage }> = [];
|
|
34
|
+
const sendMock = vi.fn(async (eventId: string, message: JSONRPCMessage) => {
|
|
35
|
+
replayedEvents.push({ eventId, message });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const returnedStreamId = await store.replayEventsAfter(
|
|
39
|
+
eventIds[1], // Replay after the second event
|
|
40
|
+
{ send: sendMock }
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// Verify the correct stream ID was returned
|
|
44
|
+
expect(returnedStreamId).toBe(streamId);
|
|
45
|
+
|
|
46
|
+
// Verify that events 3, 4, and 5 were replayed (after event 2)
|
|
47
|
+
expect(replayedEvents).toHaveLength(3);
|
|
48
|
+
expect(sendMock).toHaveBeenCalledTimes(3);
|
|
49
|
+
|
|
50
|
+
// Verify the replayed messages are correct and in order
|
|
51
|
+
expect(replayedEvents[0].message).toEqual(messages[2]);
|
|
52
|
+
expect(replayedEvents[1].message).toEqual(messages[3]);
|
|
53
|
+
expect(replayedEvents[2].message).toEqual(messages[4]);
|
|
54
|
+
|
|
55
|
+
// Verify event IDs are preserved
|
|
56
|
+
expect(replayedEvents[0].eventId).toBe(eventIds[2]);
|
|
57
|
+
expect(replayedEvents[1].eventId).toBe(eventIds[3]);
|
|
58
|
+
expect(replayedEvents[2].eventId).toBe(eventIds[4]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("isolates events by stream ID and only replays events from the same stream", async () => {
|
|
62
|
+
const store = new InMemoryEventStore();
|
|
63
|
+
const streamId1 = "stream-alpha";
|
|
64
|
+
const streamId2 = "stream-beta";
|
|
65
|
+
|
|
66
|
+
// Create messages for two different streams
|
|
67
|
+
const stream1Messages: JSONRPCMessage[] = [
|
|
68
|
+
{ id: 1, jsonrpc: "2.0", method: "stream1.init" },
|
|
69
|
+
{ id: 2, jsonrpc: "2.0", method: "stream1.process" },
|
|
70
|
+
{ id: 3, jsonrpc: "2.0", method: "stream1.complete" },
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const stream2Messages: JSONRPCMessage[] = [
|
|
74
|
+
{ id: 10, jsonrpc: "2.0", method: "stream2.init" },
|
|
75
|
+
{ id: 20, jsonrpc: "2.0", method: "stream2.process" },
|
|
76
|
+
{ id: 30, jsonrpc: "2.0", method: "stream2.complete" },
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
// Interleave storing events from both streams with small delays
|
|
80
|
+
const stream1EventIds: string[] = [];
|
|
81
|
+
const stream2EventIds: string[] = [];
|
|
82
|
+
|
|
83
|
+
// Store first event from each stream
|
|
84
|
+
stream1EventIds.push(await store.storeEvent(streamId1, stream1Messages[0]));
|
|
85
|
+
await new Promise(resolve => setTimeout(resolve, 1));
|
|
86
|
+
stream2EventIds.push(await store.storeEvent(streamId2, stream2Messages[0]));
|
|
87
|
+
await new Promise(resolve => setTimeout(resolve, 1));
|
|
88
|
+
|
|
89
|
+
// Store second event from each stream
|
|
90
|
+
stream1EventIds.push(await store.storeEvent(streamId1, stream1Messages[1]));
|
|
91
|
+
await new Promise(resolve => setTimeout(resolve, 1));
|
|
92
|
+
stream2EventIds.push(await store.storeEvent(streamId2, stream2Messages[1]));
|
|
93
|
+
await new Promise(resolve => setTimeout(resolve, 1));
|
|
94
|
+
|
|
95
|
+
// Store third event from each stream
|
|
96
|
+
stream1EventIds.push(await store.storeEvent(streamId1, stream1Messages[2]));
|
|
97
|
+
await new Promise(resolve => setTimeout(resolve, 1));
|
|
98
|
+
stream2EventIds.push(await store.storeEvent(streamId2, stream2Messages[2]));
|
|
99
|
+
|
|
100
|
+
// Replay events from stream 1 after its first event
|
|
101
|
+
const stream1ReplayedEvents: Array<{ eventId: string; message: JSONRPCMessage }> = [];
|
|
102
|
+
const stream1SendMock = vi.fn(async (eventId: string, message: JSONRPCMessage) => {
|
|
103
|
+
stream1ReplayedEvents.push({ eventId, message });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const returnedStreamId1 = await store.replayEventsAfter(
|
|
107
|
+
stream1EventIds[0],
|
|
108
|
+
{ send: stream1SendMock }
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// Verify only stream 1 events were replayed
|
|
112
|
+
expect(returnedStreamId1).toBe(streamId1);
|
|
113
|
+
expect(stream1ReplayedEvents).toHaveLength(2);
|
|
114
|
+
expect(stream1ReplayedEvents[0].message).toEqual(stream1Messages[1]);
|
|
115
|
+
expect(stream1ReplayedEvents[1].message).toEqual(stream1Messages[2]);
|
|
116
|
+
|
|
117
|
+
// Verify no stream 2 events were included
|
|
118
|
+
for (const event of stream1ReplayedEvents) {
|
|
119
|
+
expect(event.eventId).toContain(streamId1);
|
|
120
|
+
expect(event.eventId).not.toContain(streamId2);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Now replay events from stream 2 after its first event
|
|
124
|
+
const stream2ReplayedEvents: Array<{ eventId: string; message: JSONRPCMessage }> = [];
|
|
125
|
+
const stream2SendMock = vi.fn(async (eventId: string, message: JSONRPCMessage) => {
|
|
126
|
+
stream2ReplayedEvents.push({ eventId, message });
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const returnedStreamId2 = await store.replayEventsAfter(
|
|
130
|
+
stream2EventIds[0],
|
|
131
|
+
{ send: stream2SendMock }
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// Verify only stream 2 events were replayed
|
|
135
|
+
expect(returnedStreamId2).toBe(streamId2);
|
|
136
|
+
expect(stream2ReplayedEvents).toHaveLength(2);
|
|
137
|
+
expect(stream2ReplayedEvents[0].message).toEqual(stream2Messages[1]);
|
|
138
|
+
expect(stream2ReplayedEvents[1].message).toEqual(stream2Messages[2]);
|
|
139
|
+
|
|
140
|
+
// Verify no stream 1 events were included
|
|
141
|
+
for (const event of stream2ReplayedEvents) {
|
|
142
|
+
expect(event.eventId).toContain(streamId2);
|
|
143
|
+
expect(event.eventId).not.toContain(streamId1);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Test edge case: replay with non-existent event ID returns empty string
|
|
147
|
+
const invalidResult = await store.replayEventsAfter(
|
|
148
|
+
"non-existent-event-id",
|
|
149
|
+
{ send: vi.fn() }
|
|
150
|
+
);
|
|
151
|
+
expect(invalidResult).toBe("");
|
|
152
|
+
|
|
153
|
+
// Test edge case: replay with empty event ID returns empty string
|
|
154
|
+
const emptyResult = await store.replayEventsAfter(
|
|
155
|
+
"",
|
|
156
|
+
{ send: vi.fn() }
|
|
157
|
+
);
|
|
158
|
+
expect(emptyResult).toBe("");
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* This is a copy of the InMemoryEventStore from the typescript-sdk
|
|
3
|
-
* https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/inMemoryEventStore.ts
|
|
3
|
+
* https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/examples/shared/inMemoryEventStore.ts
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { EventStore } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|