diff --git a/services/ai-workspace-hub/src/server.mjs b/services/ai-workspace-hub/src/server.mjs index 71867c2..624528e 100644 --- a/services/ai-workspace-hub/src/server.mjs +++ b/services/ai-workspace-hub/src/server.mjs @@ -60,6 +60,8 @@ app.post("/api/ai-workspace/hub/v1/agents/:pairingCode/dispatch", requireInterna res.json({ ok: true, requestId: dispatch.requestId }); }); +app.use("/api/ai-workspace/hub/v1/ndc-agent-mcp/:pairingCode", asyncRoute(proxyNdcAgentMcp)); + app.use((error, _req, res, _next) => { const status = Number(error?.status || 500); res.status(status >= 400 && status < 600 ? status : 500).json({ @@ -218,6 +220,42 @@ function dispatchAgent(pairingCodeRaw, command, payload = {}, timeoutMs = 30000, return { requestId }; } +async function proxyNdcAgentMcp(req, res) { + if (!config.engineInternalUrl || !config.engineInternalAccessToken) { + res.status(503).json({ ok: false, error: "engine_proxy_not_configured" }); + return; + } + const pairingCode = cleanPairingCode(req.params.pairingCode); + const agent = agentsByCode.get(pairingCode); + if (!agent || !isAgentOnline(agent)) { + res.status(503).json({ ok: false, error: "bridge_agent_offline" }); + return; + } + + const marker = `/api/ai-workspace/hub/v1/ndc-agent-mcp/${encodeURIComponent(req.params.pairingCode)}`; + const suffix = String(req.originalUrl || "").startsWith(marker) + ? String(req.originalUrl || "").slice(marker.length) + : String(req.url || ""); + const targetUrl = `${config.engineInternalUrl.replace(/\/+$/, "")}/api/ndc-agent-mcp${suffix || ""}`; + const method = String(req.method || "GET").toUpperCase(); + const hasBody = !["GET", "HEAD"].includes(method); + const upstream = await fetch(targetUrl, { + method, + redirect: "manual", + headers: { + Accept: "application/json", + Authorization: `Bearer ${config.engineInternalAccessToken}`, + ...(hasBody ? { "Content-Type": "application/json" } : {}), + }, + ...(hasBody ? { body: JSON.stringify(req.body || {}) } : {}), + }); + const contentType = upstream.headers.get("content-type") || "application/json; charset=utf-8"; + const text = await upstream.text(); + res.status(upstream.status); + res.setHeader("content-type", contentType); + res.send(text); +} + function startAgentRequest(pairingCodeRaw, command, payload = {}, timeoutMs = 30000, options = {}) { const pairingCode = cleanPairingCode(pairingCodeRaw); const agent = agentsByCode.get(pairingCode); @@ -432,6 +470,8 @@ function readConfig() { port: Number(process.env.PORT || 18081), wsPath: process.env.AI_WORKSPACE_HUB_WS_PATH || DEFAULT_WS_PATH, internalAccessTokens, + engineInternalUrl: cleanString(process.env.NODEDC_ENGINE_INTERNAL_URL, 1000).replace(/\/+$/, ""), + engineInternalAccessToken: cleanString(process.env.NODEDC_INTERNAL_ACCESS_TOKEN, 1000), }; }