mcp-proxy 5.12.5 → 6.1.9
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 +26 -20
- package/dist/bin/mcp-proxy.mjs +36 -8
- 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 +2 -2
- package/src/bin/mcp-proxy.ts +38 -9
- 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.9",
|
|
4
4
|
"main": "dist/index.mjs",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "tsdown",
|
|
@@ -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,9 +22,8 @@ if (!("EventSource" in global)) {
|
|
|
22
22
|
|
|
23
23
|
const argv = await yargs(hideBin(process.argv))
|
|
24
24
|
.scriptName("mcp-proxy")
|
|
25
|
-
.command("$0
|
|
25
|
+
.command("$0 [command] [args...]", "Proxy an MCP stdio server over HTTP")
|
|
26
26
|
.positional("command", {
|
|
27
|
-
demandOption: true,
|
|
28
27
|
describe: "The command to run",
|
|
29
28
|
type: "string",
|
|
30
29
|
})
|
|
@@ -33,6 +32,7 @@ const argv = await yargs(hideBin(process.argv))
|
|
|
33
32
|
describe: "The arguments to pass to the command",
|
|
34
33
|
type: "string",
|
|
35
34
|
})
|
|
35
|
+
.usage("$0 [options] -- <command> [args...]\n $0 <command> [args...]")
|
|
36
36
|
.env("MCP_PROXY")
|
|
37
37
|
.parserConfiguration({
|
|
38
38
|
"populate--": true,
|
|
@@ -88,6 +88,18 @@ const argv = await yargs(hideBin(process.argv))
|
|
|
88
88
|
describe: "The SSE endpoint to listen on",
|
|
89
89
|
type: "string",
|
|
90
90
|
},
|
|
91
|
+
sslCa: {
|
|
92
|
+
describe: "Filename to override the trusted CA certificates",
|
|
93
|
+
type: "string",
|
|
94
|
+
},
|
|
95
|
+
sslCert: {
|
|
96
|
+
describe: "Cert chains filename in PEM format",
|
|
97
|
+
type: "string",
|
|
98
|
+
},
|
|
99
|
+
sslKey: {
|
|
100
|
+
describe: "Private keys filename in PEM format",
|
|
101
|
+
type: "string",
|
|
102
|
+
},
|
|
91
103
|
stateless: {
|
|
92
104
|
default: false,
|
|
93
105
|
describe:
|
|
@@ -103,15 +115,29 @@ const argv = await yargs(hideBin(process.argv))
|
|
|
103
115
|
.help()
|
|
104
116
|
.parseAsync();
|
|
105
117
|
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
throw new Error("No command specified");
|
|
109
|
-
}
|
|
118
|
+
// If -- separator was used, everything after -- is the command and its args
|
|
119
|
+
const dashDashArgs = argv["--"] as string[] | undefined;
|
|
110
120
|
|
|
111
|
-
|
|
121
|
+
let finalCommand: string;
|
|
122
|
+
let finalArgs: string[];
|
|
112
123
|
|
|
113
|
-
|
|
114
|
-
|
|
124
|
+
if (dashDashArgs && dashDashArgs.length > 0) {
|
|
125
|
+
// -- was used: first item after -- is command, rest are args
|
|
126
|
+
[finalCommand, ...finalArgs] = dashDashArgs;
|
|
127
|
+
} else if (argv.command) {
|
|
128
|
+
// No -- used: use positional command and args
|
|
129
|
+
finalCommand = argv.command as string;
|
|
130
|
+
finalArgs = (argv.args as string[]) || [];
|
|
131
|
+
} else {
|
|
132
|
+
console.error("Error: No command specified.");
|
|
133
|
+
console.error("Usage: mcp-proxy [options] -- <command> [args...]");
|
|
134
|
+
console.error(" or: mcp-proxy <command> [args...]");
|
|
135
|
+
console.error("");
|
|
136
|
+
console.error("Examples:");
|
|
137
|
+
console.error(" mcp-proxy --port 8080 -- node server.js --port 3000");
|
|
138
|
+
console.error(" mcp-proxy node server.js");
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
115
141
|
|
|
116
142
|
const connect = async (client: Client) => {
|
|
117
143
|
const transport = new StdioClientTransport({
|
|
@@ -180,6 +206,9 @@ const proxy = async () => {
|
|
|
180
206
|
argv.server && argv.server !== "sse"
|
|
181
207
|
? null
|
|
182
208
|
: (argv.sseEndpoint ?? argv.endpoint),
|
|
209
|
+
sslCa: argv.sslCa,
|
|
210
|
+
sslCert: argv.sslCert,
|
|
211
|
+
sslKey: argv.sslKey,
|
|
183
212
|
stateless: argv.stateless,
|
|
184
213
|
streamEndpoint:
|
|
185
214
|
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