Skip to content

Commit f94f2d8

Browse files
authored
Merge pull request #708 from richardkmichael/fix-630
fix: STDIO and SSE transports need MCP_FULL_PROXY_ADDRESS
2 parents 7a32c3d + 161f5d3 commit f94f2d8

File tree

3 files changed

+161
-7
lines changed

3 files changed

+161
-7
lines changed

client/src/lib/hooks/__tests__/useConnection.test.tsx

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,4 +877,129 @@ describe("useConnection", () => {
877877
});
878878
});
879879
});
880+
881+
describe("MCP_PROXY_FULL_ADDRESS Configuration", () => {
882+
beforeEach(() => {
883+
jest.clearAllMocks();
884+
// Reset the mock transport objects
885+
mockSSETransport.url = undefined;
886+
mockSSETransport.options = undefined;
887+
mockStreamableHTTPTransport.url = undefined;
888+
mockStreamableHTTPTransport.options = undefined;
889+
});
890+
891+
test("sends proxyFullAddress query parameter for stdio transport when configured", async () => {
892+
const propsWithProxyFullAddress = {
893+
...defaultProps,
894+
transportType: "stdio" as const,
895+
command: "test-command",
896+
args: "test-args",
897+
env: {},
898+
config: {
899+
...DEFAULT_INSPECTOR_CONFIG,
900+
MCP_PROXY_FULL_ADDRESS: {
901+
...DEFAULT_INSPECTOR_CONFIG.MCP_PROXY_FULL_ADDRESS,
902+
value: "https://example.com/inspector/mcp_proxy",
903+
},
904+
},
905+
};
906+
907+
const { result } = renderHook(() =>
908+
useConnection(propsWithProxyFullAddress),
909+
);
910+
911+
await act(async () => {
912+
await result.current.connect();
913+
});
914+
915+
// Check that the URL contains the proxyFullAddress parameter
916+
expect(mockSSETransport.url?.searchParams.get("proxyFullAddress")).toBe(
917+
"https://example.com/inspector/mcp_proxy",
918+
);
919+
});
920+
921+
test("sends proxyFullAddress query parameter for sse transport when configured", async () => {
922+
const propsWithProxyFullAddress = {
923+
...defaultProps,
924+
transportType: "sse" as const,
925+
sseUrl: "http://localhost:8080",
926+
config: {
927+
...DEFAULT_INSPECTOR_CONFIG,
928+
MCP_PROXY_FULL_ADDRESS: {
929+
...DEFAULT_INSPECTOR_CONFIG.MCP_PROXY_FULL_ADDRESS,
930+
value: "https://example.com/inspector/mcp_proxy",
931+
},
932+
},
933+
};
934+
935+
const { result } = renderHook(() =>
936+
useConnection(propsWithProxyFullAddress),
937+
);
938+
939+
await act(async () => {
940+
await result.current.connect();
941+
});
942+
943+
// Check that the URL contains the proxyFullAddress parameter
944+
expect(mockSSETransport.url?.searchParams.get("proxyFullAddress")).toBe(
945+
"https://example.com/inspector/mcp_proxy",
946+
);
947+
});
948+
949+
test("does not send proxyFullAddress parameter when MCP_PROXY_FULL_ADDRESS is empty", async () => {
950+
const propsWithEmptyProxy = {
951+
...defaultProps,
952+
transportType: "stdio" as const,
953+
command: "test-command",
954+
args: "test-args",
955+
env: {},
956+
config: {
957+
...DEFAULT_INSPECTOR_CONFIG,
958+
MCP_PROXY_FULL_ADDRESS: {
959+
...DEFAULT_INSPECTOR_CONFIG.MCP_PROXY_FULL_ADDRESS,
960+
value: "",
961+
},
962+
},
963+
};
964+
965+
const { result } = renderHook(() => useConnection(propsWithEmptyProxy));
966+
967+
await act(async () => {
968+
await result.current.connect();
969+
});
970+
971+
// Check that the URL does not contain the proxyFullAddress parameter
972+
expect(
973+
mockSSETransport.url?.searchParams.get("proxyFullAddress"),
974+
).toBeNull();
975+
});
976+
977+
test("does not send proxyFullAddress parameter for streamable-http transport", async () => {
978+
const propsWithStreamableHttp = {
979+
...defaultProps,
980+
transportType: "streamable-http" as const,
981+
sseUrl: "http://localhost:8080",
982+
config: {
983+
...DEFAULT_INSPECTOR_CONFIG,
984+
MCP_PROXY_FULL_ADDRESS: {
985+
...DEFAULT_INSPECTOR_CONFIG.MCP_PROXY_FULL_ADDRESS,
986+
value: "https://example.com/inspector/mcp_proxy",
987+
},
988+
},
989+
};
990+
991+
const { result } = renderHook(() =>
992+
useConnection(propsWithStreamableHttp),
993+
);
994+
995+
await act(async () => {
996+
await result.current.connect();
997+
});
998+
999+
// Check that streamable-http transport doesn't get proxyFullAddress parameter
1000+
expect(
1001+
mockStreamableHTTPTransport.url?.searchParams.get("proxyFullAddress"),
1002+
).toBeNull();
1003+
});
1004+
});
8801005
});

