import { Honcho } from "@honcho-ai/sdk"; interface HonchoConfig { apiKey: string; userName: string; baseUrl?: string; workspaceId?: string; assistantName?: string; } interface Message { role: "user" | "assistant"; content: string; metadata?: Record; } /** * JSON-RPC 2.0 request interface */ interface JsonRpcRequest { jsonrpc: "2.0"; method: string; params?: any; id?: string | number; } /** * JSON-RPC 2.0 response interface */ interface JsonRpcResponse { jsonrpc: "2.0"; id?: string | number | null; result?: any; error?: { code: number; message: string; data?: any; }; } // MCP Tool definitions interface Tool { name: string; description: string; inputSchema: { type: "object"; properties: Record; required?: string[]; }; } /** * Helper function to validate required arguments and create error responses */ function validateArguments( args: Record, required: string[], requestId: string | number | null, ): Response | null { for (const param of required) { if (!args[param]) { return createErrorResponse(requestId, -32602, `${param} is required`); } } // Special validation for arrays if (args.messages && !Array.isArray(args.messages)) { return createErrorResponse(requestId, -32602, "messages must be an array"); } if (args.peer_ids && !Array.isArray(args.peer_ids)) { return createErrorResponse(requestId, -32602, "peer_ids must be an array"); } return null; } /** * Helper function to create error responses */ function createErrorResponse( id: string | number | null, code: number, message: string, ): Response { return new Response( JSON.stringify( createJsonRpcResponse(id, undefined, createJsonRpcError(code, message)), ), { status: code === -32602 ? 400 : code === -32601 ? 404 : 500, headers: { "Content-Type": "application/json" }, }, ); } /** * Helper function to format messages for async iteration */ async function formatMessages(messagesPage: any): Promise { const messages = []; for await (const message of messagesPage) { messages.push({ id: message.id, content: message.content, peer_id: message.peer_id, session_id: message.session_id, metadata: message.metadata, created_at: message.created_at, }); } return messages; } class HonchoWorker { private honcho: Honcho; private config: HonchoConfig; constructor(config: HonchoConfig) { this.config = { baseUrl: "https://api.honcho.dev", workspaceId: "default", assistantName: "Assistant", ...config, }; this.honcho = new Honcho({ apiKey: this.config.apiKey, baseURL: this.config.baseUrl, workspaceId: this.config.workspaceId, }); } //////////////////////////////////////////////////////////////////////////////// /// /// /// "Bespoke" tools: easy to use for user-assistant conversation paradigms /// /// /// //////////////////////////////////////////////////////////////////////////////// /** * Start a new conversation with a user. Call this when a user starts a new conversation. * @returns A session ID for the conversation */ async startConversation(): Promise { // Get/create the peers first, before session creation // This avoids a race condition where peer creation during session.addPeers // could rollback the session if there's an IntegrityError const userPeer = await this.honcho.peer(this.config.userName); const assistantPeer = await this.honcho.peer(this.config.assistantName!, { config: { observe_me: false }, }); // Create a new session - pass empty config to force API call const sessionId = crypto.randomUUID(); const session = await this.honcho.session(sessionId, { config: {} }); // Add the user and assistant peers to the session await session.addPeers([ userPeer, [assistantPeer, { observe_me: null, observe_others: false }], ]); return sessionId; } /** * Add a turn to a conversation. Call this after a user has sent a message and the assistant has responded. * @param sessionId - The ID of the session to add the turn to * @param messages - A list of messages to add to the session */ async addTurn(sessionId: string, messages: Message[]): Promise { const session = await this.honcho.session(sessionId); const userPeer = await this.honcho.peer(this.config.userName); const assistantPeer = await this.honcho.peer(this.config.assistantName!); const sessionMessages = []; for (let i = 0; i < messages.length; i++) { const message = messages[i]; // Validate required fields if (!message || typeof message !== "object") { throw new Error(`Message at index ${i} must be a dictionary`); } if (!message.role) { throw new Error( `Message at index ${i} is missing required field 'role'`, ); } if (!message.content) { throw new Error( `Message at index ${i} is missing required field 'content'`, ); } const { role, content, metadata } = message; // Create message with appropriate peer if (role === "user") { if (metadata) { sessionMessages.push(userPeer.message(content, { metadata })); } else { sessionMessages.push(userPeer.message(content)); } } else if (role === "assistant") { if (metadata) { sessionMessages.push(assistantPeer.message(content, { metadata })); } else { sessionMessages.push(assistantPeer.message(content)); } } else { throw new Error( `Invalid role '${role}' at message index ${i}. Role must be one of: 'user' or 'assistant'`, ); } } await session.addMessages(sessionMessages); } /** * Get personalization insights about the user, based on the query and the accumulated knowledge of the user across all conversations. * @param sessionId - The ID of the session for context * @param query - The question about the user's preferences, habits, etc. * @returns A string with the personalization insights */ async getPersonalizationInsights( sessionId: string, query: string, ): Promise { const userPeer = await this.honcho.peer(this.config.userName); // Get the personalization insights (non-streaming returns string | null) const personalizationInsights = await userPeer.chat(query, { session: sessionId, }); if (!personalizationInsights || typeof personalizationInsights !== 'string') { return "No personalization insights found."; } return personalizationInsights; } //////////////////////////////////////////////////////////////////////////////// /// /// /// General tools for using Honcho /// /// /// //////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////// /// /// /// Workspace operations /// /// /// ////////////////////////////////////////////////////// /** * Search for messages across the entire workspace. * @param query - The search query to use * @returns A list of message dictionaries matching the search query */ async searchWorkspace(query: string): Promise { const messagesPage = await this.honcho.search(query); return await formatMessages(messagesPage); } /** * Get metadata for the current workspace. * @returns A dictionary containing the workspace's metadata */ async getWorkspaceMetadata(): Promise> { return await this.honcho.getMetadata(); } /** * Set metadata for the current workspace. * @param metadata - A dictionary of metadata to associate with the workspace */ async setWorkspaceMetadata(metadata: Record): Promise { await this.honcho.setMetadata(metadata); } ////////////////////////////////////////////////////// /// /// /// Peer operations /// /// /// ////////////////////////////////////////////////////// /** * Create or get a peer with the specified ID and optional configuration. * @param peerId - Unique identifier for the peer * @param config - Optional configuration dictionary for the peer * @returns A dictionary with the peer ID and confirmation of creation */ async createPeer( peerId: string, config?: Record, ): Promise<{ peer_id: string; config?: Record }> { const peer = await this.honcho.peer(peerId, { config }); return { peer_id: peer.id, config, }; } /** * Get metadata for a specific peer. * @param peerId - The ID of the peer to get metadata for * @returns A dictionary containing the peer's metadata */ async getPeerMetadata(peerId: string): Promise> { const peer = await this.honcho.peer(peerId); return await peer.getMetadata(); } /** * Set metadata for a specific peer. * @param peerId - The ID of the peer to set metadata for * @param metadata - A dictionary of metadata to associate with the peer */ async setPeerMetadata( peerId: string, metadata: Record, ): Promise { const peer = await this.honcho.peer(peerId); await peer.setMetadata(metadata); } /** * Search for messages sent by a peer. * @param peerId - The ID of the peer to search messages for * @param query - The search query to use * @returns A list of message dictionaries matching the search query */ async searchPeerMessages(peerId: string, query: string): Promise { const peer = await this.honcho.peer(peerId); const messagesPage = await peer.search(query); return await formatMessages(messagesPage); } /** * Query a peer's representation with natural language questions. * @param peerId - The ID of the peer to query * @param query - The natural language question to ask * @param targetPeerId - Optional target peer ID for local representation queries * @param sessionId - Optional session ID to scope the query to a specific session * @returns Response string containing the answer to the query, or "None" if no relevant information */ async chat( peerId: string, query: string, targetPeerId?: string, sessionId?: string, ): Promise { const peer = await this.honcho.peer(peerId); let targetPeer; if (targetPeerId) { targetPeer = await this.honcho.peer(targetPeerId); } const result = await peer.chat(query, { target: targetPeer, session: sessionId, }); if (!result || typeof result !== 'string') { return "None"; } return result; } /** * Get all peers in the current workspace. * @returns A list of peer dictionaries with their IDs */ async listPeers(): Promise<{ id: string }[]> { const peersPage = await this.honcho.getPeers(); const peers = []; for await (const peer of peersPage) { peers.push({ id: peer.id, }); } return peers; } ////////////////////////////////////////////////////// /// /// /// Session operations /// /// /// ////////////////////////////////////////////////////// /** * Create or get a session with the specified ID and optional configuration. * @param sessionId - Unique identifier for the session * @param config - Optional configuration dictionary for the session * @returns A dictionary with the session ID and confirmation of creation */ async createSession( sessionId: string, config?: Record, ): Promise<{ session_id: string; config?: Record }> { // Always pass config (even if empty) to force the API call to create the session // Without this, the SDK just creates a local Session object without making an API call const session = await this.honcho.session(sessionId, { config: config ?? {} }); return { session_id: session.id, config, }; } /** * Get metadata for a specific session. * @param sessionId - The ID of the session to get metadata for * @returns A dictionary containing the session's metadata */ async getSessionMetadata(sessionId: string): Promise> { const session = await this.honcho.session(sessionId); return await session.getMetadata(); } /** * Set metadata for a specific session. * @param sessionId - The ID of the session to set metadata for * @param metadata - A dictionary of metadata to associate with the session */ async setSessionMetadata( sessionId: string, metadata: Record, ): Promise { const session = await this.honcho.session(sessionId); await session.setMetadata(metadata); } /** * Add peers to a session. * @param sessionId - The ID of the session to add peers to * @param peerIds - List of peer IDs to add to the session */ async addPeersToSession(sessionId: string, peerIds: string[]): Promise { const session = await this.honcho.session(sessionId); await session.addPeers(peerIds); } /** * Remove peers from a session. * @param sessionId - The ID of the session to remove peers from * @param peerIds - List of peer IDs to remove from the session */ async removePeersFromSession( sessionId: string, peerIds: string[], ): Promise { const session = await this.honcho.session(sessionId); await session.removePeers(peerIds); } /** * Get all peer IDs in a session. * @param sessionId - The ID of the session to get peers from * @returns A list of peer IDs that are members of the session */ async getSessionPeers(sessionId: string): Promise { const session = await this.honcho.session(sessionId); const peers = await session.getPeers(); return peers.map((peer) => peer.id); } /** * Add messages to a session. * @param sessionId - The ID of the session to add messages to * @param messages - List of message dictionaries */ async addMessagesToSession( sessionId: string, messages: { peer_id: string; content: string; metadata?: Record; }[], ): Promise { const session = await this.honcho.session(sessionId); const sessionMessages = []; for (const message of messages) { const peer = await this.honcho.peer(message.peer_id); if (message.metadata) { sessionMessages.push( peer.message(message.content, { metadata: message.metadata }), ); } else { sessionMessages.push(peer.message(message.content)); } } await session.addMessages(sessionMessages); } /** * Get messages from a session with optional filtering. * @param sessionId - The ID of the session to get messages from * @param filters - Optional dictionary of filter criteria * @returns A list of message dictionaries */ async getSessionMessages( sessionId: string, filters?: Record, ): Promise { const session = await this.honcho.session(sessionId); const messagesPage = await session.getMessages({ filter: filters }); return await formatMessages(messagesPage); } /** * Get optimized context for a session within a token limit. * @param sessionId - The ID of the session to get context for * @param summary - Whether to include summary information * @param tokens - Maximum number of tokens to include in the context * @returns A dictionary containing the session context with messages and optional summary */ async getSessionContext( sessionId: string, summary: boolean = true, tokens?: number, ): Promise { const session = await this.honcho.session(sessionId); const context = await session.getContext({ summary, tokens }); return { session_id: context.sessionId, summary: context.summary, messages: context.messages.map((msg) => ({ id: msg.id, content: msg.content, peer_id: msg.peer_id, metadata: msg.metadata, created_at: msg.created_at, })), }; } /** * Search for messages in a specific session. * @param sessionId - The ID of the session to search messages in * @param query - The search query to use * @returns A list of message dictionaries matching the search query */ async searchSessionMessages( sessionId: string, query: string, ): Promise { const session = await this.honcho.session(sessionId); const messagesPage = await session.search(query); return await formatMessages(messagesPage); } /** * Get the current working representation of a peer in a session. * @param sessionId - The ID of the session * @param peerId - The ID of the peer to get the working representation of * @param targetPeerId - Optional target peer ID to get the representation of what peer_id knows about target_peer_id * @returns A dictionary containing information about the peer */ async getWorkingRepresentation( sessionId: string, peerId: string, targetPeerId?: string, ): Promise> { const session = await this.honcho.session(sessionId); if (targetPeerId) { return await session.workingRep(peerId, targetPeerId); } else { return await session.workingRep(peerId); } } /** * Get all sessions in the current workspace. * @returns A list of session dictionaries with their IDs */ async listSessions(): Promise<{ id: string }[]> { const sessionsPage = await this.honcho.getSessions(); const sessions = []; for await (const session of sessionsPage) { sessions.push({ id: session.id, }); } return sessions; } } /** * Parse configuration from request headers * @param request - The incoming request * @returns Configuration object or null if invalid */ function parseConfig(request: Request): HonchoConfig | null { // Get API key from Authorization header const authHeader = request.headers.get("Authorization"); if (!authHeader || !authHeader.startsWith("Bearer ")) { return null; } const apiKey = authHeader.substring(7); if (!apiKey) { return null; } const userName = request.headers.get("X-Honcho-User-Name"); if (!userName) { return null; } // Get configuration from headers with proper defaults const config: HonchoConfig = { apiKey, userName, baseUrl: request.headers.get("X-Honcho-Base-URL") || "https://api.honcho.dev", workspaceId: request.headers.get("X-Honcho-Workspace-ID") || "default", assistantName: request.headers.get("X-Honcho-Assistant-Name") || "Assistant", }; return config; } /** * Create a JSON-RPC 2.0 response * @param id - Request ID * @param result - Response result * @param error - Error object if any * @returns JSON-RPC response object */ function createJsonRpcResponse( id: string | number | null, result?: any, error?: { code: number; message: string; data?: any }, ): JsonRpcResponse { const response: JsonRpcResponse = { jsonrpc: "2.0", id, }; if (error) { response.error = error; } else { response.result = result; } return response; } /** * Create a JSON-RPC 2.0 error object * @param code - Error code * @param message - Error message * @param data - Optional error data * @returns Error object */ function createJsonRpcError( code: number, message: string, data?: any, ): { code: number; message: string; data?: any } { return { code, message, data }; } // Define all MCP tools based on the Python server.py functions const tools: Tool[] = [ // Bespoke tools { name: "start_conversation", description: "Start a new conversation with a user. Call this when a user starts a new conversation.", inputSchema: { type: "object", properties: {}, required: [], }, }, { name: "add_turn", description: "Add a turn to a conversation. Call this after a user has sent a message and the assistant has responded.", inputSchema: { type: "object", properties: { session_id: { type: "string", description: "The ID of the session to add the turn to.", }, messages: { type: "array", description: "A list of messages to add to the session.", items: { type: "object", properties: { role: { type: "string", enum: ["user", "assistant"], description: "The role of the message author.", }, content: { type: "string", description: "The content of the message.", }, metadata: { type: "object", description: "Optional metadata about the message.", }, }, required: ["role", "content"], }, }, }, required: ["session_id", "messages"], }, }, { name: "get_personalization_insights", description: "Get personalization insights about the user, based on the query and the accumulated knowledge of the user across all conversations.", inputSchema: { type: "object", properties: { session_id: { type: "string", description: "The ID of the session for context.", }, query: { type: "string", description: "The question about the user's preferences, habits, etc.", }, }, required: ["session_id", "query"], }, }, // Workspace operations { name: "search_workspace", description: "Search for messages across the entire workspace.", inputSchema: { type: "object", properties: { query: { type: "string", description: "The search query to use.", }, }, required: ["query"], }, }, { name: "get_workspace_metadata", description: "Get metadata for the current workspace.", inputSchema: { type: "object", properties: {}, required: [], }, }, { name: "set_workspace_metadata", description: "Set metadata for the current workspace.", inputSchema: { type: "object", properties: { metadata: { type: "object", description: "A dictionary of metadata to associate with the workspace.", }, }, required: ["metadata"], }, }, // Peer operations { name: "create_peer", description: "Create or get a peer with the specified ID and optional configuration.", inputSchema: { type: "object", properties: { peer_id: { type: "string", description: "Unique identifier for the peer.", }, config: { type: "object", description: "Optional configuration dictionary for the peer.", }, }, required: ["peer_id"], }, }, { name: "get_peer_metadata", description: "Get metadata for a specific peer.", inputSchema: { type: "object", properties: { peer_id: { type: "string", description: "The ID of the peer to get metadata for.", }, }, required: ["peer_id"], }, }, { name: "set_peer_metadata", description: "Set metadata for a specific peer.", inputSchema: { type: "object", properties: { peer_id: { type: "string", description: "The ID of the peer to set metadata for.", }, metadata: { type: "object", description: "A dictionary of metadata to associate with the peer.", }, }, required: ["peer_id", "metadata"], }, }, { name: "search_peer_messages", description: "Search for messages sent by a peer.", inputSchema: { type: "object", properties: { peer_id: { type: "string", description: "The ID of the peer to search messages for.", }, query: { type: "string", description: "The search query to use.", }, }, required: ["peer_id", "query"], }, }, { name: "chat", description: "Query a peer's representation with natural language questions.", inputSchema: { type: "object", properties: { peer_id: { type: "string", description: "The ID of the peer to query.", }, query: { type: "string", description: "The natural language question to ask.", }, target_peer_id: { type: "string", description: "Optional target peer ID for local representation queries.", }, session_id: { type: "string", description: "Optional session ID to scope the query to a specific session.", }, }, required: ["peer_id", "query"], }, }, { name: "list_peers", description: "Get all peers in the current workspace.", inputSchema: { type: "object", properties: {}, required: [], }, }, // Session operations { name: "create_session", description: "Create or get a session with the specified ID and optional configuration.", inputSchema: { type: "object", properties: { session_id: { type: "string", description: "Unique identifier for the session.", }, config: { type: "object", description: "Optional configuration dictionary for the session.", }, }, required: ["session_id"], }, }, { name: "get_session_metadata", description: "Get metadata for a specific session.", inputSchema: { type: "object", properties: { session_id: { type: "string", description: "The ID of the session to get metadata for.", }, }, required: ["session_id"], }, }, { name: "set_session_metadata", description: "Set metadata for a specific session.", inputSchema: { type: "object", properties: { session_id: { type: "string", description: "The ID of the session to set metadata for.", }, metadata: { type: "object", description: "A dictionary of metadata to associate with the session.", }, }, required: ["session_id", "metadata"], }, }, { name: "add_peers_to_session", description: "Add peers to a session.", inputSchema: { type: "object", properties: { session_id: { type: "string", description: "The ID of the session to add peers to.", }, peer_ids: { type: "array", items: { type: "string" }, description: "List of peer IDs to add to the session.", }, }, required: ["session_id", "peer_ids"], }, }, { name: "remove_peers_from_session", description: "Remove peers from a session.", inputSchema: { type: "object", properties: { session_id: { type: "string", description: "The ID of the session to remove peers from.", }, peer_ids: { type: "array", items: { type: "string" }, description: "List of peer IDs to remove from the session.", }, }, required: ["session_id", "peer_ids"], }, }, { name: "get_session_peers", description: "Get all peer IDs in a session.", inputSchema: { type: "object", properties: { session_id: { type: "string", description: "The ID of the session to get peers from.", }, }, required: ["session_id"], }, }, { name: "add_messages_to_session", description: "Add messages to a session.", inputSchema: { type: "object", properties: { session_id: { type: "string", description: "The ID of the session to add messages to.", }, messages: { type: "array", items: { type: "object", properties: { peer_id: { type: "string", description: "ID of the peer sending the message", }, content: { type: "string", description: "Message content", }, metadata: { type: "object", description: "Optional metadata dictionary", }, }, required: ["peer_id", "content"], }, description: "List of message dictionaries.", }, }, required: ["session_id", "messages"], }, }, { name: "get_session_messages", description: "Get messages from a session with optional filtering.", inputSchema: { type: "object", properties: { session_id: { type: "string", description: "The ID of the session to get messages from.", }, filters: { type: "object", description: "Optional dictionary of filter criteria.", }, }, required: ["session_id"], }, }, { name: "get_session_context", description: "Get optimized context for a session within a token limit.", inputSchema: { type: "object", properties: { session_id: { type: "string", description: "The ID of the session to get context for.", }, summary: { type: "boolean", description: "Whether to include summary information.", default: true, }, tokens: { type: "integer", description: "Maximum number of tokens to include in the context.", }, }, required: ["session_id"], }, }, { name: "search_session_messages", description: "Search for messages in a specific session.", inputSchema: { type: "object", properties: { session_id: { type: "string", description: "The ID of the session to search messages in.", }, query: { type: "string", description: "The search query to use.", }, }, required: ["session_id", "query"], }, }, { name: "get_working_representation", description: "Get the current working representation of a peer in a session.", inputSchema: { type: "object", properties: { session_id: { type: "string", description: "The ID of the session.", }, peer_id: { type: "string", description: "The ID of the peer to get the working representation of.", }, target_peer_id: { type: "string", description: "Optional target peer ID to get the representation of what peer_id knows about target_peer_id.", }, }, required: ["session_id", "peer_id"], }, }, { name: "list_sessions", description: "Get all sessions in the current workspace.", inputSchema: { type: "object", properties: {}, required: [], }, }, ]; /** * Execute a tool with validation and consistent response handling */ async function executeToolCall( honcho: HonchoWorker, toolName: string, toolArguments: any, requestId: string | number | null, ): Promise { let result: any; switch (toolName) { // Bespoke tools case "start_conversation": result = await honcho.startConversation(); break; case "add_turn": { const validation = validateArguments( toolArguments, ["session_id", "messages"], requestId, ); if (validation) return validation; await honcho.addTurn(toolArguments.session_id, toolArguments.messages); result = "Turn added successfully"; break; } case "get_personalization_insights": { const validation = validateArguments( toolArguments, ["session_id", "query"], requestId, ); if (validation) return validation; result = await honcho.getPersonalizationInsights( toolArguments.session_id, toolArguments.query, ); break; } // Workspace operations case "search_workspace": { const validation = validateArguments(toolArguments, ["query"], requestId); if (validation) return validation; result = await honcho.searchWorkspace(toolArguments.query); break; } case "get_workspace_metadata": result = await honcho.getWorkspaceMetadata(); break; case "set_workspace_metadata": { const validation = validateArguments( toolArguments, ["metadata"], requestId, ); if (validation) return validation; await honcho.setWorkspaceMetadata(toolArguments.metadata); result = "Workspace metadata set successfully"; break; } // Peer operations case "create_peer": { const validation = validateArguments( toolArguments, ["peer_id"], requestId, ); if (validation) return validation; result = await honcho.createPeer( toolArguments.peer_id, toolArguments.config, ); break; } case "get_peer_metadata": { const validation = validateArguments( toolArguments, ["peer_id"], requestId, ); if (validation) return validation; result = await honcho.getPeerMetadata(toolArguments.peer_id); break; } case "set_peer_metadata": { const validation = validateArguments( toolArguments, ["peer_id", "metadata"], requestId, ); if (validation) return validation; await honcho.setPeerMetadata( toolArguments.peer_id, toolArguments.metadata, ); result = "Peer metadata set successfully"; break; } case "search_peer_messages": { const validation = validateArguments( toolArguments, ["peer_id", "query"], requestId, ); if (validation) return validation; result = await honcho.searchPeerMessages( toolArguments.peer_id, toolArguments.query, ); break; } case "chat": { const validation = validateArguments( toolArguments, ["peer_id", "query"], requestId, ); if (validation) return validation; result = await honcho.chat( toolArguments.peer_id, toolArguments.query, toolArguments.target_peer_id, toolArguments.session_id, ); break; } case "list_peers": result = await honcho.listPeers(); break; // Session operations case "create_session": { const validation = validateArguments( toolArguments, ["session_id"], requestId, ); if (validation) return validation; result = await honcho.createSession( toolArguments.session_id, toolArguments.config, ); break; } case "get_session_metadata": { const validation = validateArguments( toolArguments, ["session_id"], requestId, ); if (validation) return validation; result = await honcho.getSessionMetadata(toolArguments.session_id); break; } case "set_session_metadata": { const validation = validateArguments( toolArguments, ["session_id", "metadata"], requestId, ); if (validation) return validation; await honcho.setSessionMetadata( toolArguments.session_id, toolArguments.metadata, ); result = "Session metadata set successfully"; break; } case "add_peers_to_session": { const validation = validateArguments( toolArguments, ["session_id", "peer_ids"], requestId, ); if (validation) return validation; await honcho.addPeersToSession( toolArguments.session_id, toolArguments.peer_ids, ); result = "Peers added to session successfully"; break; } case "remove_peers_from_session": { const validation = validateArguments( toolArguments, ["session_id", "peer_ids"], requestId, ); if (validation) return validation; await honcho.removePeersFromSession( toolArguments.session_id, toolArguments.peer_ids, ); result = "Peers removed from session successfully"; break; } case "get_session_peers": { const validation = validateArguments( toolArguments, ["session_id"], requestId, ); if (validation) return validation; result = await honcho.getSessionPeers(toolArguments.session_id); break; } case "add_messages_to_session": { const validation = validateArguments( toolArguments, ["session_id", "messages"], requestId, ); if (validation) return validation; await honcho.addMessagesToSession( toolArguments.session_id, toolArguments.messages, ); result = "Messages added to session successfully"; break; } case "get_session_messages": { const validation = validateArguments( toolArguments, ["session_id"], requestId, ); if (validation) return validation; result = await honcho.getSessionMessages( toolArguments.session_id, toolArguments.filters, ); break; } case "get_session_context": { const validation = validateArguments( toolArguments, ["session_id"], requestId, ); if (validation) return validation; result = await honcho.getSessionContext( toolArguments.session_id, toolArguments.summary, toolArguments.tokens, ); break; } case "search_session_messages": { const validation = validateArguments( toolArguments, ["session_id", "query"], requestId, ); if (validation) return validation; result = await honcho.searchSessionMessages( toolArguments.session_id, toolArguments.query, ); break; } case "get_working_representation": { const validation = validateArguments( toolArguments, ["session_id", "peer_id"], requestId, ); if (validation) return validation; result = await honcho.getWorkingRepresentation( toolArguments.session_id, toolArguments.peer_id, toolArguments.target_peer_id, ); break; } case "list_sessions": result = await honcho.listSessions(); break; default: return createErrorResponse( requestId, -32601, `Method not found: ${toolName}`, ); } const responseData = typeof result === "string" ? result : JSON.stringify(result); return new Response( JSON.stringify( createJsonRpcResponse(requestId, { content: [ { type: "text", text: responseData, }, ], }), ), { status: 200, headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", }, }, ); } /** * Main Cloudflare Worker export */ export default { async fetch(request: Request): Promise { // Handle CORS preflight requests if (request.method === "OPTIONS") { return new Response(null, { status: 200, headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Honcho-User-Name, X-Honcho-Base-URL, X-Honcho-Workspace-ID, X-Honcho-Assistant-Name", }, }); } // Only accept POST requests for JSON-RPC if (request.method !== "POST") { return createErrorResponse(null, -32600, "Invalid Request"); } let requestData: JsonRpcRequest; try { requestData = (await request.json()) as JsonRpcRequest; } catch (error) { return createErrorResponse(null, -32700, "Parse error"); } // Validate JSON-RPC format if (requestData.jsonrpc !== "2.0") { return createErrorResponse( requestData.id ?? null, -32600, "Invalid Request", ); } if (!requestData.method) { return createErrorResponse( requestData.id ?? null, -32600, "Invalid Request", ); } // Parse configuration const config = parseConfig(request); if (!config && requestData.method !== "initialize") { return createErrorResponse( requestData.id ?? null, -32602, "Missing or invalid API key", ); } const honcho = config ? new HonchoWorker(config) : null; try { switch (requestData.method) { case "initialize": return new Response( JSON.stringify( createJsonRpcResponse(requestData.id ?? null, { protocolVersion: "2024-11-05", capabilities: { tools: {}, }, serverInfo: { name: "Honcho MCP Server", version: "1.0.0", }, }), ), { status: 200, headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", }, }, ); case "notifications/initialized": // MCP initialized notification - no response needed return new Response(null, { status: 204, headers: { "Access-Control-Allow-Origin": "*", }, }); case "tools/list": return new Response( JSON.stringify( createJsonRpcResponse(requestData.id ?? null, { tools: tools, }), ), { status: 200, headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", }, }, ); case "tools/call": if (!honcho) { return createErrorResponse( requestData.id ?? null, -32602, "Missing API key", ); } const toolName = requestData.params?.name; const toolArguments = requestData.params?.arguments || {}; return await executeToolCall( honcho, toolName, toolArguments, requestData.id ?? null, ); default: return createErrorResponse( requestData.id ?? null, -32601, `Method not found: ${requestData.method}`, ); } } catch (error) { console.error("Worker error:", error); const errorMessage = error instanceof Error ? error.message : "Internal server error"; return createErrorResponse(requestData.id ?? null, -32603, errorMessage); } }, };