/** * MCP tools for rGov (modular governance decision circuits). * rGov shapes live in the space's main canvas document, not a separate Automerge doc. * * Tools: rgov_list_circuits, rgov_get_circuit */ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { getDocumentData } from "../community-store"; import { resolveAccess, accessDeniedResponse } from "./_auth"; const GOV_TYPES = [ "folk-gov-binary", "folk-gov-threshold", "folk-gov-knob", "folk-gov-project", "folk-gov-amendment", "folk-gov-quadratic", "folk-gov-conviction", "folk-gov-multisig", "folk-gov-sankey", ]; export function registerGovTools(server: McpServer) { server.tool( "rgov_list_circuits", "List governance circuit shapes on the space canvas", { space: z.string().describe("Space slug"), token: z.string().optional().describe("JWT auth token"), type: z.string().optional().describe("Filter by gov shape type (e.g. folk-gov-project, folk-gov-threshold)"), }, async ({ space, token, type }) => { const access = await resolveAccess(token, space, false); if (!access.allowed) return accessDeniedResponse(access.reason!); const docData = getDocumentData(space); if (!docData?.shapes) return { content: [{ type: "text" as const, text: JSON.stringify({ shapes: [] }) }] }; let govShapes = Object.values(docData.shapes) .filter((s: any) => !s.forgotten && GOV_TYPES.includes(s.type)); if (type) govShapes = govShapes.filter((s: any) => s.type === type); const summary = govShapes.map((s: any) => ({ id: s.id, type: s.type, title: s.title, status: s.status, x: s.x, y: s.y, })); return { content: [{ type: "text" as const, text: JSON.stringify(summary, null, 2) }] }; }, ); server.tool( "rgov_get_circuit", "Get full details of a governance shape by ID", { space: z.string().describe("Space slug"), token: z.string().optional().describe("JWT auth token"), shape_id: z.string().describe("Shape ID"), }, async ({ space, token, shape_id }) => { const access = await resolveAccess(token, space, false); if (!access.allowed) return accessDeniedResponse(access.reason!); const docData = getDocumentData(space); const shape = docData?.shapes?.[shape_id] as any; if (!shape || !GOV_TYPES.includes(shape.type)) { return { content: [{ type: "text" as const, text: JSON.stringify({ error: "Gov shape not found" }) }] }; } // Find arrows connected to this shape const arrows = Object.values(docData!.shapes) .filter((s: any) => s.type === "folk-arrow" && (s.sourceId === shape_id || s.targetId === shape_id)) .map((a: any) => ({ id: a.id, sourceId: a.sourceId, targetId: a.targetId })); return { content: [{ type: "text" as const, text: JSON.stringify({ shape, connectedArrows: arrows }, null, 2), }], }; }, ); }