client/src/lib/hooks/useConnection.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,11 +409,20 @@ export function useConnection({
409409

410410
let mcpProxyServerUrl;
411411
switch (transportType) {
412-
case "stdio":
412+
case "stdio": {
413413
mcpProxyServerUrl = new URL(`${getMCPProxyAddress(config)}/stdio`);
414414
mcpProxyServerUrl.searchParams.append("command", command);
415415
mcpProxyServerUrl.searchParams.append("args", args);
416416
mcpProxyServerUrl.searchParams.append("env", JSON.stringify(env));
417+
418+
const proxyFullAddress = config.MCP_PROXY_FULL_ADDRESS
419+
.value as string;
420+
if (proxyFullAddress) {
421+
mcpProxyServerUrl.searchParams.append(
422+
"proxyFullAddress",
423+
proxyFullAddress,
424+
);
425+
}
417426
transportOptions = {
418427
authProvider: serverAuthProvider,
419428
eventSourceInit: {
@@ -431,10 +440,20 @@ export function useConnection({
431440
},
432441
};
433442
break;
443+
}
434444

435-
case "sse":
445+
case "sse": {
436446
mcpProxyServerUrl = new URL(`${getMCPProxyAddress(config)}/sse`);
437447
mcpProxyServerUrl.searchParams.append("url", sseUrl);
448+
449+
const proxyFullAddressSSE = config.MCP_PROXY_FULL_ADDRESS
450+
.value as string;
451+
if (proxyFullAddressSSE) {
452+
mcpProxyServerUrl.searchParams.append(
453+
"proxyFullAddress",
454+
proxyFullAddressSSE,
455+
);
456+
}
438457
transportOptions = {
439458
eventSourceInit: {
440459
fetch: (
@@ -451,6 +470,7 @@ export function useConnection({
451470
},
452471
};
453472
break;
473+
}
454474

455475
case "streamable-http":
456476
mcpProxyServerUrl = new URL(`${getMCPProxyAddress(config)}/mcp`);

server/src/index.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,6 @@ app.get(
378378
let serverTransport: Transport | undefined;
379379
try {
380380
serverTransport = await createTransport(req);
381-
console.log("Created server transport");
382381
} catch (error) {
383382
if (error instanceof SseError && error.code === 401) {
384383
console.error(
@@ -391,11 +390,16 @@ app.get(
391390
throw error;
392391
}
393392

394-
const webAppTransport = new SSEServerTransport("/message", res);
395-
console.log("Created client transport");
393+
const proxyFullAddress = (req.query.proxyFullAddress as string) || "";
394+
const prefix = proxyFullAddress || "";
395+
const endpoint = `${prefix}/message`;
396396

397+
const webAppTransport = new SSEServerTransport(endpoint, res);
397398
webAppTransports.set(webAppTransport.sessionId, webAppTransport);
399+
console.log("Created client transport");
400+
398401
serverTransports.set(webAppTransport.sessionId, serverTransport);
402+
console.log("Created server transport");
399403

400404
await webAppTransport.start();
401405

@@ -484,7 +488,7 @@ app.get(
484488
async (req, res) => {
485489
try {
486490
console.log(
487-
"New SSE connection request. NOTE: The sse transport is deprecated and has been replaced by StreamableHttp",
491+
"New SSE connection request. NOTE: The SSE transport is deprecated and has been replaced by StreamableHttp",
488492
);
489493
let serverTransport: Transport | undefined;
490494
try {
@@ -511,9 +515,14 @@ app.get(
511515
}
512516

513517
if (serverTransport) {
514-
const webAppTransport = new SSEServerTransport("/message", res);
518+
const proxyFullAddress = (req.query.proxyFullAddress as string) || "";
519+
const prefix = proxyFullAddress || "";
520+
const endpoint = `${prefix}/message`;
521+
522+
const webAppTransport = new SSEServerTransport(endpoint, res);
515523
webAppTransports.set(webAppTransport.sessionId, webAppTransport);
516524
console.log("Created client transport");
525+
517526
serverTransports.set(webAppTransport.sessionId, serverTransport!);
518527
console.log("Created server transport");
519528

0 commit comments

Comments
 (0)