@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
JavaScript
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