@diegoaltoworks/localize-remote-mcp-server 1.0.0
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/bin/cli.js +33 -0
- package/package.json +19 -0
- package/src/proxy.js +84 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
import { createProxy } from "../src/proxy.js";
|
|
5
|
+
|
|
6
|
+
const { values, positionals } = parseArgs({
|
|
7
|
+
options: {
|
|
8
|
+
port: { type: "string", short: "p", default: "3000" },
|
|
9
|
+
help: { type: "boolean", short: "h", default: false },
|
|
10
|
+
},
|
|
11
|
+
allowPositionals: true,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
if (values.help || positionals.length === 0) {
|
|
15
|
+
console.log(`
|
|
16
|
+
Usage: localize-remote-mcp-server [options] <remote-url>
|
|
17
|
+
|
|
18
|
+
Proxy a remote HTTP MCP server to localhost.
|
|
19
|
+
|
|
20
|
+
Options:
|
|
21
|
+
-p, --port <port> Local port to listen on (default: 3000)
|
|
22
|
+
-h, --help Show this help message
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
npx @diegoaltoworks/localize-remote-mcp-server --port 6969 https://example.com/mcp
|
|
26
|
+
`);
|
|
27
|
+
process.exit(values.help ? 0 : 1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const remoteUrl = positionals[0];
|
|
31
|
+
const port = parseInt(values.port, 10);
|
|
32
|
+
|
|
33
|
+
createProxy({ remoteUrl, port });
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@diegoaltoworks/localize-remote-mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Proxy a remote HTTP MCP server to localhost",
|
|
5
|
+
"bin": {
|
|
6
|
+
"localize-remote-mcp-server": "./bin/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
14
|
+
"keywords": [
|
|
15
|
+
"mcp",
|
|
16
|
+
"proxy",
|
|
17
|
+
"model-context-protocol"
|
|
18
|
+
]
|
|
19
|
+
}
|
package/src/proxy.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
|
|
3
|
+
export function createProxy({ remoteUrl, port }) {
|
|
4
|
+
const remote = new URL(remoteUrl);
|
|
5
|
+
const localPath = remote.pathname;
|
|
6
|
+
|
|
7
|
+
const server = createServer(async (req, res) => {
|
|
8
|
+
// Only proxy requests to the MCP path
|
|
9
|
+
if (req.url !== localPath) {
|
|
10
|
+
res.writeHead(404, { "content-type": "text/plain" });
|
|
11
|
+
res.end(`Not found. MCP endpoint is at ${localPath}\n`);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// MCP Streamable HTTP uses POST and GET
|
|
16
|
+
if (req.method !== "POST" && req.method !== "GET") {
|
|
17
|
+
res.writeHead(405, { "content-type": "text/plain" });
|
|
18
|
+
res.end("Method not allowed.\n");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const body = req.method === "POST" ? await readBody(req) : undefined;
|
|
24
|
+
|
|
25
|
+
// Forward all relevant headers
|
|
26
|
+
const headers = {};
|
|
27
|
+
if (req.headers["content-type"]) {
|
|
28
|
+
headers["content-type"] = req.headers["content-type"];
|
|
29
|
+
}
|
|
30
|
+
// MCP Streamable HTTP requires accepting both JSON and SSE
|
|
31
|
+
headers["accept"] =
|
|
32
|
+
req.headers["accept"] || "application/json, text/event-stream";
|
|
33
|
+
if (req.headers["authorization"]) {
|
|
34
|
+
headers["authorization"] = req.headers["authorization"];
|
|
35
|
+
}
|
|
36
|
+
if (req.headers["mcp-session-id"]) {
|
|
37
|
+
headers["mcp-session-id"] = req.headers["mcp-session-id"];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const response = await fetch(remoteUrl, {
|
|
41
|
+
method: req.method,
|
|
42
|
+
headers,
|
|
43
|
+
body,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Forward status and headers back
|
|
47
|
+
const responseHeaders = {};
|
|
48
|
+
for (const [key, value] of response.headers) {
|
|
49
|
+
responseHeaders[key] = value;
|
|
50
|
+
}
|
|
51
|
+
res.writeHead(response.status, responseHeaders);
|
|
52
|
+
|
|
53
|
+
// Stream the response body back
|
|
54
|
+
if (response.body) {
|
|
55
|
+
const reader = response.body.getReader();
|
|
56
|
+
while (true) {
|
|
57
|
+
const { done, value } = await reader.read();
|
|
58
|
+
if (done) break;
|
|
59
|
+
res.write(value);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
res.end();
|
|
63
|
+
} catch (err) {
|
|
64
|
+
console.error("Proxy error:", err.message);
|
|
65
|
+
res.writeHead(502, { "content-type": "text/plain" });
|
|
66
|
+
res.end(`Bad gateway: ${err.message}\n`);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
server.listen(port, () => {
|
|
71
|
+
console.log(`Proxying ${remoteUrl} → http://localhost:${port}${localPath}`);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return server;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function readBody(req) {
|
|
78
|
+
return new Promise((resolve, reject) => {
|
|
79
|
+
const chunks = [];
|
|
80
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
81
|
+
req.on("end", () => resolve(Buffer.concat(chunks)));
|
|
82
|
+
req.on("error", reject);
|
|
83
|
+
});
|
|
84
|
+
}
|