Files
2026-07-13 13:39:12 +08:00

182 lines
5.9 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { createMcpServer } from "../server.ts";
import {
getMcpHttpAuthHeadersForInternalFetch,
withMcpHttpAuthContext,
} from "../httpAuthContext.ts";
vi.mock("../audit.ts", () => ({
logToolCall: vi.fn().mockResolvedValue(undefined),
}));
describe("MCP HTTP auth context", () => {
it("forwards bearer and cookie credentials to in-process internal fetches", async () => {
const request = new Request("http://localhost/api/mcp/stream", {
headers: {
Authorization: "Bearer manage-key",
Cookie: "auth_token=session-token",
},
});
const headers = await withMcpHttpAuthContext(request, async () =>
getMcpHttpAuthHeadersForInternalFetch()
);
expect(headers).toEqual({
Authorization: "Bearer manage-key",
Cookie: "auth_token=session-token",
});
});
it("forwards Anthropic-style x-api-key only with its contract header", async () => {
const request = new Request("http://localhost/api/mcp/stream", {
headers: {
"x-api-key": "manage-key",
"anthropic-version": "2023-06-01",
},
});
const headers = await withMcpHttpAuthContext(request, async () =>
getMcpHttpAuthHeadersForInternalFetch()
);
expect(headers).toEqual({
"x-api-key": "manage-key",
"anthropic-version": "2023-06-01",
});
});
it("does not forward bare x-api-key without anthropic-version", async () => {
const request = new Request("http://localhost/api/mcp/stream", {
headers: { "x-api-key": "placeholder" },
});
const headers = await withMcpHttpAuthContext(request, async () =>
getMcpHttpAuthHeadersForInternalFetch()
);
expect(headers).toEqual({});
});
it("does not leak auth context outside the wrapped request", async () => {
const request = new Request("http://localhost/api/mcp/stream", {
headers: { Authorization: "Bearer manage-key" },
});
await withMcpHttpAuthContext(request, async () => {
expect(getMcpHttpAuthHeadersForInternalFetch()).toEqual({
Authorization: "Bearer manage-key",
});
});
expect(getMcpHttpAuthHeadersForInternalFetch()).toEqual({});
});
it("forwards request auth through registered core tools", async () => {
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ combos: [] }),
});
vi.stubGlobal("fetch", fetchMock);
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
const server = createMcpServer();
await server.connect(serverTransport);
const client = new Client({ name: "test-client", version: "1.0.0" });
await client.connect(clientTransport);
try {
const request = new Request("http://localhost/api/mcp/stream", {
headers: { Authorization: "Bearer manage-key" },
});
await withMcpHttpAuthContext(request, () =>
client.callTool({ name: "omniroute_list_combos", arguments: {} })
);
expect(fetchMock).toHaveBeenCalledWith(
expect.stringContaining("/api/combos"),
expect.objectContaining({
headers: expect.objectContaining({ Authorization: "Bearer manage-key" }),
})
);
} finally {
await client.close();
vi.unstubAllGlobals();
}
});
it("forwarded caller auth wins over the OMNIROUTE_API_KEY env fallback (#5819)", async () => {
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ combos: [] }),
});
vi.stubGlobal("fetch", fetchMock);
vi.stubEnv("OMNIROUTE_API_KEY", "env-fallback-key");
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
const server = createMcpServer();
await server.connect(serverTransport);
const client = new Client({ name: "test-client", version: "1.0.0" });
await client.connect(clientTransport);
try {
const request = new Request("http://localhost/api/mcp/stream", {
headers: { Authorization: "Bearer manage-key" },
});
await withMcpHttpAuthContext(request, () =>
client.callTool({ name: "omniroute_list_combos", arguments: {} })
);
// The per-caller forwarded identity must win over the static env fallback.
expect(fetchMock).toHaveBeenCalledWith(
expect.stringContaining("/api/combos"),
expect.objectContaining({
headers: expect.objectContaining({ Authorization: "Bearer manage-key" }),
})
);
const sentHeaders = fetchMock.mock.calls[0][1].headers as Record<string, string>;
expect(sentHeaders.Authorization).not.toBe("Bearer env-fallback-key");
} finally {
await client.close();
vi.unstubAllEnvs();
vi.unstubAllGlobals();
}
});
it("forwards request auth through advanced tool apiFetch", async () => {
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ entries: 2, hitRate: 0.5 }),
});
vi.stubGlobal("fetch", fetchMock);
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
const server = createMcpServer();
await server.connect(serverTransport);
const client = new Client({ name: "test-client", version: "1.0.0" });
await client.connect(clientTransport);
try {
const request = new Request("http://localhost/api/mcp/stream", {
headers: { Authorization: "Bearer manage-key" },
});
await withMcpHttpAuthContext(request, () =>
client.callTool({ name: "omniroute_cache_stats", arguments: {} })
);
expect(fetchMock).toHaveBeenCalledWith(
expect.stringContaining("/api/cache"),
expect.objectContaining({
headers: expect.objectContaining({ Authorization: "Bearer manage-key" }),
})
);
} finally {
await client.close();
vi.unstubAllGlobals();
}
});
});