/** * 0xRay Governance MCP Server * * First-class Governance Service that orchestrates the real individual * skill MCP servers (code-review, security-audit, researcher) plus the * required Dynamo Solar SSOT filter. * * This is the primary governance entry point for all integrations * (Hermes, OpenCode, OpenClaw, Grok CLI, Jelly, CI/CD). * * It always runs proposals through the three real skill servers * and the Dynamo Solar SSOT filter (required by default). */ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; import { CallToolRequestSchema, isInitializeRequest, type CallToolResult, } from "@modelcontextprotocol/sdk/types.js"; import { XrayKnowledgeSkillBase } from "./shared/knowledge-skill-base.js"; import { mcpClientManager } from "./mcp-client.js"; import { frameworkLogger } from "../core/framework-logger.js"; import { featuresConfigLoader } from "../core/features-config.js"; import * as fs from "fs"; import * as path from "path"; import { fileURLToPath } from "url"; import { randomUUID } from "crypto"; import { applyConferConsultResult } from "../nucleus/confer.js"; import { getGovernanceService } from "../governance/governance-service.js"; import { getCodexPolicyService } from "../governance/codex-policy.service.js"; import { initializeGovernanceIntegration, shutdownGovernanceIntegration } from "../integrations/governance/index.js"; import type { GovernanceRequest } from "../governance/governance-types.js"; /** Bytes. Matches the SDK 1.32 default. Skipped when the caller passes parsedBody. */ export const MCP_MAX_REQUEST_BODY_SIZE = 4194304; interface GovernanceProposalInput { id?: string; type: 'fix' | 'refactor' | 'guard' | 'automate' | 'codify' | 'strategic' | 'compliance'; title: string; description: string; evidence?: string[]; source?: string; confidence?: number; } interface GovernProposalsArgs { proposals: GovernanceProposalInput[]; context?: { project?: string; phase?: string; source?: string; }; options?: { require_external?: boolean; // default true (Dynamo is required) }; } interface GovernReflectionArgs { reflectionPath?: string; reflectionContent?: string; context?: Record; } class GovernanceServer extends XrayKnowledgeSkillBase { constructor() { super("governance", "3.1.0"); this.tools = [ { name: "govern_proposals", description: "Run one or more proposals through the full 0xRay governance system. " + "Internal deliberation via 3 skill MCPs + required Dynamo Solar SSOT filter. " + "Returns merged structured decisions. " + "Supports regulatory compliance proposals: AML/KYC, PSD2, GDPR content moderation, " + "and other compliance-related governance scenarios.", inputSchema: { type: "object", properties: { proposals: { type: "array", items: { type: "object", properties: { id: { type: "string" }, type: { type: "string", enum: ["fix", "refactor", "guard", "automate", "codify", "strategic", "compliance"], }, title: { type: "string" }, description: { type: "string" }, evidence: { type: "array", items: { type: "string" } }, source: { type: "string" }, confidence: { type: "number" }, }, required: ["type", "title", "description"], }, description: "List of proposals to govern", }, context: { type: "object", description: "Optional context about the proposals (project, phase, etc.)", }, options: { type: "object", properties: { require_external: { type: "boolean", default: true, description: "Whether external Dynamo/Solar governance is required (default: true)", }, }, }, }, required: ["proposals"], }, }, { name: "govern_reflection", description: "Parse a reflection (or reflection file) and run its extracted proposals through the full governance system. " + "This is the primary way to govern outcomes from reflection-based workflows.", inputSchema: { type: "object", properties: { reflectionPath: { type: "string", description: "Path to a reflection .md file (alternative to reflectionContent)", }, reflectionContent: { type: "string", description: "Raw reflection content (alternative to reflectionPath)", }, context: { type: "object" }, }, required: [], }, }, { name: "record_confer_receipt", description: "Record a confer verdict written by the wearing TUI. " + "The TUI already called the seat's MCP server. This tool stores the receipt. " + "Only Verdict: PASS completes the consult todo.", inputSchema: { type: "object", properties: { todoId: { type: "string", description: "Consult todo id, such as s.1" }, subagent: { type: "string", description: "researcher, architect-tools, or code-review" }, sessionId: { type: "string", description: "Plan session id" }, outputText: { type: "string", description: "Host verdict text, including a Verdict line" }, projectRoot: { type: "string", description: "Checkout root. Defaults to XRAY_ROOT or the process cwd." }, }, required: ["todoId", "subagent", "sessionId", "outputText"], }, }, { name: "get_active_codex", description: "Get the currently active Codex (SSOT) — returns metadata about the loaded codex terms, " + "including version, term count, source path, and governance status. " + "Optionally include the full raw codex data with includeRaw=true.", inputSchema: { type: "object", properties: { includeRaw: { type: "boolean", description: "Whether to include the full raw codex data in the response", default: false, }, }, required: [], }, }, ]; this.handlers = { "govern_proposals": async (args) => this.handleGovernProposals(this.validateGovernProposalsArgs(args)), "govern_reflection": async (args) => this.handleGovernReflection(this.validateGovernReflectionArgs(args)), "get_active_codex": async (args) => this.handleGetActiveCodex(args as { includeRaw?: boolean }), "record_confer_receipt": async (args) => this.handleRecordConferReceipt(args), }; this.setupToolHandlers(); } private validateGovernProposalsArgs(value: unknown): GovernProposalsArgs { if (!value || typeof value !== 'object') { throw new Error('govern_proposals requires an object argument'); } const obj = value as Record; if (!Array.isArray(obj.proposals)) { throw new Error('govern_proposals requires a "proposals" array'); } for (let i = 0; i < obj.proposals.length; i++) { const p = obj.proposals[i] as Record; if (!p || typeof p !== 'object') { throw new Error(`proposals[${i}] must be an object`); } if (typeof p.type !== 'string' || !['fix', 'refactor', 'guard', 'automate', 'codify', 'strategic', 'compliance'].includes(p.type)) { throw new Error(`proposals[${i}].type must be one of: fix, refactor, guard, automate, codify, strategic, compliance`); } if (typeof p.title !== 'string') { throw new Error(`proposals[${i}].title must be a string`); } if (typeof p.description !== 'string') { throw new Error(`proposals[${i}].description must be a string`); } } return value as GovernProposalsArgs; } private validateGovernReflectionArgs(value: unknown): GovernReflectionArgs { if (!value || typeof value !== 'object') { throw new Error('govern_reflection requires an object argument'); } return value as GovernReflectionArgs; } protected setupToolHandlers(): void { this.setupListToolsHandler(); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case "govern_proposals": return await this.handleGovernProposals(this.validateGovernProposalsArgs(args)); case "govern_reflection": return await this.handleGovernReflection(this.validateGovernReflectionArgs(args)); case "get_active_codex": return await this.handleGetActiveCodex(args as { includeRaw?: boolean }); case "record_confer_receipt": return await this.handleRecordConferReceipt(args); default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { frameworkLogger.log("governance-mcp", "tool-error", "error", { tool: name, error: error instanceof Error ? error.message : String(error), }); return { content: [ { type: "text", text: `Governance failed: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, } as CallToolResult; } }); } /** Align tool default with features.json inference_governance.enabled (P0.2). */ private resolveRequireExternal(explicit?: boolean): boolean { if (explicit !== undefined) return explicit; try { const config = featuresConfigLoader.loadConfig() as { inference_governance?: { enabled?: boolean }; }; return config?.inference_governance?.enabled === true; } catch { return false; } } /** Boot stays flag-gated. A required external call starts the client on demand. */ private async armRequiredDynamo(requireExternal: boolean): Promise { if (!requireExternal) return; const integration = await initializeGovernanceIntegration(); if (!integration.isAvailable()) { await integration.ensureDynamoClient(); } } private async handleGovernProposals(args: GovernProposalsArgs): Promise { const service = getGovernanceService(); const requireExternal = this.resolveRequireExternal(args.options?.require_external); await this.armRequiredDynamo(requireExternal); const request: GovernanceRequest = { proposals: args.proposals.map((p, i) => ({ id: p.id || `prop-${Date.now()}-${i}`, type: p.type, title: p.title, description: p.description, evidence: p.evidence || [], source: "manual", confidence: p.confidence || 0.8, })), context: args.context || {}, options: { requireExternalDynamo: requireExternal, }, }; frameworkLogger.log("governance-mcp", "delegating-to-governance-service", "info", { proposalCount: request.proposals.length, }); const response = await service.govern(request); return { content: [ { type: "text", text: JSON.stringify(response, null, 2), }, ], }; } private async handleGovernReflection(args: GovernReflectionArgs): Promise { const { reflectionPath, reflectionContent, context } = args; let content = reflectionContent; if (!content && reflectionPath) { if (!fs.existsSync(reflectionPath)) { throw new Error(`Reflection file not found: ${reflectionPath}`); } content = fs.readFileSync(reflectionPath, "utf-8"); } if (!content) { throw new Error("Either reflectionPath or reflectionContent must be provided"); } frameworkLogger.log("governance-mcp", "parsing-reflection", "info", { reflectionPath, contentLength: content.length }); const proposals = this.parseCodexTermsFromReflection(content); if (proposals.length === 0) { return { content: [ { type: "text", text: JSON.stringify({ message: "No codex term proposals found in reflection.", proposals: [], }, null, 2), }, ], }; } frameworkLogger.log("governance-mcp", "reflection-proposals-found", "info", { count: proposals.length }); // Delegate to the main govern_proposals logic return this.handleGovernProposals({ proposals, context: { ...(context || {}), source: "reflection" }, options: { require_external: true }, }); } private async handleRecordConferReceipt(args: unknown): Promise { if (!args || typeof args !== "object") { throw new Error("record_confer_receipt requires an object argument"); } const obj = args as Record; const todoId = typeof obj.todoId === "string" ? obj.todoId.trim() : ""; const subagent = typeof obj.subagent === "string" ? obj.subagent.trim() : ""; const sessionId = typeof obj.sessionId === "string" ? obj.sessionId.trim() : ""; const outputText = typeof obj.outputText === "string" ? obj.outputText : ""; if (!todoId || !subagent || !sessionId || !outputText.trim()) { throw new Error("record_confer_receipt requires todoId, subagent, sessionId, and outputText"); } const projectRoot = typeof obj.projectRoot === "string" && obj.projectRoot.trim() ? obj.projectRoot : process.env.XRAY_ROOT || process.cwd(); const result = applyConferConsultResult(todoId, subagent, sessionId, outputText, projectRoot); return { content: [ { type: "text", text: JSON.stringify(result), }, ], }; } private async handleGetActiveCodex(args: { includeRaw?: boolean }): Promise { const includeRaw = args?.includeRaw === true; const snapshot = await getCodexPolicyService().getCurrentCodex(includeRaw); return { content: [ { type: "text", text: JSON.stringify(snapshot, null, 2), }, ], }; } private parseCodexTermsFromReflection(content: string): GovernanceProposalInput[] { const CODEX_TERM_SECTION = "## Codex Term Proposals"; const PRIORITY_SECTION = "## Implementation Priority Matrix"; const startIdx = content.indexOf(CODEX_TERM_SECTION); if (startIdx === -1) return []; const endIdx = content.indexOf(PRIORITY_SECTION); const section = endIdx !== -1 ? content.slice(startIdx + CODEX_TERM_SECTION.length, endIdx) : content.slice(startIdx + CODEX_TERM_SECTION.length); const terms: GovernanceProposalInput[] = []; const blocks = section.split(/\n### /).filter(b => b.trim().length > 0); for (const block of blocks) { const nameMatch = block.match(/^([^\n]+)/); if (!nameMatch || !nameMatch[1]) continue; const name = nameMatch[1].trim(); const catMatch = block.match(/\*\*Category\*\*:\s*(.+)/); const sevMatch = block.match(/\*\*Severity\*\*:\s*(.+)/); const ruleMatch = block.match(/\*\*Detection Rule\*\*:\s*"(.+)"/); const targetMatch = block.match(/\*\*Implementation Target\*\*:\s*(.+)/); const severity = (sevMatch?.[1]?.trim() ?? "medium").toLowerCase(); const category = (catMatch?.[1]?.trim() ?? "design").toLowerCase(); let type: GovernanceProposalInput['type'] = "codify"; if (category.includes("anti-pattern")) type = "guard"; else if (category.includes("aspirational")) type = "codify"; else if (category.includes("process")) type = "automate"; else if (category.includes("design")) type = "refactor"; terms.push({ id: `reflection-${Date.now()}-${terms.length}`, type, title: name, description: ruleMatch?.[1] ?? `Implement ${name}`, evidence: [ `Severity: ${severity}`, `Target: ${targetMatch?.[1]?.trim() ?? "TBD"}`, ], source: "reflection", confidence: severity === "blocking" ? 0.95 : severity === "high" ? 0.85 : severity === "medium" ? 0.7 : 0.5, }); } return terms; } private async initializeGovernance(): Promise { try { const config = featuresConfigLoader.loadConfig() as any; const govConfig = config?.inference_governance; if (govConfig?.enabled) { await initializeGovernanceIntegration(); frameworkLogger.log("governance-server", "dynamo-solar-ssot-initialized", "info", { endpoint: govConfig.endpoint_url || "default", }); } else { frameworkLogger.log("governance-server", "dynamo-solar-ssot-disabled", "info", { message: "Dynamo Solar SSOT disabled in features config, external governance will abstain", }); } } catch (err) { frameworkLogger.log("governance-server", "dynamo-solar-ssot-init-error", "error", { error: err instanceof Error ? err.message : String(err), }); } } async run(): Promise { await this.initializeGovernance(); await super.run(); } async connect(transport: Transport): Promise { await this.server.connect(transport as any); } /** * Run as HTTP server using Streamable HTTP transport (for Grok CLI compatibility). * Uses per-session transports so concurrent clients can each initialize independently. */ async runHttp(port: number = parseInt(process.env.MCP_PORT ?? "3100", 10)): Promise { await this.initializeGovernance(); const mcpHost = process.env.MCP_HOST || "127.0.0.1"; const allowedHosts = process.env.MCP_ALLOWED_HOSTS?.split(","); const app = createMcpExpressApp({ host: mcpHost, ...(allowedHosts ? { allowedHosts } : {}), }); const transports: Record = {}; const apiKey = process.env.GOVERNANCE_API_KEY; if (apiKey) { frameworkLogger.log("governance-mcp", "api-key-auth-enabled", "info", {}); } else { frameworkLogger.log("governance-mcp", "api-key-auth-disabled", "warning", { message: "GOVERNANCE_API_KEY not set - HTTP server has no authentication", }); } app.use((req: any, res: any, next: any) => { if (apiKey && req.path !== "/health") { const providedKey = req.headers["x-api-key"]; if (!providedKey || providedKey !== apiKey) { res.status(401).json({ error: "Unauthorized" }); return; } } next(); }); const mcpPostHandler = async (req: any, res: any) => { try { const sessionId = req.headers["mcp-session-id"] as string | undefined; let transport: StreamableHTTPServerTransport | undefined; if (sessionId && transports[sessionId]) { transport = transports[sessionId]; } else if (!sessionId && isInitializeRequest(req.body)) { transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), maxRequestBodySize: MCP_MAX_REQUEST_BODY_SIZE, onsessioninitialized: (sid) => { if (transport) { transports[sid] = transport; frameworkLogger.log("governance-mcp", "session-initialized", "info", { sessionId: sid, }); } }, }); transport.onclose = () => { const sid = transport?.sessionId; if (sid && transports[sid]) { delete transports[sid]; frameworkLogger.log("governance-mcp", "session-closed", "info", { sessionId: sid }); } }; const sessionServer = new GovernanceServer(); await sessionServer.connect(transport as Transport); await transport.handleRequest(req, res, req.body); return; } else { res.status(400).json({ jsonrpc: "2.0", error: { code: -32000, message: "Bad Request: No valid session ID provided" }, id: null, }); return; } await transport.handleRequest(req, res, req.body); } catch (error) { frameworkLogger.log("governance-mcp", "http-handler-error", "error", { error: String(error) }); if (!res.headersSent) { res.status(500).json({ jsonrpc: "2.0", error: { code: -32603, message: "Internal error" }, id: null }); } } }; app.post("/mcp", mcpPostHandler); app.get("/mcp", async (req: any, res: any) => { const sessionId = req.headers["mcp-session-id"] as string | undefined; if (!sessionId || !transports[sessionId]) { res.status(400).send("Invalid or missing session ID"); return; } try { await transports[sessionId].handleRequest(req, res); } catch (error) { frameworkLogger.log("governance-mcp", "http-sse-error", "error", { error: String(error) }); if (!res.headersSent) { res.status(500).send("Error processing SSE stream"); } } }); app.delete("/mcp", async (req: any, res: any) => { const sessionId = req.headers["mcp-session-id"] as string | undefined; if (!sessionId || !transports[sessionId]) { res.status(400).send("Invalid or missing session ID"); return; } try { await transports[sessionId].handleRequest(req, res); } catch (error) { frameworkLogger.log("governance-mcp", "http-delete-error", "error", { error: String(error) }); if (!res.headersSent) { res.status(500).send("Error processing session termination"); } } }); app.get("/health", (_req: any, res: any) => { res.json({ status: "ok", server: "governance" }); }); const closeAllTransports = async () => { await Promise.all(Object.values(transports).map((t) => t.close())); }; app.listen(port, mcpHost, () => { frameworkLogger.log("governance-mcp", "http-listening", "info", { port }); }); process.on("SIGINT", () => { void closeAllTransports().then(() => process.exit(0)); }); process.on("SIGTERM", () => { void closeAllTransports().then(() => process.exit(0)); }); } } // Start the server if this file is run directly const entryPoint = fs.realpathSync(process.argv[1] ?? ""); if (entryPoint && fileURLToPath(import.meta.url) === entryPoint) { // If --port or MCP_PORT is set, use HTTP transport (for Grok CLI compatibility) const cliPort = process.argv.find((a) => a.startsWith("--port="))?.split("=")[1]; const port = parseInt(cliPort ?? process.env.MCP_PORT ?? "", 10); if (!isNaN(port)) { const server = new GovernanceServer(); server.runHttp(port).catch((error) => { frameworkLogger.log('governance-mcp', 'http-startup-error', 'error', { error: String(error) }); process.exit(1); }); } else { const server = new GovernanceServer(); server.run().catch((error) => { frameworkLogger.log('governance-mcp', 'fatal-startup-error', 'error', { error: String(error) }); process.exit(1); }); } } export { GovernanceServer };