mcp-proxy 5.11.0 → 5.11.2
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.js +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1 -1
- package/dist/{stdio-BEX6di72.js → stdio-DQCs94rj.js} +21 -6
- package/dist/stdio-DQCs94rj.js.map +1 -0
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/InMemoryEventStore.test.ts +72 -0
- package/src/InMemoryEventStore.ts +19 -2
- package/src/proxyServer.ts +8 -6
- package/src/startHTTPServer.ts +2 -2
- package/dist/stdio-BEX6di72.js.map +0 -1
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -157,4 +157,76 @@ describe("InMemoryEventStore", () => {
|
|
|
157
157
|
);
|
|
158
158
|
expect(emptyResult).toBe("");
|
|
159
159
|
});
|
|
160
|
+
|
|
161
|
+
it("keeps deterministic ordering even when events share the same timestamp", async () => {
|
|
162
|
+
const store = new InMemoryEventStore();
|
|
163
|
+
const streamId = "deterministic-stream";
|
|
164
|
+
|
|
165
|
+
const messages: JSONRPCMessage[] = [
|
|
166
|
+
{ id: 1, jsonrpc: "2.0", method: "step/one" },
|
|
167
|
+
{ id: 2, jsonrpc: "2.0", method: "step/two" },
|
|
168
|
+
{ id: 3, jsonrpc: "2.0", method: "step/three" },
|
|
169
|
+
{ id: 4, jsonrpc: "2.0", method: "step/four" },
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
const fixedTimestamp = 1_730_000_000_000;
|
|
173
|
+
const nowSpy = vi.spyOn(Date, "now").mockReturnValue(fixedTimestamp);
|
|
174
|
+
|
|
175
|
+
const eventIds: string[] = [];
|
|
176
|
+
try {
|
|
177
|
+
for (const message of messages) {
|
|
178
|
+
eventIds.push(await store.storeEvent(streamId, message));
|
|
179
|
+
}
|
|
180
|
+
} finally {
|
|
181
|
+
nowSpy.mockRestore();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Ensure IDs already arrive sorted since we stored sequentially
|
|
185
|
+
expect(eventIds).toEqual([...eventIds].sort());
|
|
186
|
+
|
|
187
|
+
const parts = eventIds.map((eventId) => eventId.split("_"));
|
|
188
|
+
|
|
189
|
+
const timestampParts = parts.map(([, timestamp]) => timestamp);
|
|
190
|
+
expect(timestampParts).toEqual(
|
|
191
|
+
Array(messages.length).fill(fixedTimestamp.toString())
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const counterSuffixes = parts.map(([, , counter]) => counter);
|
|
195
|
+
expect(counterSuffixes).toEqual(
|
|
196
|
+
messages.map((_, index) => (index).toString(36).padStart(4, "0"))
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
// Random parts should be 3 base36 characters each (due to substring(2, 5))
|
|
200
|
+
const randomParts = parts.map(([, , , random]) => random);
|
|
201
|
+
for (const random of randomParts) {
|
|
202
|
+
expect(random).toMatch(/^[0-9a-z]{3}$/);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Replay after the first event and ensure the remainder flow in order
|
|
206
|
+
const replayedMessages: JSONRPCMessage[] = [];
|
|
207
|
+
const returnedStreamId = await store.replayEventsAfter(eventIds[0], {
|
|
208
|
+
send: async (_eventId: string, message: JSONRPCMessage) => {
|
|
209
|
+
replayedMessages.push(message);
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
expect(returnedStreamId).toBe(streamId);
|
|
214
|
+
expect(replayedMessages).toEqual(messages.slice(1));
|
|
215
|
+
|
|
216
|
+
// Now allow timestamp to advance to ensure counter resets
|
|
217
|
+
const nextTimestamp = fixedTimestamp + 1;
|
|
218
|
+
const secondSpy = vi.spyOn(Date, "now").mockReturnValue(nextTimestamp);
|
|
219
|
+
try {
|
|
220
|
+
const nextId = await store.storeEvent(streamId, {
|
|
221
|
+
id: 5,
|
|
222
|
+
jsonrpc: "2.0",
|
|
223
|
+
method: "step/five",
|
|
224
|
+
});
|
|
225
|
+
const [, , counter, random] = nextId.split("_");
|
|
226
|
+
expect(counter).toBe("0000");
|
|
227
|
+
expect(random).toMatch(/^[0-9a-z]{3}$/);
|
|
228
|
+
} finally {
|
|
229
|
+
secondSpy.mockRestore();
|
|
230
|
+
}
|
|
231
|
+
});
|
|
160
232
|
});
|
|
@@ -14,6 +14,8 @@ import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
|
|
|
14
14
|
export class InMemoryEventStore implements EventStore {
|
|
15
15
|
private events: Map<string, { message: JSONRPCMessage; streamId: string }> =
|
|
16
16
|
new Map();
|
|
17
|
+
private lastTimestamp = 0;
|
|
18
|
+
private lastTimestampCounter = 0;
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
21
|
* Replays events that occurred after a specific event ID
|
|
@@ -79,10 +81,25 @@ export class InMemoryEventStore implements EventStore {
|
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
/**
|
|
82
|
-
* Generates a unique event ID
|
|
84
|
+
* Generates a monotonic unique event ID in
|
|
85
|
+
* `${streamId}_${timestamp}_${counter}_${random}` format.
|
|
83
86
|
*/
|
|
84
87
|
private generateEventId(streamId: string): string {
|
|
85
|
-
|
|
88
|
+
|
|
89
|
+
const now = Date.now();
|
|
90
|
+
|
|
91
|
+
if (now === this.lastTimestamp) {
|
|
92
|
+
this.lastTimestampCounter++;
|
|
93
|
+
} else {
|
|
94
|
+
this.lastTimestampCounter = 0;
|
|
95
|
+
this.lastTimestamp = now;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const timestamp = now.toString();
|
|
99
|
+
const counter = this.lastTimestampCounter.toString(36).padStart(4, "0");
|
|
100
|
+
const random = Math.random().toString(36).substring(2, 5);
|
|
101
|
+
|
|
102
|
+
return `${streamId}_${timestamp}_${counter}_${random}`;
|
|
86
103
|
}
|
|
87
104
|
|
|
88
105
|
/**
|
package/src/proxyServer.ts
CHANGED
|
@@ -124,10 +124,12 @@ export const proxyServer = async ({
|
|
|
124
124
|
});
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
127
|
+
if (serverCapabilities?.completions) {
|
|
128
|
+
server.setRequestHandler(CompleteRequestSchema, async (args) => {
|
|
129
|
+
return client.complete(
|
|
130
|
+
args.params,
|
|
131
|
+
requestTimeout ? { timeout: requestTimeout } : undefined,
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
133
135
|
};
|
package/src/startHTTPServer.ts
CHANGED
|
@@ -718,8 +718,8 @@ const handleSSERequest = async <T extends ServerLike>({
|
|
|
718
718
|
|
|
719
719
|
await transport.send({
|
|
720
720
|
jsonrpc: "2.0",
|
|
721
|
-
method: "
|
|
722
|
-
params: {
|
|
721
|
+
method: "notifications/message",
|
|
722
|
+
params: { data: "SSE Connection established", level: "info" },
|
|
723
723
|
});
|
|
724
724
|
|
|
725
725
|
if (onConnect) {
|