import assert from "node:assert/strict"; import { createServer } from "node:http"; import { mkdtemp, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import test from "node:test"; import { createFoundryAgentGateway } from "./foundry-agent-gateway.mjs"; import { createFoundryAgentStore } from "./foundry-agent-store.mjs"; import { handleFoundryMcpRequest } from "./foundry-mcp.mjs"; async function listen(server) { await new Promise((resolve, reject) => { server.once("error", reject); server.listen(0, "127.0.0.1", resolve); }); const address = server.address(); return `http://127.0.0.1:${address.port}`; } async function close(server) { await new Promise((resolve) => server.close(resolve)); } async function json(response) { return await response.json(); } test("one setup command provisions independent Foundry and Ontology MCP transports", async () => { const dataRoot = await mkdtemp(path.join(os.tmpdir(), "nodedc-foundry-agent-gateway-")); const installerPath = path.join(dataRoot, "installer.tgz"); await writeFile(installerPath, "installer", "utf8"); let ontologyAuthorization = ""; const ontologyServer = createServer(async (request, response) => { ontologyAuthorization = String(request.headers.authorization || ""); const chunks = []; for await (const chunk of request) chunks.push(chunk); const message = JSON.parse(Buffer.concat(chunks).toString("utf8")); const result = message.method === "tools/list" ? { tools: [{ name: "ontology_status" }, { name: "ontology_search" }] } : { protocolVersion: "2025-06-18", capabilities: { tools: { listChanged: false } }, serverInfo: { name: "nodedc-ontology-core", version: "test" }, }; response.writeHead(200, { "content-type": "application/json", "mcp-protocol-version": "2025-06-18" }); response.end(JSON.stringify({ jsonrpc: "2.0", id: message.id, result })); }); const ontologyBase = await listen(ontologyServer); const store = createFoundryAgentStore({ dataRoot: path.join(dataRoot, "store") }); const gateway = createFoundryAgentGateway({ store, installerPackagePath: installerPath, ontologyCoreUrl: ontologyBase, ontologyCoreAccessToken: "platform-internal-token", }); const operations = { status: async () => ({ ok: true }), listApplications: async () => ({ applications: [] }), getApplication: async () => ({ application: null }), }; const foundryServer = createServer(async (request, response) => { const url = new URL(request.url || "/", `http://${request.headers.host}`); if (await gateway.handlePublicRequest(request, response, url)) return; if (await gateway.handleOntologyMcp(request, response, url)) return; if (url.pathname === "/api/mcp") { await handleFoundryMcpRequest(request, response, { getConfig: () => ({ capabilitySecret: "", mcpAllowedOrigins: [] }), operations, authenticateAgentToken: gateway.authenticateFoundryToken, }); return; } if (await gateway.handleManagementRequest(request, response, url, { id: "user_root", email: "dcctouch@gmail.com", displayName: "DC SUDO", })) return; response.writeHead(404); response.end(); }); const foundryBase = await listen(foundryServer); try { const createdResponse = await fetch(`${foundryBase}/api/foundry-agent-api/agents`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ name: "Foundry Codex" }), }); assert.equal(createdResponse.status, 201); const created = await json(createdResponse); const setupResponse = await fetch(`${foundryBase}/api/foundry-agent-api/agents/${created.agent.id}/setup-code`, { method: "POST" }); assert.equal(setupResponse.status, 201); const setup = await json(setupResponse); assert.match(setup.install.command, /nodedc-foundry-codex-agent/); const code = setup.install.command.match(/--code\s+(fnd_setup_[A-Za-z0-9_-]+)/)?.[1]; assert.ok(code); const redeemResponse = await fetch(`${foundryBase}/api/foundry-agent/v1/setup/redeem`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ code, deviceName: "Test Codex" }), }); assert.equal(redeemResponse.status, 200); const redeemed = await json(redeemResponse); assert.equal(redeemed.foundry.serverName, "nodedc_module_foundry"); assert.equal(redeemed.ontology.serverName, "nodedc_ontology"); assert.equal(redeemed.ontology.readOnly, true); assert.notEqual(redeemed.foundry.token, redeemed.ontology.token); const foundryListResponse = await fetch(redeemed.foundry.mcpUrl, { method: "POST", headers: { authorization: `Bearer ${redeemed.foundry.token}`, "content-type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list", params: {} }), }); assert.equal(foundryListResponse.status, 200); const foundryList = await json(foundryListResponse); assert.ok(foundryList.result.tools.some((tool) => tool.name === "foundry_create_application")); const removePinTool = foundryList.result.tools.find((tool) => tool.name === "foundry_remove_map_pin_binding"); assert.deepEqual(removePinTool.inputSchema.required, ["applicationId", "pageId", "bindingId", "idempotencyKey"]); assert.equal(removePinTool.inputSchema.additionalProperties, false); const presentationTool = foundryList.result.tools.find((tool) => tool.name === "foundry_upsert_map_presentation_profile"); assert.equal(presentationTool.inputSchema.properties.profile.additionalProperties, false); assert.ok(presentationTool.inputSchema.properties.profile.required.includes("facets")); assert.ok(presentationTool.inputSchema.properties.profile.required.includes("target")); assert.equal(Object.hasOwn(presentationTool.inputSchema.properties.profile.properties, "pin"), false); assert.equal(presentationTool.inputSchema.properties.profile.properties.label.properties.sizePx.minimum, 8); assert.equal(Object.hasOwn(presentationTool.inputSchema.properties.profile.properties.label.properties, "fontSizePx"), false); assert.deepEqual(presentationTool.inputSchema.properties.profile.properties.label.properties.mode.enum, ["subject_id", "attributes", "none"]); const subjectDetailTool = foundryList.result.tools.find((tool) => tool.name === "foundry_upsert_map_subject_detail_profile"); assert.ok(subjectDetailTool); assert.equal(subjectDetailTool.inputSchema.properties.profile.additionalProperties, false); assert.deepEqual(subjectDetailTool.inputSchema.properties.profile.required, ["id", "version", "title", "semanticTypes", "defaultTabId", "tabs"]); assert.deepEqual( subjectDetailTool.inputSchema.properties.profile.properties.tabs.items.properties.sections.items.properties.fields.items.properties.format.enum, ["text", "number", "timestamp", "boolean", "coordinate", "signal_state", "movement_state", "telemetry_readings"], ); const mapSettingsTool = foundryList.result.tools.find((tool) => tool.name === "foundry_update_map_page_settings"); assert.ok(mapSettingsTool); assert.equal(mapSettingsTool.inputSchema.properties.settings.additionalProperties, false); assert.equal(Object.hasOwn(mapSettingsTool.inputSchema.properties.settings.properties, "providerUrl"), false); const mapViewStateTool = foundryList.result.tools.find((tool) => tool.name === "foundry_save_map_page_view_state"); assert.ok(mapViewStateTool); const subjectStateSchema = mapViewStateTool.inputSchema.properties.viewState.properties.subjectStates.items; assert.deepEqual(subjectStateSchema.required, ["bindingId", "visible", "filters", "window"]); assert.equal(subjectStateSchema.properties.filters.additionalProperties.items.type, "string"); assert.equal(subjectStateSchema.properties.window.properties.rect.additionalProperties, false); const bindingTool = foundryList.result.tools.find((tool) => tool.name === "foundry_upsert_map_data_product_binding"); assert.equal(bindingTool.inputSchema.properties.binding.properties.displayName.maxLength, 120); assert.equal(bindingTool.inputSchema.properties.binding.properties.order.maximum, 10000); assert.ok(bindingTool.inputSchema.properties.binding.properties.subjectDetailProfileId); assert.ok(bindingTool.inputSchema.properties.binding.properties.aspectId); assert.ok(bindingTool.inputSchema.properties.binding.properties.joinToBindingId); const crossTokenResponse = await fetch(redeemed.ontology.mcpUrl, { method: "POST", headers: { authorization: `Bearer ${redeemed.foundry.token}`, "content-type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} }), }); assert.equal(crossTokenResponse.status, 401); const ontologyListResponse = await fetch(redeemed.ontology.mcpUrl, { method: "POST", headers: { authorization: `Bearer ${redeemed.ontology.token}`, "content-type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: 3, method: "tools/list", params: {} }), }); assert.equal(ontologyListResponse.status, 200); const ontologyList = await json(ontologyListResponse); assert.deepEqual(ontologyList.result.tools.map((tool) => tool.name), ["ontology_status", "ontology_search"]); assert.equal(ontologyAuthorization, "Bearer platform-internal-token"); const revokeResponse = await fetch(`${foundryBase}/api/foundry-agent-api/agents/${created.agent.id}/revoke`, { method: "POST" }); assert.equal(revokeResponse.status, 200); const revokedFoundryResponse = await fetch(redeemed.foundry.mcpUrl, { method: "POST", headers: { authorization: `Bearer ${redeemed.foundry.token}`, "content-type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: 4, method: "tools/list", params: {} }), }); const revokedOntologyResponse = await fetch(redeemed.ontology.mcpUrl, { method: "POST", headers: { authorization: `Bearer ${redeemed.ontology.token}`, "content-type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: 5, method: "tools/list", params: {} }), }); assert.equal(revokedFoundryResponse.status, 401); assert.equal(revokedOntologyResponse.status, 401); } finally { await close(foundryServer); await close(ontologyServer); await rm(dataRoot, { recursive: true, force: true }); } });