Skip to content

Commit 0392d41

Browse files
cliffhallclaude
andcommitted
test(servers): add e2e check that settings.headers reach upstream MCP
Stitches the full v2 pipeline together in one assertion: browser-style RemoteClientTransport → POST /api/mcp/connect (settings in body) → backend createTransportNode applies settings.headers to requestInit → SDK StreamableHTTPClientTransport → upstream MCP test server Captures the upstream HTTP request via FetchRequestLogState and asserts both `X-Tenant` and `X-Trace` are present on the outbound POST. Catches any regression that breaks the wire between the form and the upstream server in a single integration test, instead of the previous coverage which exercised each hop in isolation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 777a6e5 commit 0392d41

1 file changed

Lines changed: 57 additions & 0 deletions

File tree

clients/web/src/test/integration/mcp/remote/transport.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,63 @@ describe("Remote transport e2e", () => {
386386
await client.disconnect();
387387
}
388388
});
389+
390+
it("end-to-end: settings.headers reach the upstream MCP server (browser → backend → upstream)", async () => {
391+
mcpHttpServer = createTestServerHttp({
392+
serverInfo: createTestServerInfo(),
393+
tools: [createEchoTool()],
394+
serverType: "streamable-http",
395+
});
396+
await mcpHttpServer.start();
397+
398+
const { baseUrl, server, authToken } = await startRemoteServer(0);
399+
remoteServer = server;
400+
401+
const config: MCPServerConfig = {
402+
type: "streamable-http",
403+
url: mcpHttpServer.url,
404+
};
405+
const serverSettings = {
406+
headers: [
407+
{ key: "X-Tenant", value: "acme" },
408+
{ key: "X-Trace", value: "abc123" },
409+
],
410+
metadata: [],
411+
connectionTimeout: 0,
412+
requestTimeout: 0,
413+
};
414+
415+
const createTransport = createRemoteTransport({ baseUrl, authToken });
416+
const client = new InspectorClient(config, {
417+
environment: { transport: createTransport },
418+
serverSettings,
419+
});
420+
const fetchRequestLogState = new FetchRequestLogState(client);
421+
422+
try {
423+
await client.connect();
424+
await client.listTools();
425+
426+
// Backend-side fetch tracking sees the upstream HTTP request with the
427+
// settings.headers applied — this is the e2e contract from
428+
// `ServerSettingsForm` → on-disk `settings.headers` → backend
429+
// `createTransportNode` → SDK `requestInit.headers` → upstream wire.
430+
const fetchRequests = fetchRequestLogState.getFetchRequests();
431+
expect(fetchRequests.length).toBeGreaterThan(0);
432+
const postRequest = fetchRequests.find((r) => r.method === "POST");
433+
expect(postRequest).toBeDefined();
434+
const lowered: Record<string, string> = {};
435+
for (const [k, v] of Object.entries(
436+
postRequest?.requestHeaders ?? {},
437+
)) {
438+
lowered[k.toLowerCase()] = v;
439+
}
440+
expect(lowered["x-tenant"]).toBe("acme");
441+
expect(lowered["x-trace"]).toBe("abc123");
442+
} finally {
443+
await client.disconnect();
444+
}
445+
});
389446
});
390447

391448
describe("authentication", () => {

0 commit comments

Comments
 (0)