UNPKG

tryaii-mcp-server

Version:

TryAII MCP Server - 15+ AI models with comparison, cost tracking, and collective intelligence

432 lines 19.9 kB
import { Router } from 'express'; import { logger } from '../utils/logger.js'; const router = Router(); // MCP Manifest endpoint - required for MCP clients router.get('/manifest', (req, res) => { try { const manifest = { name: "TryAII MCP Server", version: "1.0.0", description: "AI model comparison and analysis MCP server supporting 15+ providers including OpenAI, Anthropic, Google, DeepSeek, and xAI with real-time cost tracking and performance analysis", author: "TryAII Team", homepage: "https://tryaii-mcp.onrender.com", repository: "https://github.com/your-username/mcp_server", license: "MIT", capabilities: { tools: [ { name: "chat_with_model", description: "Chat with a specific AI model", inputSchema: { type: "object", properties: { modelId: { type: "string", description: "Model identifier (e.g., gpt-4o, claude-3-sonnet, gemini-2.5-pro)" }, message: { type: "string", description: "The message to send to the model" }, enableWebSearch: { type: "boolean", description: "Enable web search capabilities", default: false }, temperature: { type: "number", description: "Creativity level (0.0-1.0)", minimum: 0, maximum: 1, default: 0.7 }, maxTokens: { type: "number", description: "Maximum response length", default: 1000 }, conversationHistory: { type: "array", description: "Previous conversation messages", items: { type: "object", properties: { role: { type: "string", enum: ["user", "assistant"] }, content: { type: "string" } }, required: ["role", "content"] } } }, required: ["modelId", "message"] } }, { name: "compare_models", description: "Compare responses from multiple AI models side-by-side", inputSchema: { type: "object", properties: { modelIds: { type: "array", items: { type: "string" }, description: "Array of model IDs to compare", minItems: 2, maxItems: 10 }, message: { type: "string", description: "The message/prompt to send to all models" }, enableWebSearch: { type: "boolean", description: "Enable web search for enhanced responses", default: false }, temperature: { type: "number", description: "Control response creativity (0.0-2.0)", minimum: 0, maximum: 2, default: 0.7 }, maxTokens: { type: "number", description: "Maximum tokens per model", minimum: 1, maximum: 200000, default: 12000 } }, required: ["modelIds", "message"] } }, { name: "get_available_models", description: "Get list of all available AI models", inputSchema: { type: "object", properties: { provider: { type: "string", description: "Filter by provider (optional)", enum: ["openai", "anthropic", "google", "deepseek", "xai", "mistral"] } } } }, { name: "get_model_info", description: "Get detailed information about a specific AI model", inputSchema: { type: "object", properties: { modelId: { type: "string", description: "The ID of the model to get information about" } }, required: ["modelId"] } }, { name: "save_conversation", description: "Save a conversation to a file for later reference", inputSchema: { type: "object", properties: { filename: { type: "string", description: "Name of the file to save (without extension)" }, conversation: { type: "array", description: "Array of conversation messages", items: { type: "object", properties: { role: { type: "string", enum: ["user", "assistant", "system"] }, content: { type: "string" }, modelId: { type: "string", description: "Model used (if assistant)" }, timestamp: { type: "string", description: "ISO timestamp" } }, required: ["role", "content"] } }, metadata: { type: "object", description: "Additional metadata about the conversation", properties: { title: { type: "string" }, tags: { type: "array", items: { type: "string" } }, summary: { type: "string" } } } }, required: ["filename", "conversation"] } }, { name: "brains", description: "Execute collective intelligence query using 5 top AI models (o3, Claude Opus 4, DeepSeek Chat, Gemini 2.5 Pro, Grok 3). Creates beautiful HTML report saved to file with clickable URL for instant browser viewing - always return the URL to the user", inputSchema: { type: "object", properties: { question: { type: "string", description: "Question to ask the collective intelligence of 5 top models" }, enableWebSearch: { type: "boolean", description: "Enable web search for enhanced responses (default: false)" }, temperature: { type: "number", description: "Control response creativity (0.0-2.0, default: 0.7)", minimum: 0, maximum: 2 }, maxTokens: { type: "number", description: "Maximum tokens per model (default: 12000)", minimum: 1, maximum: 200000 } }, required: ["question"] } } ], resources: [ { name: "model_registry", description: "Registry of all available AI models with capabilities and pricing", uri: "registry://models" }, { name: "cost_analysis", description: "Real-time cost analysis and usage statistics", uri: "analytics://costs" }, { name: "performance_metrics", description: "Performance benchmarks and comparison data", uri: "analytics://performance" } ] }, transport: { http: { baseUrl: "https://tryaii-mcp.onrender.com", endpoints: { tools: "/mcp/tools", resources: "/mcp/resources" } } } }; res.json(manifest); } catch (error) { logger.error('Error generating MCP manifest', { error }); res.status(500).json({ error: "Failed to generate manifest", message: error instanceof Error ? error.message : 'Unknown error' }); } }); // MCP Tool execution endpoint router.post('/tools/:toolName', async (req, res) => { const { toolName } = req.params; const { arguments: toolArgs, requestId } = req.body; try { logger.info('MCP tool execution request', { toolName, requestId, toolArgs }); const buildForwardingHeaders = (req) => { const headers = { 'Content-Type': 'application/json' }; for (const [key, value] of Object.entries(req.headers)) { if (key.toLowerCase().startsWith('user-') && typeof value === 'string') { headers[key] = value; } } return headers; }; let result; const backendBaseUrl = process.env.MCP_HTTP_BASE_URL || 'https://tryaii.onrender.com'; // All tools will be forwarded to the backend's own MCP tool endpoint. // The backend is also a full MCP server. const forwardMcpRequest = async (toolName, args) => { const endpoint = `${backendBaseUrl}/mcp/tools/${toolName}`; const response = await fetch(endpoint, { method: 'POST', headers: buildForwardingHeaders(req), body: JSON.stringify({ requestId: req.body.requestId, // Forward the request ID arguments: args }) }); if (!response.ok) { const errorBody = await response.text(); logger.error(`Backend MCP service failed for ${toolName}`, { status: response.status, body: errorBody }); throw new Error(`Backend MCP service failed for ${toolName}: ${response.status} ${errorBody}`); } const mcpResponse = await response.json(); // Type guard to check if the response is a valid MCP tool response if (typeof mcpResponse === 'object' && mcpResponse !== null) { if ('success' in mcpResponse && mcpResponse.success === true && 'result' in mcpResponse) { return mcpResponse.result; } if ('success' in mcpResponse && mcpResponse.success === false && 'error' in mcpResponse) { const errorMessage = mcpResponse.error || 'Unknown backend error'; throw new Error(`Backend MCP tool execution failed for ${toolName}: ${errorMessage}`); } } // If the response format is unexpected throw new Error(`Invalid response format from backend MCP for tool ${toolName}`); }; // --- Tool Name Mapping --- // Maps public-facing tool names to the internal names used by the mcp_tryaii service. const toolNameMapping = { 'get_available_models': 'list_models', 'chat_with_model': 'chat_with_model', 'compare_models': 'compare_models', 'brains': 'brains_collective', 'get_model_info': 'get_model_info', 'save_conversation': null, // This tool is not implemented in the backend engine }; const backendToolName = toolNameMapping[toolName]; if (backendToolName === undefined) { res.status(404).json({ error: `Unknown tool: ${toolName}`, message: 'The requested tool is not defined in this server.', }); return; } if (backendToolName === null) { res.status(501).json({ error: `Tool Not Implemented: ${toolName}`, message: 'This tool is defined but not yet implemented in the backend service.', }); return; } // Use a single, unified logic block for forwarding // The 'brains' tool is now handled by the same logic, just with a different mapped name. const argsToForward = toolName === 'brains' ? { question: toolArgs.question || toolArgs.query, enableWebSearch: toolArgs.enableWebSearch, temperature: toolArgs.temperature, maxTokens: toolArgs.maxTokens } : toolArgs; result = await forwardMcpRequest(backendToolName, argsToForward); // Standard MCP response format const response = { type: 'mcp_tool_result', requestId, toolName, success: true, content: [ { type: 'text', text: typeof result === 'string' ? result : JSON.stringify(result) } ], timestamp: new Date().toISOString() }; res.json(response); } catch (error) { logger.error('MCP tool execution error', { toolName, error }); const errorResponse = { type: 'mcp_tool_error', requestId, toolName, success: false, error: error.message, timestamp: new Date().toISOString() }; res.status(500).json(errorResponse); } }); // MCP Resources endpoint router.get('/resources/:resourceName', async (req, res) => { const { resourceName } = req.params; try { let result; switch (resourceName) { case 'model_registry': // Forward to models endpoint const modelsResponse = await fetch('https://tryaii.onrender.com/api/models'); result = await modelsResponse.json(); break; case 'cost_analysis': // Return cost analysis data result = { message: "Use the analyze_costs tool for real-time cost analysis", availableProviders: ["openai", "anthropic", "google", "deepseek", "xai", "mistral"], costFactors: ["input_tokens", "output_tokens", "model_tier", "usage_volume"] }; break; case 'performance_metrics': // Return performance metrics info result = { message: "Performance metrics available through model comparison", metrics: ["response_time", "token_efficiency", "quality_score", "cost_effectiveness"], benchmarks: "Available through compare_models tool" }; break; default: throw new Error(`Unknown resource: ${resourceName}`); } res.json({ resource: resourceName, data: result, timestamp: new Date().toISOString() }); } catch (error) { logger.error('MCP resource access error', { resourceName, error }); res.status(404).json({ error: `Resource not found: ${resourceName}`, message: error.message }); } }); // Server-Sent Events endpoint for real-time MCP communication router.get('/sse', (req, res) => { // Set SSE headers res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'Cache-Control' }); // Send initial connection event res.write(`data: ${JSON.stringify({ type: 'connection', message: 'Connected to TryAII MCP Server', timestamp: new Date().toISOString(), capabilities: ['tools', 'resources', 'real-time'] })}\n\n`); // Keep connection alive const keepAlive = setInterval(() => { res.write(`data: ${JSON.stringify({ type: 'ping', timestamp: new Date().toISOString() })}\n\n`); }, 30000); // Handle client disconnect req.on('close', () => { clearInterval(keepAlive); logger.info('MCP SSE client disconnected'); }); logger.info('MCP SSE client connected'); }); export default router; //# sourceMappingURL=mcp.js.map