mcp-proxy 2.13.0 → 2.13.1
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 +71 -71
- package/dist/bin/mcp-proxy.js.map +1 -1
- package/dist/{chunk-BLDWVJ5G.js → chunk-F2LFKNGG.js} +48 -44
- package/dist/chunk-F2LFKNGG.js.map +1 -0
- package/dist/index.d.ts +32 -32
- package/dist/index.js +7 -7
- package/dist/index.js.map +1 -1
- package/eslint.config.ts +14 -0
- package/package.json +5 -1
- package/src/InMemoryEventStore.ts +30 -30
- package/src/StdioClientTransport.ts +79 -78
- package/src/bin/mcp-proxy.ts +26 -23
- package/src/index.ts +1 -1
- package/src/proxyServer.ts +9 -9
- package/src/simple-stdio-server.ts +5 -5
- package/src/startHTTPStreamServer.test.ts +20 -17
- package/src/startHTTPStreamServer.ts +20 -19
- package/src/startSSEServer.test.ts +19 -15
- package/src/startSSEServer.ts +16 -8
- package/src/tapTransport.ts +11 -11
- package/dist/chunk-BLDWVJ5G.js.map +0 -1
- package/eslint.config.js +0 -10
package/src/bin/mcp-proxy.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import yargs from "yargs";
|
|
4
|
-
import { hideBin } from "yargs/helpers";
|
|
5
3
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
6
4
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
7
5
|
import { EventSource } from "eventsource";
|
|
8
6
|
import { setTimeout } from "node:timers";
|
|
9
|
-
import { StdioClientTransport } from "../StdioClientTransport.js";
|
|
10
7
|
import util from "node:util";
|
|
11
|
-
import
|
|
12
|
-
import {
|
|
13
|
-
|
|
8
|
+
import yargs from "yargs";
|
|
9
|
+
import { hideBin } from "yargs/helpers";
|
|
10
|
+
|
|
14
11
|
import { InMemoryEventStore } from "../InMemoryEventStore.js";
|
|
12
|
+
import { proxyServer } from "../proxyServer.js";
|
|
13
|
+
import { startHTTPStreamServer } from "../startHTTPStreamServer.js";
|
|
14
|
+
import { startSSEServer } from "../startSSEServer.js";
|
|
15
|
+
import { StdioClientTransport } from "../StdioClientTransport.js";
|
|
15
16
|
|
|
16
17
|
util.inspect.defaultOptions.depth = 8;
|
|
17
18
|
|
|
@@ -24,36 +25,36 @@ const argv = await yargs(hideBin(process.argv))
|
|
|
24
25
|
.scriptName("mcp-proxy")
|
|
25
26
|
.command("$0 <command> [args...]", "Run a command with MCP arguments")
|
|
26
27
|
.positional("command", {
|
|
27
|
-
type: "string",
|
|
28
|
-
describe: "The command to run",
|
|
29
28
|
demandOption: true,
|
|
29
|
+
describe: "The command to run",
|
|
30
|
+
type: "string",
|
|
30
31
|
})
|
|
31
32
|
.positional("args", {
|
|
32
|
-
type: "string",
|
|
33
33
|
array: true,
|
|
34
34
|
describe: "The arguments to pass to the command",
|
|
35
|
+
type: "string",
|
|
35
36
|
})
|
|
36
37
|
.env("MCP_PROXY")
|
|
37
38
|
.options({
|
|
38
39
|
debug: {
|
|
39
|
-
type: "boolean",
|
|
40
|
-
describe: "Enable debug logging",
|
|
41
40
|
default: false,
|
|
41
|
+
describe: "Enable debug logging",
|
|
42
|
+
type: "boolean",
|
|
42
43
|
},
|
|
43
44
|
endpoint: {
|
|
44
|
-
type: "string",
|
|
45
45
|
describe: "The endpoint to listen on",
|
|
46
|
+
type: "string",
|
|
46
47
|
},
|
|
47
48
|
port: {
|
|
48
|
-
type: "number",
|
|
49
|
-
describe: "The port to listen on",
|
|
50
49
|
default: 8080,
|
|
50
|
+
describe: "The port to listen on",
|
|
51
|
+
type: "number",
|
|
51
52
|
},
|
|
52
53
|
server: {
|
|
53
|
-
type: "string",
|
|
54
|
-
describe: "The server type to use (sse or stream)",
|
|
55
54
|
choices: ["sse", "stream"],
|
|
56
55
|
default: "sse",
|
|
56
|
+
describe: "The server type to use (sse or stream)",
|
|
57
|
+
type: "string",
|
|
57
58
|
},
|
|
58
59
|
})
|
|
59
60
|
.help()
|
|
@@ -61,15 +62,15 @@ const argv = await yargs(hideBin(process.argv))
|
|
|
61
62
|
|
|
62
63
|
const connect = async (client: Client) => {
|
|
63
64
|
const transport = new StdioClientTransport({
|
|
64
|
-
command: argv.command,
|
|
65
65
|
args: argv.args,
|
|
66
|
+
command: argv.command,
|
|
66
67
|
env: process.env as Record<string, string>,
|
|
67
|
-
stderr: "pipe",
|
|
68
68
|
onEvent: (event) => {
|
|
69
69
|
if (argv.debug) {
|
|
70
70
|
console.debug("transport event", event);
|
|
71
71
|
}
|
|
72
72
|
},
|
|
73
|
+
stderr: "pipe",
|
|
73
74
|
});
|
|
74
75
|
|
|
75
76
|
await client.connect(transport);
|
|
@@ -83,7 +84,7 @@ const proxy = async () => {
|
|
|
83
84
|
},
|
|
84
85
|
{
|
|
85
86
|
capabilities: {},
|
|
86
|
-
}
|
|
87
|
+
},
|
|
87
88
|
);
|
|
88
89
|
|
|
89
90
|
await connect(client);
|
|
@@ -93,7 +94,9 @@ const proxy = async () => {
|
|
|
93
94
|
version: string;
|
|
94
95
|
};
|
|
95
96
|
|
|
96
|
-
const serverCapabilities = client.getServerCapabilities() as {
|
|
97
|
+
const serverCapabilities = client.getServerCapabilities() as {
|
|
98
|
+
capabilities: Record<string, unknown>;
|
|
99
|
+
};
|
|
97
100
|
|
|
98
101
|
console.info("starting the %s server on port %d", argv.server, argv.port);
|
|
99
102
|
|
|
@@ -103,8 +106,8 @@ const proxy = async () => {
|
|
|
103
106
|
});
|
|
104
107
|
|
|
105
108
|
proxyServer({
|
|
106
|
-
server,
|
|
107
109
|
client,
|
|
110
|
+
server,
|
|
108
111
|
serverCapabilities,
|
|
109
112
|
});
|
|
110
113
|
|
|
@@ -114,15 +117,15 @@ const proxy = async () => {
|
|
|
114
117
|
if (argv.server === "sse") {
|
|
115
118
|
await startSSEServer({
|
|
116
119
|
createServer,
|
|
117
|
-
port: argv.port,
|
|
118
120
|
endpoint: argv.endpoint || ("/sse" as `/${string}`),
|
|
121
|
+
port: argv.port,
|
|
119
122
|
});
|
|
120
123
|
} else {
|
|
121
124
|
await startHTTPStreamServer({
|
|
122
125
|
createServer,
|
|
123
|
-
port: argv.port,
|
|
124
126
|
endpoint: argv.endpoint || ("/stream" as `/${string}`),
|
|
125
127
|
eventStore: new InMemoryEventStore(),
|
|
128
|
+
port: argv.port,
|
|
126
129
|
});
|
|
127
130
|
}
|
|
128
131
|
};
|
package/src/index.ts
CHANGED
|
@@ -2,4 +2,4 @@ export { InMemoryEventStore } from "./InMemoryEventStore.js";
|
|
|
2
2
|
export { proxyServer } from "./proxyServer.js";
|
|
3
3
|
export { startHTTPStreamServer } from "./startHTTPStreamServer.js";
|
|
4
4
|
export { startSSEServer } from "./startSSEServer.js";
|
|
5
|
-
export { tapTransport } from "./tapTransport.js";
|
|
5
|
+
export { tapTransport } from "./tapTransport.js";
|
package/src/proxyServer.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
1
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
3
|
import {
|
|
3
4
|
CallToolRequestSchema,
|
|
@@ -9,20 +10,19 @@ import {
|
|
|
9
10
|
ListToolsRequestSchema,
|
|
10
11
|
LoggingMessageNotificationSchema,
|
|
11
12
|
ReadResourceRequestSchema,
|
|
12
|
-
SubscribeRequestSchema,
|
|
13
|
-
UnsubscribeRequestSchema,
|
|
14
13
|
ResourceUpdatedNotificationSchema,
|
|
15
14
|
ServerCapabilities,
|
|
15
|
+
SubscribeRequestSchema,
|
|
16
|
+
UnsubscribeRequestSchema,
|
|
16
17
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
17
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
18
18
|
|
|
19
19
|
export const proxyServer = async ({
|
|
20
|
-
server,
|
|
21
20
|
client,
|
|
21
|
+
server,
|
|
22
22
|
serverCapabilities,
|
|
23
23
|
}: {
|
|
24
|
-
server: Server;
|
|
25
24
|
client: Client;
|
|
25
|
+
server: Server;
|
|
26
26
|
serverCapabilities: ServerCapabilities;
|
|
27
27
|
}) => {
|
|
28
28
|
if (serverCapabilities?.logging) {
|
|
@@ -62,10 +62,10 @@ export const proxyServer = async ({
|
|
|
62
62
|
|
|
63
63
|
if (serverCapabilities?.resources.subscribe) {
|
|
64
64
|
server.setNotificationHandler(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
ResourceUpdatedNotificationSchema,
|
|
66
|
+
async (args) => {
|
|
67
|
+
return client.notification(args);
|
|
68
|
+
},
|
|
69
69
|
);
|
|
70
70
|
|
|
71
71
|
server.setRequestHandler(SubscribeRequestSchema, async (args) => {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
3
|
import {
|
|
4
|
-
ListResourceTemplatesRequestSchema,
|
|
5
4
|
ListResourcesRequestSchema,
|
|
5
|
+
ListResourceTemplatesRequestSchema,
|
|
6
6
|
ReadResourceRequestSchema,
|
|
7
7
|
SubscribeRequestSchema,
|
|
8
8
|
UnsubscribeRequestSchema,
|
|
@@ -24,8 +24,8 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
|
24
24
|
return {
|
|
25
25
|
resources: [
|
|
26
26
|
{
|
|
27
|
-
uri: "file:///example.txt",
|
|
28
27
|
name: "Example Resource",
|
|
28
|
+
uri: "file:///example.txt",
|
|
29
29
|
},
|
|
30
30
|
],
|
|
31
31
|
};
|
|
@@ -36,9 +36,9 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
36
36
|
return {
|
|
37
37
|
contents: [
|
|
38
38
|
{
|
|
39
|
-
uri: "file:///example.txt",
|
|
40
39
|
mimeType: "text/plain",
|
|
41
40
|
text: "This is the content of the example resource.",
|
|
41
|
+
uri: "file:///example.txt",
|
|
42
42
|
},
|
|
43
43
|
],
|
|
44
44
|
};
|
|
@@ -51,9 +51,9 @@ server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
|
51
51
|
return {
|
|
52
52
|
resourceTemplates: [
|
|
53
53
|
{
|
|
54
|
-
uriTemplate: `file://{filename}`,
|
|
55
|
-
name: "Example resource template",
|
|
56
54
|
description: "Specify the filename to retrieve",
|
|
55
|
+
name: "Example resource template",
|
|
56
|
+
uriTemplate: `file://{filename}`,
|
|
57
57
|
},
|
|
58
58
|
],
|
|
59
59
|
};
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
2
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
3
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
-
import { it, expect, vi } from "vitest";
|
|
5
|
-
import { startHTTPStreamServer } from "./startHTTPStreamServer.js";
|
|
6
|
-
import { getRandomPort } from "get-port-please";
|
|
7
3
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
4
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
8
5
|
import { EventSource } from "eventsource";
|
|
6
|
+
import { getRandomPort } from "get-port-please";
|
|
9
7
|
import { setTimeout as delay } from "node:timers/promises";
|
|
8
|
+
import { expect, it, vi } from "vitest";
|
|
9
|
+
|
|
10
10
|
import { proxyServer } from "./proxyServer.js";
|
|
11
|
+
import { startHTTPStreamServer } from "./startHTTPStreamServer.js";
|
|
11
12
|
|
|
12
13
|
if (!("EventSource" in global)) {
|
|
13
14
|
// @ts-expect-error - figure out how to use --experimental-eventsource with vitest
|
|
@@ -16,8 +17,8 @@ if (!("EventSource" in global)) {
|
|
|
16
17
|
|
|
17
18
|
it("proxies messages between HTTP stream and stdio servers", async () => {
|
|
18
19
|
const stdioTransport = new StdioClientTransport({
|
|
19
|
-
command: "tsx",
|
|
20
20
|
args: ["src/simple-stdio-server.ts"],
|
|
21
|
+
command: "tsx",
|
|
21
22
|
});
|
|
22
23
|
|
|
23
24
|
const stdioClient = new Client(
|
|
@@ -27,7 +28,7 @@ it("proxies messages between HTTP stream and stdio servers", async () => {
|
|
|
27
28
|
},
|
|
28
29
|
{
|
|
29
30
|
capabilities: {},
|
|
30
|
-
}
|
|
31
|
+
},
|
|
31
32
|
);
|
|
32
33
|
|
|
33
34
|
await stdioClient.connect(stdioTransport);
|
|
@@ -37,7 +38,9 @@ it("proxies messages between HTTP stream and stdio servers", async () => {
|
|
|
37
38
|
version: string;
|
|
38
39
|
};
|
|
39
40
|
|
|
40
|
-
const serverCapabilities = stdioClient.getServerCapabilities() as {
|
|
41
|
+
const serverCapabilities = stdioClient.getServerCapabilities() as {
|
|
42
|
+
capabilities: Record<string, unknown>;
|
|
43
|
+
};
|
|
41
44
|
|
|
42
45
|
const port = await getRandomPort();
|
|
43
46
|
|
|
@@ -51,17 +54,17 @@ it("proxies messages between HTTP stream and stdio servers", async () => {
|
|
|
51
54
|
});
|
|
52
55
|
|
|
53
56
|
await proxyServer({
|
|
54
|
-
server: mcpServer,
|
|
55
57
|
client: stdioClient,
|
|
58
|
+
server: mcpServer,
|
|
56
59
|
serverCapabilities,
|
|
57
60
|
});
|
|
58
61
|
|
|
59
62
|
return mcpServer;
|
|
60
63
|
},
|
|
61
|
-
port,
|
|
62
64
|
endpoint: "/stream",
|
|
63
|
-
onConnect,
|
|
64
65
|
onClose,
|
|
66
|
+
onConnect,
|
|
67
|
+
port,
|
|
65
68
|
});
|
|
66
69
|
|
|
67
70
|
const streamClient = new Client(
|
|
@@ -71,11 +74,11 @@ it("proxies messages between HTTP stream and stdio servers", async () => {
|
|
|
71
74
|
},
|
|
72
75
|
{
|
|
73
76
|
capabilities: {},
|
|
74
|
-
}
|
|
77
|
+
},
|
|
75
78
|
);
|
|
76
79
|
|
|
77
80
|
const transport = new StreamableHTTPClientTransport(
|
|
78
|
-
new URL(`http://localhost:${port}/stream`)
|
|
81
|
+
new URL(`http://localhost:${port}/stream`),
|
|
79
82
|
);
|
|
80
83
|
|
|
81
84
|
await streamClient.connect(transport);
|
|
@@ -84,20 +87,20 @@ it("proxies messages between HTTP stream and stdio servers", async () => {
|
|
|
84
87
|
expect(result).toEqual({
|
|
85
88
|
resources: [
|
|
86
89
|
{
|
|
87
|
-
uri: "file:///example.txt",
|
|
88
90
|
name: "Example Resource",
|
|
91
|
+
uri: "file:///example.txt",
|
|
89
92
|
},
|
|
90
93
|
],
|
|
91
94
|
});
|
|
92
95
|
|
|
93
96
|
expect(
|
|
94
|
-
await streamClient.readResource({ uri: result.resources[0].uri }, {})
|
|
97
|
+
await streamClient.readResource({ uri: result.resources[0].uri }, {}),
|
|
95
98
|
).toEqual({
|
|
96
99
|
contents: [
|
|
97
100
|
{
|
|
98
|
-
uri: "file:///example.txt",
|
|
99
101
|
mimeType: "text/plain",
|
|
100
102
|
text: "This is the content of the example resource.",
|
|
103
|
+
uri: "file:///example.txt",
|
|
101
104
|
},
|
|
102
105
|
],
|
|
103
106
|
});
|
|
@@ -106,9 +109,9 @@ it("proxies messages between HTTP stream and stdio servers", async () => {
|
|
|
106
109
|
expect(await streamClient.listResourceTemplates()).toEqual({
|
|
107
110
|
resourceTemplates: [
|
|
108
111
|
{
|
|
109
|
-
uriTemplate: `file://{filename}`,
|
|
110
|
-
name: "Example resource template",
|
|
111
112
|
description: "Specify the filename to retrieve",
|
|
113
|
+
name: "Example resource template",
|
|
114
|
+
uriTemplate: `file://{filename}`,
|
|
112
115
|
},
|
|
113
116
|
],
|
|
114
117
|
});
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
1
2
|
import {
|
|
2
3
|
EventStore,
|
|
3
4
|
StreamableHTTPServerTransport,
|
|
4
5
|
} from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
5
|
-
import { randomUUID } from "node:crypto";
|
|
6
|
-
import http from "http";
|
|
7
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
8
6
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
+
import http from "http";
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
9
|
+
|
|
9
10
|
import { InMemoryEventStore } from "./InMemoryEventStore.js";
|
|
10
11
|
|
|
11
12
|
export type SSEServer = {
|
|
@@ -13,35 +14,35 @@ export type SSEServer = {
|
|
|
13
14
|
};
|
|
14
15
|
|
|
15
16
|
type ServerLike = {
|
|
16
|
-
connect: Server["connect"];
|
|
17
17
|
close: Server["close"];
|
|
18
|
+
connect: Server["connect"];
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
export const startHTTPStreamServer = async <T extends ServerLike>({
|
|
21
|
-
port,
|
|
22
22
|
createServer,
|
|
23
23
|
endpoint,
|
|
24
24
|
eventStore,
|
|
25
|
-
onConnect,
|
|
26
25
|
onClose,
|
|
26
|
+
onConnect,
|
|
27
27
|
onUnhandledRequest,
|
|
28
|
+
port,
|
|
28
29
|
}: {
|
|
29
|
-
port: number;
|
|
30
|
-
endpoint: string;
|
|
31
30
|
createServer: (request: http.IncomingMessage) => Promise<T>;
|
|
31
|
+
endpoint: string;
|
|
32
32
|
eventStore?: EventStore;
|
|
33
|
-
onConnect?: (server: T) => void;
|
|
34
33
|
onClose?: (server: T) => void;
|
|
34
|
+
onConnect?: (server: T) => void;
|
|
35
35
|
onUnhandledRequest?: (
|
|
36
36
|
req: http.IncomingMessage,
|
|
37
|
-
res: http.ServerResponse
|
|
37
|
+
res: http.ServerResponse,
|
|
38
38
|
) => Promise<void>;
|
|
39
|
+
port: number;
|
|
39
40
|
}): Promise<SSEServer> => {
|
|
40
41
|
const activeTransports: Record<
|
|
41
42
|
string,
|
|
42
43
|
{
|
|
43
|
-
transport: StreamableHTTPServerTransport;
|
|
44
44
|
server: T;
|
|
45
|
+
transport: StreamableHTTPServerTransport;
|
|
45
46
|
}
|
|
46
47
|
> = {};
|
|
47
48
|
|
|
@@ -92,15 +93,15 @@ export const startHTTPStreamServer = async <T extends ServerLike>({
|
|
|
92
93
|
} else if (!sessionId && isInitializeRequest(body)) {
|
|
93
94
|
// Create a new transport for the session
|
|
94
95
|
transport = new StreamableHTTPServerTransport({
|
|
95
|
-
sessionIdGenerator: randomUUID,
|
|
96
96
|
eventStore: eventStore || new InMemoryEventStore(),
|
|
97
97
|
onsessioninitialized: (_sessionId) => {
|
|
98
98
|
// add only when the id Sesison id is generated
|
|
99
99
|
activeTransports[_sessionId] = {
|
|
100
|
-
transport,
|
|
101
100
|
server,
|
|
101
|
+
transport,
|
|
102
102
|
};
|
|
103
103
|
},
|
|
104
|
+
sessionIdGenerator: randomUUID,
|
|
104
105
|
});
|
|
105
106
|
|
|
106
107
|
// Handle the server close event
|
|
@@ -139,13 +140,13 @@ export const startHTTPStreamServer = async <T extends ServerLike>({
|
|
|
139
140
|
res.setHeader("Content-Type", "application/json");
|
|
140
141
|
res.writeHead(400).end(
|
|
141
142
|
JSON.stringify({
|
|
142
|
-
jsonrpc: "2.0",
|
|
143
143
|
error: {
|
|
144
144
|
code: -32000,
|
|
145
145
|
message: "Bad Request: No valid session ID provided",
|
|
146
146
|
},
|
|
147
147
|
id: null,
|
|
148
|
-
|
|
148
|
+
jsonrpc: "2.0",
|
|
149
|
+
}),
|
|
149
150
|
);
|
|
150
151
|
|
|
151
152
|
return;
|
|
@@ -158,10 +159,10 @@ export const startHTTPStreamServer = async <T extends ServerLike>({
|
|
|
158
159
|
res.setHeader("Content-Type", "application/json");
|
|
159
160
|
res.writeHead(500).end(
|
|
160
161
|
JSON.stringify({
|
|
161
|
-
jsonrpc: "2.0",
|
|
162
162
|
error: { code: -32603, message: "Internal Server Error" },
|
|
163
163
|
id: null,
|
|
164
|
-
|
|
164
|
+
jsonrpc: "2.0",
|
|
165
|
+
}),
|
|
165
166
|
);
|
|
166
167
|
}
|
|
167
168
|
return;
|
|
@@ -174,8 +175,8 @@ export const startHTTPStreamServer = async <T extends ServerLike>({
|
|
|
174
175
|
const sessionId = req.headers["mcp-session-id"] as string | undefined;
|
|
175
176
|
const activeTransport:
|
|
176
177
|
| {
|
|
177
|
-
transport: StreamableHTTPServerTransport;
|
|
178
178
|
server: T;
|
|
179
|
+
transport: StreamableHTTPServerTransport;
|
|
179
180
|
}
|
|
180
181
|
| undefined = sessionId ? activeTransports[sessionId] : undefined;
|
|
181
182
|
|
|
@@ -213,7 +214,7 @@ export const startHTTPStreamServer = async <T extends ServerLike>({
|
|
|
213
214
|
|
|
214
215
|
console.log("received delete request for session", sessionId);
|
|
215
216
|
|
|
216
|
-
const {
|
|
217
|
+
const { server, transport } = activeTransports[sessionId];
|
|
217
218
|
if (!transport) {
|
|
218
219
|
res.writeHead(400).end("No active transport");
|
|
219
220
|
return;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
2
3
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
3
4
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
-
import { it, expect, vi } from "vitest";
|
|
5
|
-
import { startSSEServer } from "./startSSEServer.js";
|
|
6
|
-
import { getRandomPort } from "get-port-please";
|
|
7
|
-
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
8
5
|
import { EventSource } from "eventsource";
|
|
6
|
+
import { getRandomPort } from "get-port-please";
|
|
9
7
|
import { setTimeout as delay } from "node:timers/promises";
|
|
8
|
+
import { expect, it, vi } from "vitest";
|
|
9
|
+
|
|
10
10
|
import { proxyServer } from "./proxyServer.js";
|
|
11
|
+
import { startSSEServer } from "./startSSEServer.js";
|
|
11
12
|
|
|
12
13
|
if (!("EventSource" in global)) {
|
|
13
14
|
// @ts-expect-error - figure out how to use --experimental-eventsource with vitest
|
|
@@ -16,8 +17,8 @@ if (!("EventSource" in global)) {
|
|
|
16
17
|
|
|
17
18
|
it("proxies messages between SSE and stdio servers", async () => {
|
|
18
19
|
const stdioTransport = new StdioClientTransport({
|
|
19
|
-
command: "tsx",
|
|
20
20
|
args: ["src/simple-stdio-server.ts"],
|
|
21
|
+
command: "tsx",
|
|
21
22
|
});
|
|
22
23
|
|
|
23
24
|
const stdioClient = new Client(
|
|
@@ -37,7 +38,9 @@ it("proxies messages between SSE and stdio servers", async () => {
|
|
|
37
38
|
version: string;
|
|
38
39
|
};
|
|
39
40
|
|
|
40
|
-
const serverCapabilities = stdioClient.getServerCapabilities() as {
|
|
41
|
+
const serverCapabilities = stdioClient.getServerCapabilities() as {
|
|
42
|
+
capabilities: Record<string, unknown>;
|
|
43
|
+
};
|
|
41
44
|
|
|
42
45
|
const port = await getRandomPort();
|
|
43
46
|
|
|
@@ -51,17 +54,17 @@ it("proxies messages between SSE and stdio servers", async () => {
|
|
|
51
54
|
});
|
|
52
55
|
|
|
53
56
|
await proxyServer({
|
|
54
|
-
server: mcpServer,
|
|
55
57
|
client: stdioClient,
|
|
58
|
+
server: mcpServer,
|
|
56
59
|
serverCapabilities,
|
|
57
60
|
});
|
|
58
61
|
|
|
59
62
|
return mcpServer;
|
|
60
63
|
},
|
|
61
|
-
port,
|
|
62
64
|
endpoint: "/sse",
|
|
63
|
-
onConnect,
|
|
64
65
|
onClose,
|
|
66
|
+
onConnect,
|
|
67
|
+
port,
|
|
65
68
|
});
|
|
66
69
|
|
|
67
70
|
const sseClient = new Client(
|
|
@@ -84,18 +87,20 @@ it("proxies messages between SSE and stdio servers", async () => {
|
|
|
84
87
|
expect(result).toEqual({
|
|
85
88
|
resources: [
|
|
86
89
|
{
|
|
87
|
-
uri: "file:///example.txt",
|
|
88
90
|
name: "Example Resource",
|
|
91
|
+
uri: "file:///example.txt",
|
|
89
92
|
},
|
|
90
93
|
],
|
|
91
94
|
});
|
|
92
95
|
|
|
93
|
-
expect(
|
|
96
|
+
expect(
|
|
97
|
+
await sseClient.readResource({ uri: result.resources[0].uri }, {}),
|
|
98
|
+
).toEqual({
|
|
94
99
|
contents: [
|
|
95
100
|
{
|
|
96
|
-
uri: "file:///example.txt",
|
|
97
101
|
mimeType: "text/plain",
|
|
98
102
|
text: "This is the content of the example resource.",
|
|
103
|
+
uri: "file:///example.txt",
|
|
99
104
|
},
|
|
100
105
|
],
|
|
101
106
|
});
|
|
@@ -104,14 +109,13 @@ it("proxies messages between SSE and stdio servers", async () => {
|
|
|
104
109
|
expect(await sseClient.listResourceTemplates()).toEqual({
|
|
105
110
|
resourceTemplates: [
|
|
106
111
|
{
|
|
107
|
-
uriTemplate: `file://{filename}`,
|
|
108
|
-
name: "Example resource template",
|
|
109
112
|
description: "Specify the filename to retrieve",
|
|
113
|
+
name: "Example resource template",
|
|
114
|
+
uriTemplate: `file://{filename}`,
|
|
110
115
|
},
|
|
111
116
|
],
|
|
112
117
|
});
|
|
113
118
|
|
|
114
|
-
|
|
115
119
|
expect(onConnect).toHaveBeenCalled();
|
|
116
120
|
expect(onClose).not.toHaveBeenCalled();
|
|
117
121
|
|
package/src/startSSEServer.ts
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
1
2
|
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
2
3
|
import http from "http";
|
|
3
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
4
|
|
|
5
5
|
export type SSEServer = {
|
|
6
6
|
close: () => Promise<void>;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
type ServerLike = {
|
|
10
|
-
connect: Server["connect"];
|
|
11
10
|
close: Server["close"];
|
|
11
|
+
connect: Server["connect"];
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
export const startSSEServer = async <T extends ServerLike>({
|
|
15
|
-
port,
|
|
16
15
|
createServer,
|
|
17
16
|
endpoint,
|
|
18
|
-
onConnect,
|
|
19
17
|
onClose,
|
|
18
|
+
onConnect,
|
|
20
19
|
onUnhandledRequest,
|
|
20
|
+
port,
|
|
21
21
|
}: {
|
|
22
|
-
port: number;
|
|
23
|
-
endpoint: string;
|
|
24
22
|
createServer: (request: http.IncomingMessage) => Promise<T>;
|
|
25
|
-
|
|
23
|
+
endpoint: string;
|
|
26
24
|
onClose?: (server: T) => void;
|
|
25
|
+
onConnect?: (server: T) => void;
|
|
27
26
|
onUnhandledRequest?: (
|
|
28
27
|
req: http.IncomingMessage,
|
|
29
28
|
res: http.ServerResponse,
|
|
30
29
|
) => Promise<void>;
|
|
30
|
+
port: number;
|
|
31
31
|
}): Promise<SSEServer> => {
|
|
32
32
|
const activeTransports: Record<string, SSEServerTransport> = {};
|
|
33
33
|
|
|
@@ -54,13 +54,21 @@ export const startSSEServer = async <T extends ServerLike>({
|
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
if (req.method === "GET" && req.url === "/health") {
|
|
58
|
+
res.writeHead(200, { "Content-Type": "text/plain" }).end("OK");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
57
62
|
if (req.method === "GET" && req.url === `/ping`) {
|
|
58
63
|
res.writeHead(200).end("pong");
|
|
59
64
|
|
|
60
65
|
return;
|
|
61
66
|
}
|
|
62
67
|
|
|
63
|
-
if (
|
|
68
|
+
if (
|
|
69
|
+
req.method === "GET" &&
|
|
70
|
+
new URL(req.url!, "http://localhost").pathname === endpoint
|
|
71
|
+
) {
|
|
64
72
|
const transport = new SSEServerTransport("/messages", res);
|
|
65
73
|
|
|
66
74
|
let server: T;
|
package/src/tapTransport.ts
CHANGED
|
@@ -3,22 +3,22 @@ import { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
|
|
|
3
3
|
|
|
4
4
|
type TransportEvent =
|
|
5
5
|
| {
|
|
6
|
-
|
|
6
|
+
error: Error;
|
|
7
|
+
type: "onerror";
|
|
7
8
|
}
|
|
8
9
|
| {
|
|
9
|
-
|
|
10
|
+
message: JSONRPCMessage;
|
|
11
|
+
type: "onmessage";
|
|
10
12
|
}
|
|
11
13
|
| {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
message: JSONRPCMessage;
|
|
15
|
+
type: "send";
|
|
14
16
|
}
|
|
15
17
|
| {
|
|
16
|
-
type: "
|
|
17
|
-
message: JSONRPCMessage;
|
|
18
|
+
type: "close";
|
|
18
19
|
}
|
|
19
20
|
| {
|
|
20
|
-
type: "
|
|
21
|
-
message: JSONRPCMessage;
|
|
21
|
+
type: "onclose";
|
|
22
22
|
}
|
|
23
23
|
| {
|
|
24
24
|
type: "start";
|
|
@@ -53,8 +53,8 @@ export const tapTransport = (
|
|
|
53
53
|
|
|
54
54
|
transport.onerror = async (error: Error) => {
|
|
55
55
|
eventHandler({
|
|
56
|
-
type: "onerror",
|
|
57
56
|
error,
|
|
57
|
+
type: "onerror",
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
return originalOnError?.(error);
|
|
@@ -62,8 +62,8 @@ export const tapTransport = (
|
|
|
62
62
|
|
|
63
63
|
transport.onmessage = async (message: JSONRPCMessage) => {
|
|
64
64
|
eventHandler({
|
|
65
|
-
type: "onmessage",
|
|
66
65
|
message,
|
|
66
|
+
type: "onmessage",
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
return originalOnMessage?.(message);
|
|
@@ -71,8 +71,8 @@ export const tapTransport = (
|
|
|
71
71
|
|
|
72
72
|
transport.send = async (message: JSONRPCMessage) => {
|
|
73
73
|
eventHandler({
|
|
74
|
-
type: "send",
|
|
75
74
|
message,
|
|
75
|
+
type: "send",
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
return originalSend?.(message);
|