UNPKG

tryaii-mcp-server

Version:

MCP Server for TryAII - Lightweight proxy to TryAII API service

313 lines (312 loc) 13.9 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { ListToolsRequestSchema, CallToolRequestSchema, ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js"; // Import API client and models registry import { TryAIIApiClient } from './clients/api-client.js'; import { AVAILABLE_MODELS } from './models/registry.js'; // ============================================================================================ // CONFIGURATION // ============================================================================================ const API_BASE_URL = 'https://tryaii-mcp.onrender.com'; const API_KEY = process.env.TRYAII_API_KEY; if (!API_KEY) { console.error('❌ TRYAII_API_KEY environment variable is required'); process.exit(1); } // Initialize API client const apiClient = new TryAIIApiClient({ baseUrl: API_BASE_URL, apiKey: API_KEY }); // ============================================================================================ // MCP SERVER CONFIGURATION // ============================================================================================ const server = new Server({ name: "tryaii-server", version: "2.0.0", }, { capabilities: { tools: {}, }, }); // ============================================================================================ // TOOLS DEFINITION // ============================================================================================ server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "get_available_models", description: "Get a list of all available AI models with their capabilities and pricing", inputSchema: { type: "object", properties: { provider: { type: "string", description: "Filter models by provider (optional)", enum: ["openai", "anthropic", "google", "deepseek", "xai", "mistral"] } } } }, { name: "chat_with_model", description: "Have a conversation with a specific AI model", inputSchema: { type: "object", properties: { modelId: { type: "string", description: "The ID of the model to use" }, message: { type: "string", description: "The message to send to the model" }, conversationHistory: { type: "array", description: "Previous conversation messages (optional)", items: { type: "object", properties: { role: { type: "string", enum: ["user", "assistant", "system"] }, content: { type: "string" } }, required: ["role", "content"] } }, enableWebSearch: { type: "boolean", description: "Enable web search for enhanced responses (default: false)" }, temperature: { type: "number", description: "Control randomness (0.0-2.0, default: 0.7)", minimum: 0, maximum: 2 }, maxTokens: { type: "number", description: "Maximum tokens to generate (default: 12000)", minimum: 1, maximum: 200000 } }, required: ["modelId", "message"] } }, { name: "compare_models", description: "Compare responses from multiple AI models for the same prompt (OpenAI o3, Claude 4 Sonnet, Gemini 2.5 pro, DeepSeek R1, XAI Grok 3 and many more) and returns a tryaii URL report (https://tryaii.com/report/...) to the user", inputSchema: { type: "object", properties: { modelIds: { type: "array", description: "Array of model IDs to compare", items: { type: "string" }, minItems: 2, maxItems: 10 }, message: { type: "string", description: "The message/prompt to test with all models" }, enableWebSearch: { type: "boolean", description: "Enable web search for enhanced responses (default: false)" }, temperature: { type: "number", description: "Control randomness (0.0-2.0, default: 0.7)", minimum: 0, maximum: 2 }, maxTokens: { type: "number", description: "Maximum tokens to generate per model (default: 12000)", minimum: 1000, maximum: 20000 } }, required: ["modelIds", "message"] } }, { name: "brains", description: "Get responses from multiple top AI models (OpenAI o3, Claude 4 Sonnet, Gemini 2.5 pro, DeepSeek R1, XAI Grok 3) simultaneously and returns a tryaii URL report (https://tryaii.com/report/...) to the user", inputSchema: { type: "object", properties: { question: { type: "string", description: "The question or prompt to ask all models" }, enableWebSearch: { type: "boolean", description: "Enable web search for enhanced responses (default: false)" }, temperature: { type: "number", description: "Control randomness (0.0-2.0, default: 0.7)", minimum: 0.5, maximum: 2 }, maxTokens: { type: "number", description: "Maximum tokens to generate per model (default: 12000)", minimum: 1000, maximum: 20000 } }, required: ["question"] } }, { 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"] } }, ] }; }); // ============================================================================================ // TOOL HANDLERS // ============================================================================================ server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case "get_available_models": { const { provider } = args; try { // Fetch models from the API server instead of local registry const result = await apiClient.getAvailableModels(provider); return { content: [ { type: "text", text: JSON.stringify(result, null, 2) } ] }; } catch (error) { // Fallback to local registry if API fails console.warn('Failed to fetch models from API, using local registry:', error instanceof Error ? error.message : String(error)); let models = AVAILABLE_MODELS; if (provider) { models = models.filter(model => model.provider.toLowerCase() === provider.toLowerCase()); } return { content: [ { type: "text", text: JSON.stringify({ models: models.map(model => ({ id: model.id, name: model.name, provider: model.provider, description: model.description, capabilities: model.capabilities, pricing: { inputCostPer1kTokens: model.inputCostPer1kTokens, outputCostPer1kTokens: model.outputCostPer1kTokens }, limits: { maxContextTokens: model.maxContextTokens ? model.maxContextTokens < 2000 ? 2000 : model.maxContextTokens : 2000 }, features: { webSearch: model.webSearch, reasoning: model.reasoning, latencySpeed: model.latencySpeed } })), metadata: { totalModels: models.length, providers: [...new Set(models.map(m => m.provider))], timestamp: new Date().toISOString(), source: 'local_fallback' } }, null, 2) } ] }; } } case "chat_with_model": { const result = await apiClient.chatWithModel(args); return { content: [ { type: "text", text: JSON.stringify(result, null, 2) } ] }; } case "compare_models": { const result = await apiClient.compareModels(args); return { content: [ { type: "text", text: JSON.stringify(result, null, 2) } ] }; } case "brains": { const result = await apiClient.brains(args); return { content: [ { type: "text", text: JSON.stringify(result, null, 2) } ] }; } case "get_model_info": { const result = await apiClient.getModelInfo(args); return { content: [ { type: "text", text: JSON.stringify(result, null, 2) } ] }; } default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); } } catch (error) { if (error instanceof McpError) { throw error; } throw new McpError(ErrorCode.InternalError, `Tool execution failed: ${error instanceof Error ? error.message : String(error)}`); } }); // ============================================================================================ // SERVER STARTUP // ============================================================================================ async function main() { const transport = new StdioServerTransport(); await server.connect(transport); } main().catch((error) => { console.error('❌ Server startup failed:', error); process.exit(1); });