mcp-proxy 5.12.4 → 6.1.8
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/.github/workflows/main.yaml +4 -13
- package/README.md +12 -20
- package/dist/bin/mcp-proxy.mjs +27 -14
- package/dist/bin/mcp-proxy.mjs.map +1 -1
- package/dist/index.d.mts +6 -0
- package/dist/index.mjs +1 -1
- package/dist/{stdio-DBuYn6eo.mjs → stdio-BArgKxoc.mjs} +30 -8
- package/dist/stdio-BArgKxoc.mjs.map +1 -0
- package/jsr.json +1 -1
- package/package.json +3 -3
- package/src/bin/mcp-proxy.ts +25 -19
- package/src/startHTTPServer.test.ts +62 -0
- package/src/startHTTPServer.ts +45 -2
- package/tsconfig.json +1 -1
- package/dist/stdio-DBuYn6eo.mjs.map +0 -1
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-proxy",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.1.8",
|
|
4
4
|
"main": "dist/index.mjs",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "tsdown",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"description": "A TypeScript SSE proxy for MCP servers that use stdio transport.",
|
|
22
22
|
"module": "dist/index.mjs",
|
|
23
|
-
"types": "dist/index.d.
|
|
23
|
+
"types": "dist/index.d.mts",
|
|
24
24
|
"repository": {
|
|
25
25
|
"url": "https://github.com/punkpeye/mcp-proxy"
|
|
26
26
|
},
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"@eslint/js": "^9.39.1",
|
|
42
42
|
"@modelcontextprotocol/sdk": "^1.24.3",
|
|
43
43
|
"@sebbo2002/semantic-release-jsr": "^3.1.0",
|
|
44
|
-
"@tsconfig/
|
|
44
|
+
"@tsconfig/node24": "^24.0.3",
|
|
45
45
|
"@types/express": "^5.0.6",
|
|
46
46
|
"@types/node": "^24.10.1",
|
|
47
47
|
"@types/yargs": "^17.0.35",
|
package/src/bin/mcp-proxy.ts
CHANGED
|
@@ -22,17 +22,7 @@ if (!("EventSource" in global)) {
|
|
|
22
22
|
|
|
23
23
|
const argv = await yargs(hideBin(process.argv))
|
|
24
24
|
.scriptName("mcp-proxy")
|
|
25
|
-
.
|
|
26
|
-
.positional("command", {
|
|
27
|
-
demandOption: true,
|
|
28
|
-
describe: "The command to run",
|
|
29
|
-
type: "string",
|
|
30
|
-
})
|
|
31
|
-
.positional("args", {
|
|
32
|
-
array: true,
|
|
33
|
-
describe: "The arguments to pass to the command",
|
|
34
|
-
type: "string",
|
|
35
|
-
})
|
|
25
|
+
.usage("$0 [options] -- <command> [args...]")
|
|
36
26
|
.env("MCP_PROXY")
|
|
37
27
|
.parserConfiguration({
|
|
38
28
|
"populate--": true,
|
|
@@ -88,6 +78,18 @@ const argv = await yargs(hideBin(process.argv))
|
|
|
88
78
|
describe: "The SSE endpoint to listen on",
|
|
89
79
|
type: "string",
|
|
90
80
|
},
|
|
81
|
+
sslCa: {
|
|
82
|
+
describe: "Filename to override the trusted CA certificates",
|
|
83
|
+
type: "string",
|
|
84
|
+
},
|
|
85
|
+
sslCert: {
|
|
86
|
+
describe: "Cert chains filename in PEM format",
|
|
87
|
+
type: "string",
|
|
88
|
+
},
|
|
89
|
+
sslKey: {
|
|
90
|
+
describe: "Private keys filename in PEM format",
|
|
91
|
+
type: "string",
|
|
92
|
+
},
|
|
91
93
|
stateless: {
|
|
92
94
|
default: false,
|
|
93
95
|
describe:
|
|
@@ -103,15 +105,16 @@ const argv = await yargs(hideBin(process.argv))
|
|
|
103
105
|
.help()
|
|
104
106
|
.parseAsync();
|
|
105
107
|
|
|
106
|
-
// Determine the final command and args
|
|
107
|
-
|
|
108
|
-
|
|
108
|
+
// Determine the final command and args from -- separator
|
|
109
|
+
const dashDashArgs = argv["--"] as string[] | undefined;
|
|
110
|
+
if (!dashDashArgs || dashDashArgs.length === 0) {
|
|
111
|
+
console.error("Error: No command specified.");
|
|
112
|
+
console.error("Usage: mcp-proxy [options] -- <command> [args...]");
|
|
113
|
+
console.error("");
|
|
114
|
+
console.error("Example: mcp-proxy --port 8080 -- node server.js --port 3000");
|
|
115
|
+
process.exit(1);
|
|
109
116
|
}
|
|
110
|
-
|
|
111
|
-
const finalCommand = argv.command;
|
|
112
|
-
|
|
113
|
-
// If -- separator was used, args after -- are in argv["--"], otherwise use parsed args
|
|
114
|
-
const finalArgs = (argv["--"] as string[]) || argv.args;
|
|
117
|
+
const [finalCommand, ...finalArgs] = dashDashArgs;
|
|
115
118
|
|
|
116
119
|
const connect = async (client: Client) => {
|
|
117
120
|
const transport = new StdioClientTransport({
|
|
@@ -180,6 +183,9 @@ const proxy = async () => {
|
|
|
180
183
|
argv.server && argv.server !== "sse"
|
|
181
184
|
? null
|
|
182
185
|
: (argv.sseEndpoint ?? argv.endpoint),
|
|
186
|
+
sslCa: argv.sslCa,
|
|
187
|
+
sslCert: argv.sslCert,
|
|
188
|
+
sslKey: argv.sslKey,
|
|
183
189
|
stateless: argv.stateless,
|
|
184
190
|
streamEndpoint:
|
|
185
191
|
argv.server && argv.server !== "stream"
|
|
@@ -4,7 +4,9 @@ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
|
|
|
4
4
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
5
5
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
6
6
|
import { EventSource } from "eventsource";
|
|
7
|
+
import fs from "fs";
|
|
7
8
|
import { getRandomPort } from "get-port-please";
|
|
9
|
+
import https from "https";
|
|
8
10
|
import { setTimeout as delay } from "node:timers/promises";
|
|
9
11
|
import { expect, it, vi } from "vitest";
|
|
10
12
|
|
|
@@ -2085,3 +2087,63 @@ it("supports custom methods and maxAge", async () => {
|
|
|
2085
2087
|
|
|
2086
2088
|
await httpServer.close();
|
|
2087
2089
|
});
|
|
2090
|
+
|
|
2091
|
+
// SSL Tests
|
|
2092
|
+
|
|
2093
|
+
it("supports creating an SSL server", async () => {
|
|
2094
|
+
const port = await getRandomPort();
|
|
2095
|
+
|
|
2096
|
+
const httpServer = await startHTTPServer({
|
|
2097
|
+
createServer: async () => {
|
|
2098
|
+
const mcpServer = new Server(
|
|
2099
|
+
{ name: "test", version: "1.0.0" },
|
|
2100
|
+
{ capabilities: {} },
|
|
2101
|
+
);
|
|
2102
|
+
return mcpServer;
|
|
2103
|
+
},
|
|
2104
|
+
port,
|
|
2105
|
+
sslCert: "src/fixtures/certs/server-cert.pem",
|
|
2106
|
+
sslKey: "src/fixtures/certs/server-key.pem",
|
|
2107
|
+
});
|
|
2108
|
+
|
|
2109
|
+
const options = {
|
|
2110
|
+
ca: fs.readFileSync("src/fixtures/certs/ca-cert.pem"),
|
|
2111
|
+
cert: fs.readFileSync("src/fixtures/certs/client-cert.pem"),
|
|
2112
|
+
hostname: "localhost",
|
|
2113
|
+
key: fs.readFileSync("src/fixtures/certs/client-key.pem"),
|
|
2114
|
+
method: "GET",
|
|
2115
|
+
path: "/ping",
|
|
2116
|
+
port,
|
|
2117
|
+
};
|
|
2118
|
+
|
|
2119
|
+
// Use https.get to test client certificate authentication
|
|
2120
|
+
// (Node's fetch API doesn't support custom HTTPS agents with client certs)
|
|
2121
|
+
const response = await new Promise<{ statusCode?: number; text: string }>(
|
|
2122
|
+
(resolve, reject) => {
|
|
2123
|
+
https
|
|
2124
|
+
.get(options, (res) => {
|
|
2125
|
+
let data = "";
|
|
2126
|
+
|
|
2127
|
+
res.on("data", (chunk) => {
|
|
2128
|
+
data += chunk;
|
|
2129
|
+
});
|
|
2130
|
+
|
|
2131
|
+
res.on("end", () => {
|
|
2132
|
+
resolve({ statusCode: res.statusCode, text: data });
|
|
2133
|
+
});
|
|
2134
|
+
|
|
2135
|
+
res.on("error", (err) => {
|
|
2136
|
+
reject(err);
|
|
2137
|
+
});
|
|
2138
|
+
})
|
|
2139
|
+
.on("error", (err) => {
|
|
2140
|
+
reject(err);
|
|
2141
|
+
});
|
|
2142
|
+
},
|
|
2143
|
+
);
|
|
2144
|
+
|
|
2145
|
+
expect(response.statusCode).toBe(200);
|
|
2146
|
+
expect(response.text).toBe("pong");
|
|
2147
|
+
|
|
2148
|
+
await httpServer.close();
|
|
2149
|
+
});
|
package/src/startHTTPServer.ts
CHANGED
|
@@ -5,7 +5,9 @@ import {
|
|
|
5
5
|
StreamableHTTPServerTransport,
|
|
6
6
|
} from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
7
7
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
+
import fs from "fs";
|
|
8
9
|
import http from "http";
|
|
10
|
+
import https from "https";
|
|
9
11
|
import { randomUUID } from "node:crypto";
|
|
10
12
|
|
|
11
13
|
import { AuthConfig, AuthenticationMiddleware } from "./authentication.js";
|
|
@@ -857,6 +859,9 @@ export const startHTTPServer = async <T extends ServerLike>({
|
|
|
857
859
|
onUnhandledRequest,
|
|
858
860
|
port,
|
|
859
861
|
sseEndpoint = "/sse",
|
|
862
|
+
sslCa,
|
|
863
|
+
sslCert,
|
|
864
|
+
sslKey,
|
|
860
865
|
stateless,
|
|
861
866
|
streamEndpoint = "/mcp",
|
|
862
867
|
}: {
|
|
@@ -876,6 +881,9 @@ export const startHTTPServer = async <T extends ServerLike>({
|
|
|
876
881
|
) => Promise<void>;
|
|
877
882
|
port: number;
|
|
878
883
|
sseEndpoint?: null | string;
|
|
884
|
+
sslCa?: null | string;
|
|
885
|
+
sslCert?: null | string;
|
|
886
|
+
sslKey?: null | string;
|
|
879
887
|
stateless?: boolean;
|
|
880
888
|
streamEndpoint?: null | string;
|
|
881
889
|
}): Promise<SSEServer> => {
|
|
@@ -894,7 +902,7 @@ export const startHTTPServer = async <T extends ServerLike>({
|
|
|
894
902
|
/**
|
|
895
903
|
* @author https://dev.classmethod.jp/articles/mcp-sse/
|
|
896
904
|
*/
|
|
897
|
-
const
|
|
905
|
+
const requestListener: http.RequestListener = async (req, res) => {
|
|
898
906
|
// Apply CORS headers
|
|
899
907
|
applyCorsHeaders(req, res, cors);
|
|
900
908
|
|
|
@@ -958,7 +966,42 @@ export const startHTTPServer = async <T extends ServerLike>({
|
|
|
958
966
|
} else {
|
|
959
967
|
res.writeHead(404).end();
|
|
960
968
|
}
|
|
961
|
-
}
|
|
969
|
+
};
|
|
970
|
+
|
|
971
|
+
let httpServer;
|
|
972
|
+
if (sslCa || sslCert || sslKey) {
|
|
973
|
+
const options: https.ServerOptions = {};
|
|
974
|
+
if (sslCa) {
|
|
975
|
+
try {
|
|
976
|
+
options.ca = fs.readFileSync(sslCa);
|
|
977
|
+
} catch (error) {
|
|
978
|
+
throw new Error(
|
|
979
|
+
`Failed to read CA file '${sslCa}': ${(error as Error).message}`,
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
if (sslCert) {
|
|
984
|
+
try {
|
|
985
|
+
options.cert = fs.readFileSync(sslCert);
|
|
986
|
+
} catch (error) {
|
|
987
|
+
throw new Error(
|
|
988
|
+
`Failed to read certificate file '${sslCert}': ${(error as Error).message}`,
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
if (sslKey) {
|
|
993
|
+
try {
|
|
994
|
+
options.key = fs.readFileSync(sslKey);
|
|
995
|
+
} catch (error) {
|
|
996
|
+
throw new Error(
|
|
997
|
+
`Failed to read key file '${sslKey}': ${(error as Error).message}`,
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
httpServer = https.createServer(options, requestListener);
|
|
1002
|
+
} else {
|
|
1003
|
+
httpServer = http.createServer(requestListener);
|
|
1004
|
+
}
|
|
962
1005
|
|
|
963
1006
|
await new Promise((resolve) => {
|
|
964
1007
|
httpServer.listen(port, host, () => {
|
package/tsconfig.json
CHANGED