import { expect, test } from "bun:test"; import { mkdtempSync, writeFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; import { loadTargetRegistry } from "./target-profiles"; test("profile registry rejects unsafe or ambiguous configuration", () => { expect(() => loadTargetRegistry({ MCP_DEFAULT_TARGET_PROFILE: "tenant-a" })).toThrow(/requires MCP_TARGET_PROFILES_FILE/); const dir = mkdtempSync(join(tmpdir(), "dbk-mcp-profiles-")); const file = join(dir, "profiles.json"); const base = { id: "tenant-a", displayName: "Tenant A", kind: "production", baseUrl: "https://a.example/api", usernameEnv: "A_USER", passwordEnv: "A_PASS" }; const env = { MCP_TARGET_PROFILES_FILE: file, MCP_DEFAULT_TARGET_PROFILE: "tenant-a", A_USER: "alice", A_PASS: "secret", B_USER: "bob", B_PASS: "secret2" }; try { writeFileSync(file, JSON.stringify({ profiles: [base] })); expect(loadTargetRegistry(env)?.profiles[0]?.id).toBe("tenant-a"); writeFileSync(file, JSON.stringify({ profiles: [base, base] })); expect(() => loadTargetRegistry(env)).toThrow(/duplicated/); writeFileSync(file, JSON.stringify({ profiles: [{ ...base, baseUrl: "http://customer.example/api" }] })); expect(() => loadTargetRegistry(env)).toThrow(/HTTPS/); for (const baseUrl of ["https://a.example/api?", "https://a.example/api#"]) { writeFileSync(file, JSON.stringify({ profiles: [{ ...base, baseUrl }] })); expect(() => loadTargetRegistry(env)).toThrow(/query, or fragment/); } writeFileSync(file, JSON.stringify({ profiles: [base, { ...base, id: "tenant-b", baseUrl: "https://A.EXAMPLE:443/api/", usernameEnv: "B_USER", passwordEnv: "B_PASS" }] })); expect(() => loadTargetRegistry(env)).toThrow(/duplicated/); writeFileSync(file, JSON.stringify({ profiles: [{ ...base, password: "literal" }] })); expect(() => loadTargetRegistry(env)).toThrow(/literal credentials/); writeFileSync(file, JSON.stringify({ profiles: [base] })); expect(() => loadTargetRegistry({ ...env, A_PASS: "", ADMIN_PASSWORD: "legacy-fallback" })).toThrow(/missing credential/); expect(() => loadTargetRegistry({ ...env, MCP_DEFAULT_TARGET_PROFILE: "missing" })).toThrow(/MCP_DEFAULT_TARGET_PROFILE/); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("MCP switches between two backends without sharing credentials or tokens", async () => { const dir = mkdtempSync(join(tmpdir(), "dbk-mcp-protocol-")); const file = join(dir, "profiles.json"); const seenA: Array<{ path: string; auth: string; username?: string; password?: string }> = []; const seenB: Array<{ path: string; auth: string; username?: string; password?: string }> = []; const bSettingsBodies: unknown[] = []; let holdA = false; let signalAStarted: (() => void) | undefined; let releaseA: (() => void) | undefined; const aStarted = new Promise((resolve) => { signalAStarted = resolve; }); const aReleased = new Promise((resolve) => { releaseA = resolve; }); const createBackend = (seen: typeof seenA, token: string) => Bun.serve({ port: 0, fetch: async (request) => { const path = new URL(request.url).pathname; if (path === "/api/auth/login") { const body = await request.json() as { username: string; password: string }; seen.push({ path, auth: request.headers.get("authorization") ?? "", ...body }); return Response.json({ token }); } seen.push({ path, auth: request.headers.get("authorization") ?? "" }); if (token === "token-b" && path === "/api/settings" && request.method === "PUT") { const body = await request.json(); bSettingsBodies.push(body); return Response.json(body); } if (token === "token-a" && path === "/api/products/brands" && holdA) { signalAStarted?.(); await aReleased; } if (path === "/api/products/brands") return Response.json([{ id: token }]); return new Response("Not found", { status: 404 }); }, }); const a = createBackend(seenA, "token-a"); const b = createBackend(seenB, "token-b"); let client: Client | undefined; try { writeFileSync(file, JSON.stringify({ profiles: [ { id: "tenant-a", displayName: "Tenant A", kind: "local", baseUrl: `http://localhost:${a.port}/api`, usernameEnv: "A_USER", passwordEnv: "A_PASS" }, { id: "tenant-b", displayName: "Tenant B", kind: "local", baseUrl: `http://localhost:${b.port}/api`, usernameEnv: "B_USER", passwordEnv: "B_PASS" }, ] })); const env = { ...process.env, MCP_TARGET_PROFILES_FILE: file, MCP_DEFAULT_TARGET_PROFILE: "tenant-a", A_USER: "alice", A_PASS: "pass-a", B_USER: "bob", B_PASS: "pass-b" } as Record; const transport = new StdioClientTransport({ command: process.execPath, args: [join(import.meta.dir, "index.ts")], cwd: import.meta.dir, env, stderr: "pipe" }); client = new Client({ name: "profile-test", version: "1.0.0" }); await client.connect(transport); const tools = (await client.listTools()).tools; expect(tools.some((tool) => tool.name === "list_target_profiles")).toBe(true); expect(tools.some((tool) => tool.name === "select_target_profile")).toBe(true); expect(tools.some((tool) => tool.name === "upload_product_image_from_url")).toBe(false); expect(tools.find((tool) => tool.name === "list_product_brands")?.inputSchema.required).toContain("expectedProfileId"); expect(tools.find((tool) => tool.name === "update_brand_name")?.inputSchema.required).toContain("expectedProfileId"); const listed = await client.callTool({ name: "list_target_profiles", arguments: {} }); expect(JSON.stringify(listed)).not.toContain("pass-a"); expect(JSON.stringify(listed)).not.toContain("pass-b"); const missing = await client.callTool({ name: "list_product_brands", arguments: {} }); expect(missing.isError).toBe(true); const blockedUpload = await client.callTool({ name: "upload_product_image_from_url", arguments: { expectedProfileId: "tenant-a", imageUrl: "https://example.com/photo.jpg", productId: "p1" } }); expect(blockedUpload.isError).toBe(true); expect(JSON.stringify(blockedUpload)).toContain("PROFILE_TOOL_UNAVAILABLE"); expect(seenA.filter((item) => item.path === "/api/products/brands")).toHaveLength(0); const first = await client.callTool({ name: "list_product_brands", arguments: { expectedProfileId: "tenant-a" } }); expect(first.structuredContent).toEqual({ data: [{ id: "token-a" }] }); const legacy = await client.callTool({ name: "set_target_environment", arguments: { env: "production", customUrl: "https://evil.example/api" } }); expect(legacy.isError).toBe(true); expect((await client.callTool({ name: "get_target_environment", arguments: {} })).structuredContent).toMatchObject({ activeProfile: { id: "tenant-a" } }); const unknown = await client.callTool({ name: "select_target_profile", arguments: { profileId: "unknown" } }); expect(unknown.isError).toBe(true); await client.callTool({ name: "select_target_profile", arguments: { profileId: "tenant-b" } }); const staleRead = await client.callTool({ name: "list_product_brands", arguments: { expectedProfileId: "tenant-a" } }); expect(staleRead.isError).toBe(true); const staleWrite = await client.callTool({ name: "update_brand_name", arguments: { expectedProfileId: "tenant-a", brandName: "Wrong tenant" } }); expect(staleWrite.isError).toBe(true); expect(seenB).toHaveLength(0); // no login, stale read, or stale write reached backend const second = await client.callTool({ name: "list_product_brands", arguments: { expectedProfileId: "tenant-b" } }); expect(second.structuredContent).toEqual({ data: [{ id: "token-b" }] }); const validWrite = await client.callTool({ name: "update_brand_name", arguments: { expectedProfileId: "tenant-b", brandName: "Tenant B" } }); expect(validWrite.isError).toBeFalsy(); expect(bSettingsBodies).toEqual([{ brandName: "Tenant B" }]); await client.callTool({ name: "select_target_profile", arguments: { profileId: "tenant-a" } }); await client.callTool({ name: "list_product_brands", arguments: { expectedProfileId: "tenant-a" } }); holdA = true; const inFlight = client.callTool({ name: "list_product_brands", arguments: { expectedProfileId: "tenant-a" } }); await aStarted; const switching = client.callTool({ name: "select_target_profile", arguments: { profileId: "tenant-b" } }); releaseA?.(); expect((await inFlight).structuredContent).toEqual({ data: [{ id: "token-a" }] }); expect((await switching).structuredContent).toMatchObject({ activeProfile: { id: "tenant-b" } }); expect((await client.callTool({ name: "list_product_brands", arguments: { expectedProfileId: "tenant-b" } })).structuredContent).toEqual({ data: [{ id: "token-b" }] }); expect(seenA.filter((item) => item.path === "/api/auth/login")).toEqual([{ path: "/api/auth/login", auth: "", username: "alice", password: "pass-a" }]); expect(seenB.filter((item) => item.path === "/api/auth/login")).toEqual([{ path: "/api/auth/login", auth: "", username: "bob", password: "pass-b" }]); expect(seenA.filter((item) => item.path === "/api/products/brands").every((item) => item.auth === "Bearer token-a")).toBe(true); expect(seenB.filter((item) => item.path === "/api/products/brands").every((item) => item.auth === "Bearer token-b")).toBe(true); await client.close(); client = undefined; const restartTransport = new StdioClientTransport({ command: process.execPath, args: [join(import.meta.dir, "index.ts")], cwd: import.meta.dir, env, stderr: "pipe" }); client = new Client({ name: "profile-restart-test", version: "1.0.0" }); await client.connect(restartTransport); expect((await client.callTool({ name: "get_target_environment", arguments: {} })).structuredContent).toMatchObject({ mode: "profiles", activeProfile: { id: "tenant-a" } }); } finally { await client?.close(); a.stop(); b.stop(); rmSync(dir, { recursive: true, force: true }); } });