Pure mechanical split, no behavior changes. Pulls the 70+ tool handlers out of one monolith into api.ts (PLATFORM_URL + apiCall) plus 12 tools/*.ts files grouped by domain (workspaces, agents, secrets, files, memory, plugins, channels, delegation, schedules, approvals, discovery, remote_agents). Each module exports its handlers and a registerXxxTools(srv) function; createServer() wires them up. index.ts drops from 1697 → 89 lines. Largest new file is 183 lines. All handlers still re-exported from index.ts so existing tests that import them via "../index.js" keep working. Build clean; jest results unchanged from pre-refactor baseline. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
174 lines
5.9 KiB
TypeScript
174 lines
5.9 KiB
TypeScript
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
import { z } from "zod";
|
|
import { apiCall } from "../api.js";
|
|
|
|
export async function handleListPeers(params: { workspace_id: string }) {
|
|
const data = await apiCall("GET", `/registry/${params.workspace_id}/peers`);
|
|
return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
|
|
}
|
|
|
|
export async function handleDiscoverWorkspace(params: { workspace_id: string }) {
|
|
const data = await apiCall("GET", `/registry/discover/${params.workspace_id}`);
|
|
return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
|
|
}
|
|
|
|
export async function handleCheckAccess(params: { caller_id: string; target_id: string }) {
|
|
const { caller_id, target_id } = params;
|
|
const data = await apiCall("POST", `/registry/check-access`, { caller_id, target_id });
|
|
return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
|
|
}
|
|
|
|
export async function handleListEvents(params: { workspace_id?: string }) {
|
|
const path = params.workspace_id ? `/events/${params.workspace_id}` : "/events";
|
|
const data = await apiCall("GET", path);
|
|
return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
|
|
}
|
|
|
|
export async function handleListTemplates() {
|
|
const data = await apiCall("GET", "/templates");
|
|
return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
|
|
}
|
|
|
|
export async function handleListOrgTemplates() {
|
|
const data = await apiCall("GET", "/org/templates");
|
|
return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
|
|
}
|
|
|
|
export async function handleImportOrg(params: { dir: string }) {
|
|
const data = await apiCall("POST", "/org/import", { dir: params.dir });
|
|
return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
|
|
}
|
|
|
|
export async function handleImportTemplate(params: { name: string; files: Record<string, string> }) {
|
|
const { name, files } = params;
|
|
const data = await apiCall("POST", `/templates/import`, { name, files });
|
|
return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
|
|
}
|
|
|
|
export async function handleExportBundle(params: { workspace_id: string }) {
|
|
const data = await apiCall("GET", `/bundles/export/${params.workspace_id}`);
|
|
return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
|
|
}
|
|
|
|
export async function handleImportBundle(params: { bundle: Record<string, unknown> }) {
|
|
const data = await apiCall("POST", `/bundles/import`, params.bundle);
|
|
return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
|
|
}
|
|
|
|
export async function handleGetViewport() {
|
|
const data = await apiCall("GET", "/canvas/viewport");
|
|
return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
|
|
}
|
|
|
|
export async function handleSetViewport(params: { x: number; y: number; zoom: number }) {
|
|
const data = await apiCall("PUT", "/canvas/viewport", params);
|
|
return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
|
|
}
|
|
|
|
export async function handleExpandTeam(params: { workspace_id: string }) {
|
|
const data = await apiCall("POST", `/workspaces/${params.workspace_id}/expand`, {});
|
|
return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
|
|
}
|
|
|
|
export async function handleCollapseTeam(params: { workspace_id: string }) {
|
|
const data = await apiCall("POST", `/workspaces/${params.workspace_id}/collapse`, {});
|
|
return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
|
|
}
|
|
|
|
export function registerDiscoveryTools(srv: McpServer) {
|
|
srv.tool(
|
|
"list_peers",
|
|
"List reachable peer workspaces (siblings, children, parent)",
|
|
{ workspace_id: z.string() },
|
|
handleListPeers
|
|
);
|
|
|
|
srv.tool(
|
|
"discover_workspace",
|
|
"Resolve a workspace URL by ID (for A2A communication)",
|
|
{ workspace_id: z.string() },
|
|
handleDiscoverWorkspace
|
|
);
|
|
|
|
srv.tool(
|
|
"check_access",
|
|
"Check if two workspaces can communicate",
|
|
{ caller_id: z.string(), target_id: z.string() },
|
|
handleCheckAccess
|
|
);
|
|
|
|
srv.tool(
|
|
"list_events",
|
|
"List structure events (global or per workspace)",
|
|
{ workspace_id: z.string().optional().describe("Filter to workspace, or omit for all") },
|
|
handleListEvents
|
|
);
|
|
|
|
srv.tool("list_templates", "List available workspace templates", {}, handleListTemplates);
|
|
|
|
srv.tool("list_org_templates", "List available org templates", {}, handleListOrgTemplates);
|
|
|
|
srv.tool(
|
|
"import_org",
|
|
"Import an org template to create an entire workspace hierarchy",
|
|
{ dir: z.string().describe("Org template directory name (e.g., 'molecule-dev')") },
|
|
handleImportOrg
|
|
);
|
|
|
|
srv.tool(
|
|
"import_template",
|
|
"Import agent files as a new workspace template",
|
|
{
|
|
name: z.string().describe("Template name"),
|
|
files: z.record(z.string()).describe("Map of file path → content"),
|
|
},
|
|
handleImportTemplate
|
|
);
|
|
|
|
srv.tool(
|
|
"export_bundle",
|
|
"Export a workspace as a portable .bundle.json",
|
|
{ workspace_id: z.string() },
|
|
handleExportBundle
|
|
);
|
|
|
|
srv.tool(
|
|
"import_bundle",
|
|
"Import a workspace from a bundle JSON object",
|
|
{ bundle: z.record(z.unknown()).describe("Bundle JSON object") },
|
|
handleImportBundle
|
|
);
|
|
|
|
srv.tool(
|
|
"get_canvas_viewport",
|
|
"Get the current canvas viewport (x, y, zoom) persisted per-user.",
|
|
{},
|
|
handleGetViewport,
|
|
);
|
|
|
|
srv.tool(
|
|
"set_canvas_viewport",
|
|
"Persist the canvas viewport (x, y, zoom).",
|
|
{
|
|
x: z.number(),
|
|
y: z.number(),
|
|
zoom: z.number(),
|
|
},
|
|
handleSetViewport,
|
|
);
|
|
|
|
srv.tool(
|
|
"expand_team",
|
|
"Expand a workspace into a team of sub-workspaces",
|
|
{ workspace_id: z.string().describe("Workspace ID to expand") },
|
|
handleExpandTeam
|
|
);
|
|
|
|
srv.tool(
|
|
"collapse_team",
|
|
"Collapse a team back to a single workspace",
|
|
{ workspace_id: z.string().describe("Workspace ID to collapse") },
|
|
handleCollapseTeam
|
|
);
|
|
}
|