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 assistant peer with observe_me=false const assistant = this.honcho.peer(this.config.assistantName!, { config: { observe_me: false } }); // Create a new session const sessionId = crypto.randomUUID(); const session = this.honcho.session(sessionId); // Add the user and assistant peers to the session // @ts-expect-error - API accepts null for observe_me despite type definition await session.addPeers([this.config.userName, [assistant, { 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 = this.honcho.session(sessionId); const userPeer = this.honcho.peer(this.config.userName); const assistantPeer = 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 = this.honcho.peer(this.config.userName); // Get the personalization insights const personalizationInsights = await userPeer.chat(query, { sessionId }); if (!personalizationInsights) { 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 = 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 = 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 = 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 = 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 = this.honcho.peer(peerId); let targetPeer; if (targetPeerId) { targetPeer = this.honcho.peer(targetPeerId); } const result = await peer.chat(query, { target: targetPeer, sessionId }); return result || "None"; } /** * 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 }> { const session = this.honcho.session(sessionId, { 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 = 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 = 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 = 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 = 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 = 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 = this.honcho.session(sessionId); const sessionMessages = []; for (const message of messages) { const peer = 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 = 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 = 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 = 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 = 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); } }, };