UNPKG

converse-mcp-server

Version:

Converse MCP Server - Converse with other LLMs with chat and consensus tools

552 lines (466 loc) โ€ข 15 kB
# Converse MCP Server - Architecture Overview ## ๐Ÿ—๏ธ System Architecture The Converse MCP Server follows a **functional, modular architecture** designed for simplicity, maintainability, and performance. It implements the Model Context Protocol (MCP) to provide AI capabilities through multiple providers. ## ๐Ÿ“ Project Structure ``` src/ โ”œโ”€โ”€ index.js # Main entry point & MCP server setup โ”œโ”€โ”€ config.js # Configuration and environment management โ”œโ”€โ”€ systemPrompts.js # System prompts for tools โ”œโ”€โ”€ providers/ # AI provider implementations โ”‚ โ”œโ”€โ”€ registry.js # Provider registry and management โ”‚ โ”œโ”€โ”€ openai.js # OpenAI provider implementation โ”‚ โ”œโ”€โ”€ google.js # Google/Gemini provider implementation โ”‚ โ””โ”€โ”€ xai.js # X.AI/Grok provider implementation โ”œโ”€โ”€ tools/ # MCP tool implementations โ”‚ โ”œโ”€โ”€ chat.js # Single-provider chat tool โ”‚ โ””โ”€โ”€ consensus.js # Multi-provider consensus tool โ”œโ”€โ”€ utils/ # Utility functions โ”‚ โ”œโ”€โ”€ logger.js # Structured logging โ”‚ โ”œโ”€โ”€ context.js # File and image processing โ”‚ โ”œโ”€โ”€ continuation.js # Conversation persistence โ”‚ โ””โ”€โ”€ validators.js # Input validation bin/ โ”œโ”€โ”€ converse.js # CLI entry point for npx execution docs/ # Documentation tests/ # Test suites ``` ## ๐Ÿ”„ Core Design Principles ### 1. Functional Programming - **No Classes**: Pure functions and modules only - **Immutable Data**: Avoid state mutations where possible - **Composable Functions**: Small, focused, reusable functions - **Error Boundaries**: Explicit error handling at module boundaries ### 2. Provider Abstraction - **Unified Interface**: All providers implement consistent API - **Auto-Discovery**: Providers register themselves dynamically - **Graceful Degradation**: System works with any subset of providers - **Parallel Execution**: Multiple providers can run simultaneously ### 3. Tool Architecture - **Minimal Interface**: Tools expose simple, focused functionality - **Context Processing**: Standardized file and image handling - **Continuation Support**: Persistent conversation management - **Parameter Validation**: Comprehensive input validation ## ๐Ÿ”Œ MCP Integration Layer ### Server Setup **HTTP Transport (Default):** ```javascript // index.js - HTTP transport setup import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; const server = new Server( { name: 'converse-mcp-server', version: '1.0.0' }, { capabilities: { tools: {} } } ); // HTTP transport on port 3157 (default) const httpTransport = new StreamableHTTPServerTransport({ host: 'localhost', port: 3157 }); ``` **Stdio Transport (Legacy):** ```javascript // Alternative stdio transport import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; const transport = new StdioServerTransport(); await server.connect(transport); ``` **Transport Selection:** - **HTTP**: Default, better for development and debugging - **Stdio**: Use `--transport=stdio` or `MCP_TRANSPORT=stdio` ### Tool Registration ```javascript // Dynamic tool registration server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; switch (name) { case 'chat': return await chatTool(args); case 'consensus': return await consensusTool(args); default: throw new McpError(ErrorCode.MethodNotFound, `Tool not found: ${name}`); } }); ``` ## ๐Ÿค– Provider System ### Provider Registry Pattern ```javascript // providers/registry.js const providers = new Map(); export function registerProvider(name, implementation) { if (implementation.isAvailable()) { providers.set(name, implementation); } } export function getProvider(name) { return providers.get(name); } export function getAvailableProviders() { return Array.from(providers.keys()); } ``` ### Provider Implementation Contract ```javascript // Each provider must implement: export const providerImplementation = { // Check if provider is configured and available isAvailable: () => Boolean(process.env.API_KEY), // Get supported models getSupportedModels: () => ['model1', 'model2'], // Main chat completion method chatCompletion: async (messages, options) => { // Implementation details... return { content: 'Response text', usage: { input_tokens: 100, output_tokens: 50 } }; }, // Provider name for logging/tracking name: 'provider-name' }; ``` ### Model Resolution ```javascript // Automatic model resolution across providers export function resolveModel(modelName) { // Handle aliases (flash -> gemini-2.5-flash) const resolvedName = MODEL_ALIASES[modelName] || modelName; // Find provider that supports this model const provider = findProviderForModel(resolvedName); return { provider, model: resolvedName }; } ``` ## ๐Ÿ› ๏ธ Tool Architecture ### Tool Implementation Pattern ```javascript // tools/example.js export const exampleTool = { definition: { name: 'example', description: 'Example tool description', inputSchema: { type: 'object', properties: { prompt: { type: 'string', description: 'User prompt' } }, required: ['prompt'] } }, handler: async (args) => { // 1. Validate input const validation = validateInput(args); if (!validation.valid) { throw new McpError(ErrorCode.InvalidParams, validation.error); } // 2. Process context (files, images) const context = await processContext(args.files, args.images); // 3. Execute main logic const result = await executeLogic(args, context); // 4. Return standardized response return formatResponse(result); } }; ``` ### Context Processing Pipeline ```javascript // utils/context.js export async function processContext(files = [], images = []) { return { fileContext: await processFiles(files), imageContext: await processImages(images) }; } async function processFiles(filePaths) { return await Promise.all( filePaths.map(async (path) => { const content = await readFile(path); return { path, content: addLineNumbers(content), metadata: { size: content.length, type: getFileType(path) } }; }) ); } ``` ## ๐Ÿ”„ Data Flow ### Single Tool Execution (Chat) ``` User Request โ†“ Input Validation โ†“ Context Processing (files/images) โ†“ Provider Selection (auto/manual) โ†“ Model Resolution โ†“ API Call to Provider โ†“ Response Processing โ†“ Continuation Management โ†“ Response to Client ``` ### Multi-Provider Execution (Consensus) ``` User Request โ†“ Input Validation โ†“ Context Processing โ†“ Provider Selection (multiple) โ†“ Parallel Execution โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€ Provider A โ”œโ”€โ”€โ”€ Provider B โ””โ”€โ”€โ”€ Provider C โ†“ Initial Response Collection โ†“ Cross-Feedback Phase (optional) โ†“ Parallel Refinement โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€ Provider A (sees B,C) โ”œโ”€โ”€โ”€ Provider B (sees A,C) โ””โ”€โ”€โ”€ Provider C (sees A,B) โ†“ Final Response Aggregation โ†“ Response to Client ``` ## ๐Ÿ”ง Configuration System ### Environment-Driven Configuration ```javascript // config.js export const config = { // Provider API keys providers: { openai: { apiKey: process.env.OPENAI_API_KEY }, google: { apiKey: process.env.GOOGLE_API_KEY }, xai: { apiKey: process.env.XAI_API_KEY } }, // Server settings server: { port: parseInt(process.env.PORT) || 3157, logLevel: process.env.LOG_LEVEL || 'info', maxOutputTokens: parseInt(process.env.MAX_MCP_OUTPUT_TOKENS) || 25000 }, // Model mappings and aliases models: { aliases: { 'flash': 'gemini-2.5-flash', 'pro': 'gemini-2.5-pro', 'grok': 'grok-4-0709' } } }; ``` ### Dynamic Provider Registration ```javascript // index.js - Provider initialization async function initializeProviders() { const providerModules = [ await import('./providers/openai.js'), await import('./providers/google.js'), await import('./providers/xai.js') ]; for (const module of providerModules) { const provider = module.default; if (provider.isAvailable()) { registerProvider(provider.name, provider); logger.info(`Registered provider: ${provider.name}`); } } } ``` ## ๐Ÿ“Š State Management ### Stateless Design - **No Global State**: Each request is independent - **Continuation Storage**: Conversations stored as isolated state - **Provider Independence**: Providers don't share state - **Immutable Responses**: Responses are constructed, not modified ### Continuation System ```javascript // utils/continuation.js const continuations = new Map(); export function storeContinuation(id, data) { continuations.set(id, { ...data, lastAccessed: Date.now(), messageCount: (data.messageCount || 0) + 1 }); } export function getContinuation(id) { const continuation = continuations.get(id); if (continuation) { continuation.lastAccessed = Date.now(); } return continuation; } ``` ## ๐Ÿš€ Performance Characteristics ### Parallel Execution - **Consensus Tool**: Executes all providers simultaneously - **Non-Blocking I/O**: All async operations use Promise.all() - **Provider Isolation**: One provider failure doesn't affect others - **Request Batching**: Multiple requests handled concurrently ### Memory Management - **Streaming Responses**: Large responses handled efficiently - **Context Cleanup**: Old continuations automatically expire - **File Processing**: Files read on-demand, not cached - **Provider Pooling**: Connection reuse where possible ### Error Resilience ```javascript // Graceful error handling pattern async function executeWithFallback(primaryFn, fallbackFn) { try { return await primaryFn(); } catch (primaryError) { logger.warn('Primary execution failed, attempting fallback', { error: primaryError.message }); try { return await fallbackFn(); } catch (fallbackError) { logger.error('Both primary and fallback failed', { primaryError: primaryError.message, fallbackError: fallbackError.message }); throw new McpError(ErrorCode.InternalError, 'All execution attempts failed'); } } } ``` ## ๐Ÿ”’ Security Architecture ### Input Validation ```javascript // utils/validators.js export function validateChatInput(args) { const errors = []; if (!args.prompt || typeof args.prompt !== 'string') { errors.push('prompt must be a non-empty string'); } if (args.files && !Array.isArray(args.files)) { errors.push('files must be an array'); } return { valid: errors.length === 0, errors }; } ``` ### Path Security ```javascript // Prevent path traversal attacks function validateFilePath(filePath) { const normalized = path.resolve(filePath); const allowed = path.resolve(process.cwd()); if (!normalized.startsWith(allowed)) { throw new McpError(ErrorCode.InvalidParams, 'Access denied: Path outside allowed directory'); } return normalized; } ``` ### API Key Protection - **Environment Variables**: Keys never hardcoded - **No Logging**: API keys excluded from all logs - **Provider Isolation**: Keys scoped to specific providers - **Error Sanitization**: Error messages don't expose keys ## ๐Ÿ” Observability ### Structured Logging ```javascript // utils/logger.js export const logger = { info: (message, meta = {}) => { console.log(JSON.stringify({ level: 'info', timestamp: new Date().toISOString(), message, ...meta })); }, error: (message, error = {}) => { console.error(JSON.stringify({ level: 'error', timestamp: new Date().toISOString(), message, error: { name: error.name, message: error.message, stack: error.stack } })); } }; ``` ### Request Tracing ```javascript // Request correlation IDs function generateRequestId() { return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } // All operations tagged with request ID logger.info('Processing chat request', { requestId, provider: 'openai', model: 'gpt-4o' }); ``` ## ๐Ÿ“ˆ Extensibility ### Adding New Providers 1. Create provider module in `src/providers/` 2. Implement the provider contract 3. Export as default 4. Provider auto-registers if API key is available ### Adding New Tools 1. Create tool module in `src/tools/` 2. Define tool schema and handler 3. Register in main server setup 4. Add to MCP tool list ### Configuration Extensions ```javascript // Adding new configuration options export const config = { // ... existing config // New feature config experimental: { enableFeatureX: process.env.ENABLE_FEATURE_X === 'true', featureXTimeout: parseInt(process.env.FEATURE_X_TIMEOUT) || 30000 } }; ``` ## ๐Ÿงช Testing Architecture ### Test Strategy - **Unit Tests**: Individual functions and modules - **Integration Tests**: Provider interactions (mocked APIs) - **E2E Tests**: Full request/response cycles - **Contract Tests**: Provider interface compliance ### Test Organization ``` tests/ โ”œโ”€โ”€ unit/ # Unit tests for individual functions โ”œโ”€โ”€ integration/ # Integration tests with mocked dependencies โ”œโ”€โ”€ e2e/ # End-to-end tests โ”œโ”€โ”€ fixtures/ # Test data and mocks โ””โ”€โ”€ helpers/ # Test utilities ``` ## ๐Ÿ”ง Development Workflow ### Hot Reload Development ```bash # Development with auto-restart npm run dev # Debug mode with inspection npm run debug ``` ### Code Quality Pipeline ```bash # Full validation pipeline npm run validate # Individual checks npm run lint npm run typecheck npm run test npm run format:check ``` --- This architecture emphasizes **simplicity**, **reliability**, and **extensibility** while maintaining high performance through parallel execution and efficient resource management.