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/jsr.json CHANGED
@@ -3,5 +3,5 @@
3
3
  "include": ["src/index.ts", "src/bin/mcp-proxy.ts"],
4
4
  "license": "MIT",
5
5
  "name": "@punkpeye/mcp-proxy",
6
- "version": "5.12.5"
6
+ "version": "6.1.9"
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-proxy",
3
- "version": "5.12.5",
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/node22": "^22.0.5",
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",
@@ -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 <command> [args...]", "Run a command with MCP arguments")
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
- // Determine the final command and args
107
- if (!argv.command) {
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
- const finalCommand = argv.command;
121
+ let finalCommand: string;
122
+ let finalArgs: string[];
112
123
 
113
- // If -- separator was used, args after -- are in argv["--"], otherwise use parsed args
114
- const finalArgs = (argv["--"] as string[]) || argv.args;
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
+ });
@@ -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 httpServer = http.createServer(async (req, res) => {
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
@@ -1,5 +1,5 @@
1
1
  {
2
- "extends": "@tsconfig/node22/tsconfig.json",
2
+ "extends": "@tsconfig/node24/tsconfig.json",
3
3
  "compilerOptions": {
4
4
  "noEmit": true,
5
5
  "noUnusedLocals": true,