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/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.4"
6
+ "version": "6.1.8"
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-proxy",
3
- "version": "5.12.4",
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.ts",
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/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,17 +22,7 @@ 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")
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
- if (!argv.command) {
108
- throw new Error("No command specified");
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
+ });
@@ -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,