Skip to content

Commit 9ff7a3b

Browse files
authored
Merge pull request #19 from zacklavin11/fix/prefixed-mcp-path
Accept tunnel-prefixed MCP paths
2 parents b72ebe0 + aabb2cd commit 9ff7a3b

2 files changed

Lines changed: 71 additions & 2 deletions

File tree

src/mcp-server.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const COMMAND_TIMEOUT = 30_000;
1212
const RUN_COMMAND_LOOP_SUPPRESSION_MS = 60_000;
1313
const PERMISSION_MODE = normalizePermissionMode(process.env.POKE_GATE_PERMISSION_MODE);
1414
const SANDBOX_EXEC_PATH = "/usr/bin/sandbox-exec";
15+
const TUNNEL_MCP_PATH_RE = /^\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\/mcp$/;
1516

1617
let logEnabled = false;
1718

@@ -224,6 +225,11 @@ function extractSessionId(req) {
224225
return "default";
225226
}
226227

228+
export function normalizeMcpPathname(pathname) {
229+
if (pathname === "/mcp" || TUNNEL_MCP_PATH_RE.test(pathname)) return "/mcp";
230+
return pathname;
231+
}
232+
227233
function buildApprovalResponse(name, cleanArgs, approval) {
228234
const summary = name === "run_command"
229235
? `Run command: ${cleanArgs.command}`
@@ -879,8 +885,9 @@ export function startMcpServer(port = 0) {
879885
}
880886

881887
const url = new URL(req.url, "http://localhost");
888+
const pathname = normalizeMcpPathname(url.pathname);
882889

883-
if (url.pathname === "/mcp" && req.method === "GET") {
890+
if (pathname === "/mcp" && req.method === "GET") {
884891
const accept = req.headers.accept || "";
885892
if (accept.includes("text/event-stream")) {
886893
writeMcpEventStream(req, res);
@@ -891,7 +898,7 @@ export function startMcpServer(port = 0) {
891898
return;
892899
}
893900

894-
if (url.pathname === "/mcp" && req.method === "POST") {
901+
if (pathname === "/mcp" && req.method === "POST") {
895902
try {
896903
const body = await readBody(req);
897904
const parsed = JSON.parse(body);

test/mcp-server-transport.test.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import test from "node:test";
44

55
import { startMcpServer } from "../src/mcp-server.js";
66

7+
const TUNNEL_CONNECTION_ID = "64574786-7a08-4074-ab67-8078a40b7ba2";
8+
79
function request({ port, method = "GET", path = "/", headers = {}, body }) {
810
return new Promise((resolve, reject) => {
911
const req = http.request({ hostname: "127.0.0.1", port, method, path, headers }, (res) => {
@@ -68,3 +70,63 @@ test("MCP GET supports event stream transport", async () => {
6870
httpServer.close();
6971
}
7072
});
73+
74+
test("MCP POST accepts Poke tunnel connection-id prefix", async () => {
75+
const { httpServer, port } = await startMcpServer();
76+
try {
77+
const { res, body } = await request({
78+
port,
79+
method: "POST",
80+
path: `/${TUNNEL_CONNECTION_ID}/mcp`,
81+
headers: {
82+
"Content-Type": "application/json",
83+
"Mcp-Session-Id": "session-prefixed-post",
84+
},
85+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list", params: {} }),
86+
});
87+
88+
assert.equal(res.statusCode, 200);
89+
assert.equal(res.headers["mcp-session-id"], "session-prefixed-post");
90+
assert.equal(JSON.parse(body).result.tools.length > 0, true);
91+
} finally {
92+
httpServer.close();
93+
}
94+
});
95+
96+
test("MCP GET accepts Poke tunnel connection-id prefix", async () => {
97+
const { httpServer, port } = await startMcpServer();
98+
try {
99+
const { res, body } = await request({
100+
port,
101+
path: `/${TUNNEL_CONNECTION_ID}/mcp`,
102+
headers: {
103+
Accept: "text/event-stream",
104+
"Mcp-Session-Id": "session-prefixed-get",
105+
},
106+
});
107+
108+
assert.equal(res.statusCode, 200);
109+
assert.equal(res.headers["content-type"], "text/event-stream");
110+
assert.equal(res.headers["mcp-session-id"], "session-prefixed-get");
111+
assert.match(body, /: connected/);
112+
} finally {
113+
httpServer.close();
114+
}
115+
});
116+
117+
test("MCP route rejects non-connection-id prefixes", async () => {
118+
const { httpServer, port } = await startMcpServer();
119+
try {
120+
const { res } = await request({
121+
port,
122+
method: "POST",
123+
path: "/not-a-connection/mcp",
124+
headers: { "Content-Type": "application/json" },
125+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list", params: {} }),
126+
});
127+
128+
assert.equal(res.statusCode, 404);
129+
} finally {
130+
httpServer.close();
131+
}
132+
});

0 commit comments

Comments
 (0)