diff --git a/mcp/bun.lock b/mcp/bun.lock index 6d28ed3c..29d050c2 100644 --- a/mcp/bun.lock +++ b/mcp/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "honcho-mcp-proxy", @@ -7,6 +8,7 @@ "@honcho-ai/sdk": "^2.0.0", "@modelcontextprotocol/sdk": "^1.26.0", "agents": "^0.4.0", + "nanoid": "^5.1.7", "zod": "^4.3.6", }, "devDependencies": { @@ -373,7 +375,7 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="], + "nanoid": ["nanoid@5.1.7", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-ua3NDgISf6jdwezAheMOk4mbE1LXjm1DfMUDMuJf4AqxLFK3ccGpgWizwa5YV7Yz9EpXwEaWoRXSb/BnV0t5dQ=="], "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], @@ -507,10 +509,14 @@ "@honcho-ai/sdk/zod": ["zod@4.0.0", "", {}, "sha512-9diLdTPc/L7w/5jI4C3gHYNiGHDV9IZYxo1e5LSD8cabi65WVTWWb+g2BGPEpUUCOxR4D+6O5B0AzyMdUAXwrw=="], + "agents/nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="], + "mimetext/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], "miniflare/zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="], + "partyserver/nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="], + "router/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], "youch/cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="], diff --git a/mcp/src/config.ts b/mcp/src/config.ts index 6a6649cd..02ba8810 100644 --- a/mcp/src/config.ts +++ b/mcp/src/config.ts @@ -39,9 +39,9 @@ export function parseConfig(request: Request): HonchoConfig { baseUrl: request.headers.get("X-Honcho-Base-URL")?.trim() || "https://api.honcho.dev", - workspaceId: request.headers.get("X-Honcho-Workspace-ID") || "default", + workspaceId: request.headers.get("X-Honcho-Workspace-ID")?.trim() || "default", assistantName: - request.headers.get("X-Honcho-Assistant-Name") || "Assistant", + request.headers.get("X-Honcho-Assistant-Name")?.trim() || "Assistant", }; } diff --git a/mcp/src/tools/bespoke.ts b/mcp/src/tools/bespoke.ts index 7d360ae5..0c8eb324 100644 --- a/mcp/src/tools/bespoke.ts +++ b/mcp/src/tools/bespoke.ts @@ -12,7 +12,8 @@ export function register(server: McpServer, ctx: ToolContext) { description: [ "Start a new conversation for the current user.", "Call this once at the beginning of every new conversation.", - "Returns a session_id you must pass to add_turn and get_personalization_insights for the rest of this conversation.", + "Returns session_id, user_peer_id, and assistant_peer_id.", + "Use the peer IDs with add_messages_to_session to record turns.", ].join("\n"), inputSchema: {}, }, @@ -32,7 +33,11 @@ export function register(server: McpServer, ctx: ToolContext) { [assistantPeer, { observeMe: null, observeOthers: false }], ]); - return textResult(sessionId); + return textResult({ + session_id: sessionId, + user_peer_id: userPeer.id, + assistant_peer_id: assistantPeer.id, + }); } catch (e) { return errorResult( `Failed to start conversation: ${e instanceof Error ? e.message : String(e)}`, @@ -40,97 +45,4 @@ export function register(server: McpServer, ctx: ToolContext) { } }, ); - - // ── add_turn ──────────────────────────────────────────────────────── - server.registerTool( - "add_turn", - { - description: [ - "Record a user–assistant exchange in the current conversation.", - "Call this after every assistant response so Honcho can learn from the conversation.", - "Pass the full messages array containing both the user's message and your response.", - ].join("\n"), - inputSchema: { - session_id: z.string().describe("Session ID from start_conversation."), - messages: z - .array( - z.object({ - role: z - .enum(["user", "assistant"]) - .describe("Who sent the message."), - content: z.string().describe("Message text."), - metadata: z - .record(z.string(), z.unknown()) - .optional() - .describe("Optional metadata."), - }), - ) - .describe("Ordered list of messages in this turn."), - }, - }, - async ({ session_id, messages }) => { - try { - const session = await ctx.honcho.session(session_id); - const userPeer = await ctx.honcho.peer(ctx.config.userName); - const assistantPeer = await ctx.honcho.peer(ctx.config.assistantName); - - const sessionMessages = messages.map((msg) => { - const peer = msg.role === "user" ? userPeer : assistantPeer; - return msg.metadata - ? peer.message(msg.content, { metadata: msg.metadata }) - : peer.message(msg.content); - }); - - await session.addMessages(sessionMessages); - return textResult("Turn added successfully"); - } catch (e) { - return errorResult( - `Failed to add turn: ${e instanceof Error ? e.message : String(e)}`, - ); - } - }, - ); - - // ── get_personalization_insights ──────────────────────────────────── - server.registerTool( - "get_personalization_insights", - { - description: [ - "Ask Honcho a natural-language question about the user and get a personalized answer", - "grounded in everything Honcho has learned across all of the user's conversations.", - "Use this before responding when personalization would genuinely improve your response — it takes a few seconds.", - "Returns a natural-language answer.", - ].join("\n"), - inputSchema: { - session_id: z - .string() - .describe("Session ID from start_conversation, for context."), - query: z - .string() - .describe( - "Natural-language question about the user (e.g. 'What communication style does this user prefer?').", - ), - reasoning_level: z - .enum(["minimal", "low", "medium", "high", "max"]) - .optional() - .describe( - "How much reasoning effort to use. Higher = more detailed but slower. Default: 'low'.", - ), - }, - }, - async ({ session_id, query, reasoning_level }) => { - try { - const userPeer = await ctx.honcho.peer(ctx.config.userName); - const result = await userPeer.chat(query, { - session: session_id, - reasoningLevel: reasoning_level, - }); - return textResult(result ?? "No personalization insights found."); - } catch (e) { - return errorResult( - `Failed to get insights: ${e instanceof Error ? e.message : String(e)}`, - ); - } - }, - ); } diff --git a/mcp/src/tools/peers.ts b/mcp/src/tools/peers.ts index e4eb53c0..26457bcf 100644 --- a/mcp/src/tools/peers.ts +++ b/mcp/src/tools/peers.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import type { ToolContext } from "../types.js"; -import { textResult, errorResult, formatMessages } from "../types.js"; +import { textResult, errorResult } from "../types.js"; export function register(server: McpServer, ctx: ToolContext) { // ── create_peer ───────────────────────────────────────────────────── @@ -276,83 +276,4 @@ export function register(server: McpServer, ctx: ToolContext) { } }, ); - - // ── get_peer_metadata ─────────────────────────────────────────────── - server.registerTool( - "get_peer_metadata", - { - description: [ - "Get metadata for a peer.", - ].join("\n"), - inputSchema: { - peer_id: z.string().describe("The peer to get metadata for."), - }, - }, - async ({ peer_id }) => { - try { - const peer = await ctx.honcho.peer(peer_id); - const metadata = await peer.getMetadata(); - return textResult(metadata); - } catch (e) { - return errorResult( - `Failed to get peer metadata: ${e instanceof Error ? e.message : String(e)}`, - ); - } - }, - ); - - // ── set_peer_metadata ─────────────────────────────────────────────── - server.registerTool( - "set_peer_metadata", - { - description: [ - "Set metadata for a peer.", - "Overwrites existing metadata.", - ].join("\n"), - inputSchema: { - peer_id: z.string().describe("The peer to set metadata for."), - metadata: z - .record(z.string(), z.unknown()) - .describe("Key-value pairs to set."), - }, - }, - async ({ peer_id, metadata }) => { - try { - const peer = await ctx.honcho.peer(peer_id); - await peer.setMetadata(metadata); - return textResult("Peer metadata set successfully"); - } catch (e) { - return errorResult( - `Failed to set peer metadata: ${e instanceof Error ? e.message : String(e)}`, - ); - } - }, - ); - - // ── search_peer_messages ──────────────────────────────────────────── - server.registerTool( - "search_peer_messages", - { - description: [ - "Semantic search across all messages authored by a specific peer.", - "Use this to find what a particular peer has said across all sessions.", - "Returns an array of matching messages.", - ].join("\n"), - inputSchema: { - peer_id: z.string().describe("The peer whose messages to search."), - query: z.string().describe("Search query."), - }, - }, - async ({ peer_id, query }) => { - try { - const peer = await ctx.honcho.peer(peer_id); - const messages = await peer.search(query); - return textResult(formatMessages(messages)); - } catch (e) { - return errorResult( - `Search failed: ${e instanceof Error ? e.message : String(e)}`, - ); - } - }, - ); } diff --git a/mcp/src/tools/sessions.ts b/mcp/src/tools/sessions.ts index 66b9d9df..7fd75f0e 100644 --- a/mcp/src/tools/sessions.ts +++ b/mcp/src/tools/sessions.ts @@ -270,8 +270,8 @@ export function register(server: McpServer, ctx: ToolContext) { { description: [ "Add messages to a session from specific peers.", - "Use this for multi-peer conversations where you need to attribute messages to specific peers.", - "For the simple user/assistant flow, use add_turn instead.", + "Use this to record conversation turns. Each message must specify the peer_id of the author.", + "For the bespoke flow, use start_conversation first to get the user_peer_id and assistant_peer_id.", ].join("\n"), inputSchema: { session_id: z.string().describe("The session to add messages to."), @@ -391,33 +391,6 @@ export function register(server: McpServer, ctx: ToolContext) { }, ); - // ── search_session_messages ───────────────────────────────────────── - server.registerTool( - "search_session_messages", - { - description: [ - "Semantic search across messages in a specific session.", - "Use this to find relevant messages within a single conversation.", - "Returns an array of matching messages.", - ].join("\n"), - inputSchema: { - session_id: z.string().describe("The session to search in."), - query: z.string().describe("Search query."), - }, - }, - async ({ session_id, query }) => { - try { - const session = await ctx.honcho.session(session_id); - const messages = await session.search(query); - return textResult(formatMessages(messages)); - } catch (e) { - return errorResult( - `Search failed: ${e instanceof Error ? e.message : String(e)}`, - ); - } - }, - ); - // ── get_session_context ───────────────────────────────────────────── server.registerTool( "get_session_context", @@ -456,119 +429,4 @@ export function register(server: McpServer, ctx: ToolContext) { } }, ); - - // ── get_session_summaries ─────────────────────────────────────────── - server.registerTool( - "get_session_summaries", - { - description: [ - "Get the short and long summaries available for a session.", - "Use this when you want Honcho's generated rollups of the conversation so far.", - "Returns the session ID plus short and long summary objects when available.", - ].join("\n"), - inputSchema: { - session_id: z.string().describe("The session to get summaries for."), - }, - }, - async ({ session_id }) => { - try { - const session = await ctx.honcho.session(session_id); - const summaries = await session.summaries(); - return textResult(formatSessionSummaries(summaries)); - } catch (e) { - return errorResult( - `Failed to get session summaries: ${e instanceof Error ? e.message : String(e)}`, - ); - } - }, - ); - - // ── get_session_representation ────────────────────────────────────── - server.registerTool( - "get_session_representation", - { - description: [ - "Get a peer's representation scoped to a specific session.", - "Use this to see what Honcho has learned about a peer from a single conversation.", - "Returns a formatted string of session-scoped conclusions.", - ].join("\n"), - inputSchema: { - session_id: z.string().describe("The session to scope to."), - peer_id: z - .string() - .describe("The peer to get the representation for."), - target_peer_id: z - .string() - .optional() - .describe( - "Optional: get what peer_id knows about target_peer_id in this session.", - ), - }, - }, - async ({ session_id, peer_id, target_peer_id }) => { - try { - const session = await ctx.honcho.session(session_id); - const rep = await session.representation(peer_id, { - target: target_peer_id, - }); - return textResult(rep); - } catch (e) { - return errorResult( - `Failed to get representation: ${e instanceof Error ? e.message : String(e)}`, - ); - } - }, - ); - - // ── get_session_metadata ──────────────────────────────────────────── - server.registerTool( - "get_session_metadata", - { - description: [ - "Get metadata for a session.", - ].join("\n"), - inputSchema: { - session_id: z.string().describe("The session to get metadata for."), - }, - }, - async ({ session_id }) => { - try { - const session = await ctx.honcho.session(session_id); - const metadata = await session.getMetadata(); - return textResult(metadata); - } catch (e) { - return errorResult( - `Failed to get session metadata: ${e instanceof Error ? e.message : String(e)}`, - ); - } - }, - ); - - // ── set_session_metadata ──────────────────────────────────────────── - server.registerTool( - "set_session_metadata", - { - description: [ - "Set metadata for a session.", - "Overwrites existing metadata.", - ].join("\n"), - inputSchema: { - session_id: z.string().describe("The session to set metadata for."), - metadata: z - .record(z.string(), z.unknown()) - .describe("Key-value pairs to set."), - }, - }, - async ({ session_id, metadata }) => { - try { - const session = await ctx.honcho.session(session_id); - await session.setMetadata(metadata); - return textResult("Session metadata set successfully"); - } catch (e) { - return errorResult( - `Failed to set session metadata: ${e instanceof Error ? e.message : String(e)}`, - ); - } - }, - ); } diff --git a/mcp/src/tools/workspace.ts b/mcp/src/tools/workspace.ts index 97658958..04524bbb 100644 --- a/mcp/src/tools/workspace.ts +++ b/mcp/src/tools/workspace.ts @@ -69,22 +69,41 @@ export function register(server: McpServer, ctx: ToolContext) { }, ); - // ── search_workspace ──────────────────────────────────────────────── + // ── search ──────────────────────────────────────────────────────── server.registerTool( - "search_workspace", + "search", { description: [ - "Semantic search across all messages in the workspace.", - "Use this to find past conversations or messages from any peer/session.", + "Semantic search across messages. Scope is determined by which optional params are provided:", + "- No scope params: search all messages in the workspace.", + "- peer_id only: search messages authored by that peer across all sessions.", + "- session_id only: search messages within that session.", "Returns an array of matching messages with their content, peer, and session info.", ].join("\n"), inputSchema: { query: z.string().describe("Search query."), + peer_id: z + .string() + .optional() + .describe("Optional: scope search to messages by this peer."), + session_id: z + .string() + .optional() + .describe("Optional: scope search to messages in this session."), }, }, - async ({ query }) => { + async ({ query, peer_id, session_id }) => { try { - const messages = await ctx.honcho.search(query); + let messages; + if (session_id) { + const session = await ctx.honcho.session(session_id); + messages = await session.search(query); + } else if (peer_id) { + const peer = await ctx.honcho.peer(peer_id); + messages = await peer.search(query); + } else { + messages = await ctx.honcho.search(query); + } return textResult(formatMessages(messages)); } catch (e) { return errorResult( @@ -94,18 +113,39 @@ export function register(server: McpServer, ctx: ToolContext) { }, ); - // ── get_workspace_metadata ────────────────────────────────────────── + // ── get_metadata ────────────────────────────────────────────────── server.registerTool( - "get_workspace_metadata", + "get_metadata", { description: [ - "Get metadata for the current workspace.", + "Get metadata for a resource. Scope is determined by which optional params are provided:", + "- No scope params: get workspace metadata.", + "- peer_id only: get peer metadata.", + "- session_id only: get session metadata.", ].join("\n"), - inputSchema: {}, + inputSchema: { + peer_id: z + .string() + .optional() + .describe("Optional: get metadata for this peer."), + session_id: z + .string() + .optional() + .describe("Optional: get metadata for this session."), + }, }, - async () => { + async ({ peer_id, session_id }) => { try { - const metadata = await ctx.honcho.getMetadata(); + let metadata; + if (session_id) { + const session = await ctx.honcho.session(session_id); + metadata = await session.getMetadata(); + } else if (peer_id) { + const peer = await ctx.honcho.peer(peer_id); + metadata = await peer.getMetadata(); + } else { + metadata = await ctx.honcho.getMetadata(); + } return textResult(metadata); } catch (e) { return errorResult( @@ -115,24 +155,45 @@ export function register(server: McpServer, ctx: ToolContext) { }, ); - // ── set_workspace_metadata ────────────────────────────────────────── + // ── set_metadata ────────────────────────────────────────────────── server.registerTool( - "set_workspace_metadata", + "set_metadata", { description: [ - "Set metadata for the current workspace.", - "Overwrites existing metadata.", + "Set metadata for a resource. Overwrites existing metadata.", + "Scope is determined by which optional params are provided:", + "- No scope params: set workspace metadata.", + "- peer_id only: set peer metadata.", + "- session_id only: set session metadata.", ].join("\n"), inputSchema: { metadata: z .record(z.string(), z.unknown()) - .describe("Key-value pairs to set as workspace metadata."), + .describe("Key-value pairs to set as metadata."), + peer_id: z + .string() + .optional() + .describe("Optional: set metadata for this peer."), + session_id: z + .string() + .optional() + .describe("Optional: set metadata for this session."), }, }, - async ({ metadata }) => { + async ({ metadata, peer_id, session_id }) => { try { - await ctx.honcho.setMetadata(metadata); - return textResult("Workspace metadata set successfully"); + if (session_id) { + const session = await ctx.honcho.session(session_id); + await session.setMetadata(metadata); + return textResult("Session metadata set successfully"); + } else if (peer_id) { + const peer = await ctx.honcho.peer(peer_id); + await peer.setMetadata(metadata); + return textResult("Peer metadata set successfully"); + } else { + await ctx.honcho.setMetadata(metadata); + return textResult("Workspace metadata set successfully"); + } } catch (e) { return errorResult( `Failed to set metadata: ${e instanceof Error ? e.message : String(e)}`,