import { z } from "zod"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import type { ToolContext } from "../types.js"; import { textResult, errorResult } from "../types.js"; export function register(server: McpServer, ctx: ToolContext) { // ── list_conclusions ──────────────────────────────────────────────── server.registerTool( "list_conclusions", { description: [ "List conclusions (facts and observations) that Honcho has derived about a peer (paginated).", "Use this to see what Honcho has learned. If no target is given, returns self-conclusions.", "Returns conclusion objects with pagination metadata.", ].join("\n"), inputSchema: { peer_id: z.string().describe("The observer peer."), target_peer_id: z .string() .optional() .describe( "Optional: list conclusions about this target. Omit for self-conclusions.", ), }, }, async ({ peer_id, target_peer_id }) => { try { const peer = await ctx.honcho.peer(peer_id); const scope = target_peer_id ? peer.conclusionsOf(target_peer_id) : peer.conclusions; const page = await scope.list(); return textResult({ conclusions: page.items.map((c) => ({ id: c.id, content: c.content, observer_id: c.observerId, observed_id: c.observedId, session_id: c.sessionId, created_at: c.createdAt, })), total: page.total, page: page.page, pages: page.pages, }); } catch (e) { return errorResult( `Failed to list conclusions: ${e instanceof Error ? e.message : String(e)}`, ); } }, ); // ── query_conclusions ─────────────────────────────────────────────── server.registerTool( "query_conclusions", { description: [ "Semantic search across a peer's conclusions.", "Use this to find specific knowledge Honcho has derived — more targeted than list_conclusions.", "Returns an array of matching conclusions ranked by relevance.", ].join("\n"), inputSchema: { peer_id: z.string().describe("The observer peer."), query: z.string().describe("Semantic search query."), target_peer_id: z .string() .optional() .describe("Optional: search conclusions about this target."), top_k: z .number() .optional() .describe("Max results to return."), }, }, async ({ peer_id, query, target_peer_id, top_k }) => { try { const peer = await ctx.honcho.peer(peer_id); const scope = target_peer_id ? peer.conclusionsOf(target_peer_id) : peer.conclusions; const conclusions = await scope.query(query, top_k); return textResult( conclusions.map((c) => ({ id: c.id, content: c.content, observer_id: c.observerId, observed_id: c.observedId, session_id: c.sessionId, created_at: c.createdAt, })), ); } catch (e) { return errorResult( `Query failed: ${e instanceof Error ? e.message : String(e)}`, ); } }, ); // ── create_conclusions ────────────────────────────────────────────── server.registerTool( "create_conclusions", { description: [ "Manually create conclusions (facts/observations) about a peer.", "Use this to inject knowledge into Honcho that wasn't derived from conversation.", "Returns the number of conclusions created.", ].join("\n"), inputSchema: { peer_id: z.string().describe("The observer peer."), target_peer_id: z .string() .describe("The peer the conclusions are about."), conclusions: z .array(z.string()) .describe("Conclusion content strings to create."), session_id: z .string() .optional() .describe( "Optional: associate conclusions with a session. Omit for global conclusions.", ), }, }, async ({ peer_id, target_peer_id, conclusions, session_id }) => { try { const peer = await ctx.honcho.peer(peer_id); const scope = peer.conclusionsOf(target_peer_id); const params = conclusions.map((content) => ({ content, sessionId: session_id, })); await scope.create(params); return textResult( `Created ${conclusions.length} conclusion${conclusions.length === 1 ? "" : "s"} successfully`, ); } catch (e) { return errorResult( `Failed to create conclusions: ${e instanceof Error ? e.message : String(e)}`, ); } }, ); // ── delete_conclusion ─────────────────────────────────────────────── server.registerTool( "delete_conclusion", { description: [ "Delete a specific conclusion by ID.", "Use this to remove incorrect or outdated knowledge.", ].join("\n"), inputSchema: { peer_id: z.string().describe("The observer peer."), target_peer_id: z .string() .describe("The peer the conclusion is about."), conclusion_id: z.string().describe("The conclusion to delete."), }, }, async ({ peer_id, target_peer_id, conclusion_id }) => { try { const peer = await ctx.honcho.peer(peer_id); const scope = peer.conclusionsOf(target_peer_id); await scope.delete(conclusion_id); return textResult("Conclusion deleted successfully"); } catch (e) { return errorResult( `Failed to delete conclusion: ${e instanceof Error ? e.message : String(e)}`, ); } }, ); }