@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.
Files changed (3) hide show
  1. package/bin/cli.js +33 -0
  2. package/package.json +19 -0
  3. 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
+ }