UNPKG

@matcha-chai/mcp-matchai-team

Version:

Access the entire Matchai AI development team through a single MCP interface - powered by n8n, LLMs, and Graphiti temporal knowledge graph

258 lines (256 loc) 11.5 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import axios from 'axios'; import { z } from 'zod'; import dotenv from 'dotenv'; // Load environment variables dotenv.config(); // Schema for matchai-team tool input const MatchaiTeamInputSchema = z.object({ prompt: z.string().describe('Your request or question to the Matchai Team'), context: z.any().optional().describe('Additional context relevant to the request'), }); class MatchaiTeamServer { server; webhookUrl; apiKey; validationWebhookUrl = 'https://matchai.app.n8n.cloud/webhook/verify-matchai-team-api-key'; constructor() { // Check for required API key const apiKey = process.env.MATCHAI_TEAM_API_KEY; if (!apiKey) { console.error(''); console.error('❌ ERROR: MATCHAI_TEAM_API_KEY is required'); console.error(''); console.error('Please set the MATCHAI_TEAM_API_KEY environment variable.'); console.error(''); console.error('Example usage:'); console.error(' MATCHAI_TEAM_API_KEY=your-key-here npx @matcha-chai/mcp-matchai-team'); console.error(''); console.error('To get your API key, contact the Matchai team.'); console.error(''); process.exit(1); } this.webhookUrl = process.env.MATCHAI_TEAM_URL || 'https://matchai.app.n8n.cloud/webhook/matchai-team'; this.apiKey = apiKey; this.server = new Server({ name: 'matchai-team', version: '1.0.3', }, { capabilities: { tools: {}, }, }); this.setupHandlers(); } /** * Validates the API key by calling the n8n webhook * @returns Promise<boolean> - true if valid, false otherwise */ async validateApiKey() { try { console.error('🔐 Validating API key...'); // Call the n8n webhook to validate the API key const response = await axios.post(this.validationWebhookUrl, { // Empty body - API key is sent in headers source: 'mcp-server-startup', timestamp: new Date().toISOString(), }, { headers: { 'X-API-Key': this.apiKey, 'Content-Type': 'application/json', }, timeout: 5000, // 5 second timeout for auth check }); // Check if the response indicates success if (response.data?.success === true) { // Log key metadata if available if (response.data.keyMetadata) { console.error(`✅ API key validated: ${response.data.keyMetadata.name || 'Unknown'}`); console.error(` Environment: ${response.data.keyMetadata.environment || 'Unknown'}`); console.error(` Permissions: ${response.data.keyMetadata.permissions?.join(', ') || 'Unknown'}`); } else { console.error('✅ API key validated successfully'); } return true; } // Validation failed but no error was thrown console.error('❌ API key validation failed: Invalid response from validation service'); return false; } catch (error) { if (axios.isAxiosError(error)) { if (error.response?.status === 401) { // Invalid or missing API key const errorData = error.response.data; console.error(`❌ API key validation failed: ${errorData?.message || 'Invalid API key'}`); if (errorData?.code) { console.error(` Error code: ${errorData.code}`); } return false; } if (error.code === 'ECONNABORTED') { console.error('❌ API key validation timeout - validation service did not respond'); return false; } if (error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED') { console.error('❌ Could not connect to validation service'); console.error(' Please check your internet connection and try again'); return false; } } // For other errors, fail closed for security console.error('❌ Unexpected error during API key validation:', error.message); return false; } } /** * Validates the API key on server startup */ async validateOnStartup() { const isValid = await this.validateApiKey(); if (!isValid) { console.error(''); console.error('❌ ERROR: API key validation failed'); console.error(''); console.error('The provided API key was rejected by the authentication service.'); console.error('This could mean:'); console.error(' • The API key is invalid or expired'); console.error(' • The API key has incorrect format'); console.error(' • The validation service is unavailable'); console.error(''); console.error('Please verify your API key and try again.'); console.error('Contact the Matchai team if you need assistance.'); console.error(''); process.exit(1); } } setupHandlers() { // Handler for listing available tools this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'matchai-team', description: `Access the entire Matchai AI development team through natural language. The team includes: - Backend Engineer Agent: Handles database architecture, API development, workflow automation, security, performance optimization, and technical implementation - Business Logic Analyst Agent: Analyzes requirements, documents processes, designs domain models, optimizes logic, and ensures business alignment Powered by n8n workflows, advanced LLMs, and Graphiti temporal knowledge graph for continuous learning. The team collaborates to deliver comprehensive solutions.`, inputSchema: { type: 'object', properties: { prompt: { type: 'string', description: 'Your request or question to the Matchai Team', }, context: { type: 'object', description: 'Additional context relevant to the request', }, }, required: ['prompt'], }, }, ], })); // Handler for calling the matchai-team tool this.server.setRequestHandler(CallToolRequestSchema, async (request) => { if (request.params.name !== 'matchai-team') { throw new Error(`Unknown tool: ${request.params.name}`); } try { // Validate input const input = MatchaiTeamInputSchema.parse(request.params.arguments); // Call the n8n webhook with the request const response = await axios.post(this.webhookUrl, { prompt: input.prompt, context: input.context || {}, source: 'mcp-server', timestamp: new Date().toISOString(), }, { headers: { 'Content-Type': 'application/json', 'X-API-Key': this.apiKey, }, timeout: 300000, // 5 minute timeout for complex operations }); // Return the response from the Matchai Team return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error) { // Handle errors gracefully if (axios.isAxiosError(error)) { if (error.response) { // Server responded with error return { content: [ { type: 'text', text: JSON.stringify({ error: 'Matchai Team error', status: error.response.status, message: error.response.data?.message || error.message, details: error.response.data, }, null, 2), }, ], }; } else if (error.request) { // No response received return { content: [ { type: 'text', text: JSON.stringify({ error: 'Connection error', message: 'Could not reach the Matchai Team. Please check your connection and try again.', details: error.message, }, null, 2), }, ], }; } } // Generic error return { content: [ { type: 'text', text: JSON.stringify({ error: 'Unexpected error', message: error.message || 'An unexpected error occurred', }, null, 2), }, ], }; } }); } async run() { // Validate API key before starting the server await this.validateOnStartup(); const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('✅ Matchai Team MCP Server running'); console.error('📍 Webhook URL: ' + this.webhookUrl); console.error('🔑 API Key: [VALIDATED]'); console.error('🤖 Available tool: matchai-team'); } } // Main execution const server = new MatchaiTeamServer(); server.run().catch((error) => { console.error('Failed to start server:', error); process.exit(1); }); //# sourceMappingURL=index.js.map