feat(ops): add workspace MCP deployment overlays

This commit is contained in:
Codex
2026-07-19 12:34:06 +03:00
parent 56b766b23b
commit 847a08da93
28 changed files with 9865 additions and 0 deletions
@@ -0,0 +1,81 @@
import type { FastifyInstance } from "fastify";
import { DatabaseNotConfiguredError } from "../db/pool.js";
import type { AgentsRepository } from "../repositories/agents.js";
import { parseBearerToken } from "../security/bearer.js";
const MCP_PROTOCOL_VERSION = "2025-06-18";
type OntologyRouteDeps = {
agentsRepository: AgentsRepository | null;
ontologyCoreUrl: string;
ontologyCoreAccessToken?: string;
};
export async function registerOntologyGatewayRoutes(app: FastifyInstance, deps: OntologyRouteDeps): Promise<void> {
app.post("/ontology-mcp", async (request, reply) => {
if (!deps.agentsRepository) {
throw new DatabaseNotConfiguredError();
}
const token = parseBearerToken(request.headers.authorization);
const session = await deps.agentsRepository.findActiveSessionByToken(token, "ontology");
if (!session) {
return reply.status(401).send({
ok: false,
error: "ontology_agent_unauthorized",
message: "Ontology token is inactive, expired, revoked, or has the wrong purpose.",
});
}
const upstreamToken = deps.ontologyCoreAccessToken;
if (!upstreamToken) {
return reply.status(503).send({
ok: false,
error: "ontology_proxy_not_configured",
message: "Ontology Core access token is not configured.",
});
}
let upstream: Response;
try {
upstream = await fetch(new URL("/mcp", deps.ontologyCoreUrl), {
method: "POST",
redirect: "manual",
headers: {
Accept: readHeader(request.headers.accept) ?? "application/json, text/event-stream",
Authorization: `Bearer ${upstreamToken}`,
"Content-Type": "application/json",
"MCP-Protocol-Version": readHeader(request.headers["mcp-protocol-version"]) ?? MCP_PROTOCOL_VERSION,
},
body: JSON.stringify(request.body ?? {}),
signal: AbortSignal.timeout(60_000),
});
} catch {
return reply.status(503).send({
ok: false,
error: "ontology_proxy_unavailable",
message: "Ontology Core MCP is unavailable.",
});
}
const payload = await upstream.text();
reply.status(upstream.status);
reply.header("Cache-Control", "no-store");
reply.header("Content-Type", upstream.headers.get("content-type") ?? "application/json; charset=utf-8");
for (const header of ["mcp-protocol-version", "mcp-session-id", "vary"]) {
const value = upstream.headers.get(header);
if (value) {
reply.header(header, value);
}
}
return reply.send(payload);
});
}
function readHeader(value: string | string[] | undefined): string | undefined {
if (Array.isArray(value)) {
return value[0];
}
return value;
}
@@ -0,0 +1,204 @@
import type { FastifyInstance } from "fastify";
import { executeMcpTool } from "../mcp/tool-runtime.js";
import type { AgentsRepository } from "../repositories/agents.js";
import type { TaskerClient } from "../tasker/client.js";
import { authenticateAgent } from "./session.js";
type ToolRouteDeps = {
agentsRepository: AgentsRepository | null;
taskerClient: TaskerClient;
};
export async function registerToolRoutes(app: FastifyInstance, deps: ToolRouteDeps): Promise<void> {
app.post("/api/v1/tools/projects", async (request) => {
const session = await authenticateAgent(request, deps);
const result = await executeMcpTool(session, "tasker_create_project", request.body, deps, toolOptions(request));
return result.structuredContent;
});
app.get("/api/v1/tools/projects", async (request) => {
const session = await authenticateAgent(request, deps);
const result = await executeMcpTool(session, "tasker_list_projects", {}, deps, toolOptions(request));
return result.structuredContent;
});
app.get("/api/v1/tools/projects/:projectId/context", async (request) => {
const session = await authenticateAgent(request, deps);
const params = request.params as { projectId: string };
const query = request.query as { workspace_slug?: string };
const result = await executeMcpTool(
session,
"tasker_get_project_context",
{
project_id: params.projectId,
workspace_slug: query.workspace_slug,
},
deps,
toolOptions(request)
);
return result.structuredContent;
});
app.post("/api/v1/tools/projects/:projectId/labels/ensure", async (request) => {
const session = await authenticateAgent(request, deps);
const params = request.params as { projectId: string };
const result = await executeMcpTool(
session,
"tasker_ensure_labels",
{
...requestBodyRecord(request.body),
project_id: params.projectId,
},
deps,
toolOptions(request)
);
return result.structuredContent;
});
app.get("/api/v1/tools/issues", async (request) => {
const session = await authenticateAgent(request, deps);
const query = request.query as { project_id?: string; workspace_slug?: string; query?: string };
const result = await executeMcpTool(session, "tasker_search_issues", query, deps, toolOptions(request));
return result.structuredContent;
});
app.post("/api/v1/tools/issues", async (request) => {
const session = await authenticateAgent(request, deps);
const result = await executeMcpTool(session, "tasker_create_issue", request.body, deps, toolOptions(request));
return result.structuredContent;
});
app.get("/api/v1/tools/issues/:issueId", async (request) => {
const session = await authenticateAgent(request, deps);
const params = request.params as { issueId: string };
const query = request.query as { project_id?: string; workspace_slug?: string };
const result = await executeMcpTool(
session,
"tasker_get_issue",
{ ...query, issue_id: params.issueId },
deps,
toolOptions(request)
);
return result.structuredContent;
});
app.patch("/api/v1/tools/issues/:issueId", async (request) => {
const session = await authenticateAgent(request, deps);
const params = request.params as { issueId: string };
const result = await executeMcpTool(
session,
"tasker_update_issue",
{
...requestBodyRecord(request.body),
issue_id: params.issueId,
},
deps,
toolOptions(request)
);
return result.structuredContent;
});
app.post("/api/v1/tools/issues/:issueId/move", async (request) => {
const session = await authenticateAgent(request, deps);
const params = request.params as { issueId: string };
const result = await executeMcpTool(
session,
"tasker_move_issue",
{
...requestBodyRecord(request.body),
issue_id: params.issueId,
},
deps,
toolOptions(request)
);
return result.structuredContent;
});
app.post("/api/v1/tools/issues/:issueId/comments", async (request) => {
const session = await authenticateAgent(request, deps);
const params = request.params as { issueId: string };
const result = await executeMcpTool(
session,
"tasker_append_comment",
{
...requestBodyRecord(request.body),
issue_id: params.issueId,
},
deps,
toolOptions(request)
);
return result.structuredContent;
});
app.post("/api/v1/tools/issues/:issueId/attachments", async (request) => {
const session = await authenticateAgent(request, deps);
const params = request.params as { issueId: string };
const result = await executeMcpTool(
session,
"tasker_attach_file",
{
...requestBodyRecord(request.body),
issue_id: params.issueId,
},
deps,
toolOptions(request)
);
return result.structuredContent;
});
app.put("/api/v1/tools/issues/:issueId/labels", async (request) => {
const session = await authenticateAgent(request, deps);
const params = request.params as { issueId: string };
const result = await executeMcpTool(
session,
"tasker_set_issue_labels",
{
...requestBodyRecord(request.body),
issue_id: params.issueId,
},
deps,
toolOptions(request)
);
return result.structuredContent;
});
app.put("/api/v1/tools/issues/:issueId/assignees", async (request) => {
const session = await authenticateAgent(request, deps);
const params = request.params as { issueId: string };
const result = await executeMcpTool(
session,
"tasker_assign_issue",
{
...requestBodyRecord(request.body),
issue_id: params.issueId,
},
deps,
toolOptions(request)
);
return result.structuredContent;
});
}
function toolOptions(request: { headers: Record<string, string | string[] | undefined> }): { source: "rest"; idempotencyKey: string | null } {
return {
source: "rest",
idempotencyKey: readHeader(request.headers["idempotency-key"]),
};
}
function requestBodyRecord(body: unknown): Record<string, unknown> {
if (typeof body === "object" && body !== null && !Array.isArray(body)) {
return body as Record<string, unknown>;
}
return {};
}
function readHeader(value: string | string[] | undefined): string | null {
if (Array.isArray(value)) {
return value[0] ?? null;
}
return value ?? null;
}