import assert from "node:assert/strict"; import { execFile } from "node:child_process"; import { createServer } from "node:http"; import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { promisify } from "node:util"; import test from "node:test"; const execFileAsync = promisify(execFile); const installer = new URL("../server/assets/foundry-agent-npm/bin/nodedc-foundry-codex-agent.mjs", import.meta.url); async function listen(server) { await new Promise((resolve, reject) => { server.once("error", reject); server.listen(0, "127.0.0.1", resolve); }); return `http://127.0.0.1:${server.address().port}`; } test("Foundry installer preserves unrelated MCP entries and installs two independent servers", async () => { let baseUrl = ""; const server = createServer(async (request, response) => { if (request.url === "/api/foundry-agent/v1/setup/redeem") { response.writeHead(200, { "content-type": "application/json" }); response.end(JSON.stringify({ ok: true, foundry: { serverName: "nodedc_module_foundry", mcpUrl: `${baseUrl}/foundry-mcp`, token: "foundry-device-token" }, ontology: { serverName: "nodedc_ontology", mcpUrl: `${baseUrl}/ontology-mcp`, token: "ontology-device-token", readOnly: true }, })); return; } if (request.url === "/foundry-mcp" || request.url === "/ontology-mcp") { const chunks = []; for await (const chunk of request) chunks.push(chunk); const message = JSON.parse(Buffer.concat(chunks).toString("utf8")); const tools = request.url === "/foundry-mcp" ? [{ name: "foundry_status" }, { name: "foundry_create_application" }] : [{ name: "ontology_status" }, { name: "ontology_search" }]; response.writeHead(200, { "content-type": "application/json" }); response.end(JSON.stringify({ jsonrpc: "2.0", id: message.id, result: message.method === "tools/list" ? { tools } : { protocolVersion: "2025-06-18", capabilities: { tools: {} }, serverInfo: { name: "test", version: "1" } }, })); return; } response.writeHead(404); response.end(); }); baseUrl = await listen(server); const codexHome = await mkdtemp(path.join(os.tmpdir(), "nodedc-foundry-installer-")); await mkdir(codexHome, { recursive: true }); const original = [ "model = \"gpt-5\"", "", "[mcp_servers.nodedc_engine_agent]", "url = \"https://engine.example/mcp\"", "", "[mcp_servers.nodedc_module_foundry]", "url = \"https://stale.example/foundry\"", "", "[mcp_servers.nodedc_ontology]", "url = \"https://stale.example/ontology\"", "", ].join("\n"); await writeFile(path.join(codexHome, "config.toml"), original, "utf8"); try { const result = await execFileAsync(process.execPath, [installer.pathname, "--server", baseUrl, "--code", "test-code", "--codex-home", codexHome]); assert.match(result.stdout, /Foundry MCP smoke tools: 2/); assert.match(result.stdout, /Ontology MCP smoke tools: 2/); const config = await readFile(path.join(codexHome, "config.toml"), "utf8"); assert.match(config, /\[mcp_servers\.nodedc_engine_agent\]/); assert.equal((config.match(/\[mcp_servers\.nodedc_module_foundry\]/g) || []).length, 1); assert.equal((config.match(/\[mcp_servers\.nodedc_ontology\]/g) || []).length, 1); assert.match(config, new RegExp(`${baseUrl.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\/foundry-mcp`)); assert.match(config, /Bearer foundry-device-token/); assert.match(config, /Bearer ontology-device-token/); assert.equal(config.includes("stale.example"), false); const backup = await readFile(path.join(codexHome, "config.toml.nodedc-foundry-agent.bak"), "utf8"); assert.equal(backup, original); const skill = await readFile(path.join(codexHome, "skills", "foundry-context", "SKILL.md"), "utf8"); assert.match(skill, /nodedc_module_foundry/); assert.match(skill, /nodedc_ontology/); const doctor = await execFileAsync(process.execPath, [installer.pathname, "doctor", "--codex-home", codexHome]); assert.match(doctor.stdout, /install is present/); } finally { await new Promise((resolve) => server.close(resolve)); await rm(codexHome, { recursive: true, force: true }); } });