UNPKG

remcode

Version:

Turn your AI assistant into a codebase expert. Intelligent code analysis, semantic search, and software engineering guidance through MCP integration.

312 lines (311 loc) 12.1 kB
"use strict"; /** * MCP-Compatible SSE Handler * * Implements proper JSON-RPC 2.0 over Server-Sent Events for MCP Inspector compatibility. * FIXED: Parameter parsing for MCP Inspector integration. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.MCPSSEHandler = void 0; const logger_1 = require("../../utils/logger"); const simple_validator_1 = require("../validation/simple-validator"); const logger = (0, logger_1.getLogger)('MCP-SSE-Handler'); class MCPSSEHandler { constructor() { this.activeConnections = new Map(); this.connectionCounter = 0; } handleSSEConnection(req, res) { const connectionId = `mcp_sse_${++this.connectionCounter}_${Date.now()}`; res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS' }); this.activeConnections.set(connectionId, res); this.sendSSEMessage(res, { jsonrpc: '2.0', method: 'connection/established', params: { connectionId, timestamp: new Date().toISOString() } }); req.on('close', () => { this.activeConnections.delete(connectionId); logger.info(`MCP SSE connection closed: ${connectionId}`); }); logger.info(`MCP SSE connection established: ${connectionId}`); } async handleMCPMessage(req, res, toolHandlers) { try { const jsonRpcRequest = req.body; if (!jsonRpcRequest.jsonrpc || jsonRpcRequest.jsonrpc !== '2.0') { const errorResponse = { jsonrpc: '2.0', error: { code: -32600, message: 'Invalid Request' }, id: jsonRpcRequest.id || null }; res.status(400).json(errorResponse); return; } logger.info(`📥 MCP Request: ${jsonRpcRequest.method}`); const response = await this.routeMCPMethod(jsonRpcRequest, toolHandlers); res.status(200).json(response); this.broadcastToSSEClients(response); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal error', data: errorMessage }, id: req.body?.id || null }); } } async routeMCPMethod(request, toolHandlers) { const { method, params, id } = request; switch (method) { case 'initialize': return { jsonrpc: '2.0', result: { protocolVersion: '2024-11-05', serverInfo: { name: 'remcode-mcp', version: '0.1.4' }, capabilities: { tools: {} } }, id: id || null }; case 'tools/list': return { jsonrpc: '2.0', result: { tools: this.getAllToolDefinitions() }, id: id || null }; case 'tools/call': return await this.handleToolCall(params, id || null, toolHandlers); default: return { jsonrpc: '2.0', error: { code: -32601, message: `Method not found: ${method}` }, id: id || null }; } } getAllToolDefinitions() { return [ { name: 'setup-repository', description: 'Set up a repository with Remcode configuration and workflows', inputSchema: { type: 'object', properties: { owner: { type: 'string', description: 'Repository owner' }, repo: { type: 'string', description: 'Repository name' }, confirm: { type: 'boolean', description: 'Confirm setup', default: false } }, required: ['owner', 'repo'] } }, { name: 'huggingface_embed_code', description: 'Generate embeddings for code using HuggingFace models', inputSchema: { type: 'object', properties: { code: { type: 'string', description: 'Code content to embed' }, model: { type: 'string', description: 'Model to use', default: 'microsoft/codebert-base' } }, required: ['code'] } }, { name: 'huggingface_list_models', description: 'List available HuggingFace embedding models', inputSchema: { type: 'object', properties: {}, required: [] } }, { name: 'pinecone_query', description: 'Search vectors in Pinecone database', inputSchema: { type: 'object', properties: { text: { type: 'string', description: 'Search text' }, topK: { type: 'number', description: 'Number of results', default: 10 } }, required: ['text'] } }, { name: 'github_get_repo', description: 'Get repository information from GitHub', inputSchema: { type: 'object', properties: { owner: { type: 'string', description: 'Repository owner' }, repo: { type: 'string', description: 'Repository name' } }, required: ['owner', 'repo'] } }, { name: 'search', description: 'Semantic search across codebase', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query' }, topK: { type: 'number', description: 'Number of results', default: 10 } }, required: ['query'] } } ]; } /** * FIXED: Handle tool call with proper MCP Inspector parameter parsing */ async handleToolCall(params, id, toolHandlers) { try { // 🔧 FIX: Handle MCP Inspector parameter format // MCP Inspector sends: { name: "tool-name", arguments: { param1: "value1", ... } } const toolName = params?.name; const toolArgs = params?.arguments || {}; if (!toolName) { return { jsonrpc: '2.0', error: { code: -32602, message: 'Tool name is required' }, id }; } logger.info(`🛠️ Executing tool: ${toolName}`); // 🛡️ Universal validation const validation = await simple_validator_1.SimpleValidator.validateQuick(); if (!validation.allValid) { return { jsonrpc: '2.0', error: { code: -32002, message: 'Setup required - missing API tokens', data: { validation: validation } }, id }; } // 🎯 Route to tool handlers let result; if (toolName === 'huggingface_list_models') { result = await this.callHandlerMethod(toolHandlers.huggingface, 'handleToolRequest', { tool: 'huggingface_list_models', parameters: {} }); } else if (toolName === 'huggingface_embed_code') { result = await this.callHandlerMethod(toolHandlers.huggingface, 'handleToolRequest', { tool: 'huggingface_embed_code', parameters: toolArgs }); } else if (toolName === 'pinecone_query') { result = await this.callHandlerMethod(toolHandlers.pinecone, 'handleQuery', toolArgs); } else if (toolName === 'github_get_repo') { result = await this.callHandlerMethod(toolHandlers.github, 'handleGetRepo', toolArgs); } else if (toolName === 'search') { result = await this.callHandlerMethod(toolHandlers.search, 'handleSearch', toolArgs); } else if (toolName === 'setup-repository') { result = await this.callHandlerMethod(toolHandlers.setup, 'handleSetupRepository', toolArgs); } else { return { jsonrpc: '2.0', error: { code: -32601, message: `Tool not implemented: ${toolName}` }, id }; } return { jsonrpc: '2.0', result: { content: [{ type: 'text', text: typeof result === 'string' ? result : JSON.stringify(result, null, 2) }] }, id }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger.error(`Tool execution failed: ${errorMessage}`); return { jsonrpc: '2.0', error: { code: -32603, message: `Tool execution failed: ${errorMessage}` }, id }; } } /** * Helper to call handler methods with proper error handling */ async callHandlerMethod(handler, methodName, args) { if (!handler || typeof handler[methodName] !== 'function') { throw new Error(`Handler method ${methodName} not found`); } // Mock Express req/res for handlers const mockReq = { body: args, params: {}, query: {}, headers: {} }; let result = null; const mockRes = { status: () => mockRes, json: (data) => { result = data; return mockRes; }, send: (data) => { result = data; return mockRes; } }; try { await handler[methodName](mockReq, mockRes, args); return result; } catch (error) { try { return await handler[methodName](args); } catch (directError) { throw new Error(`Failed to call ${methodName}: ${error instanceof Error ? error.message : String(error)}`); } } } sendSSEMessage(res, message) { try { res.write(`data: ${JSON.stringify(message)}\n\n`); } catch (error) { logger.error(`Failed to send SSE message: ${error}`); } } broadcastToSSEClients(message) { this.activeConnections.forEach((res, connectionId) => { try { this.sendSSEMessage(res, message); } catch (error) { this.activeConnections.delete(connectionId); } }); } closeAllConnections() { this.activeConnections.forEach((res) => { try { res.end(); } catch (error) { // Ignore } }); this.activeConnections.clear(); } } exports.MCPSSEHandler = MCPSSEHandler;