feat(foundry): add managed data consumers and agent settings
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
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 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 });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user