tryaii-mcp-server
Version:
TryAII MCP Server - 15+ AI models with comparison, cost tracking, and collective intelligence
432 lines • 19.9 kB
JavaScript
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