/** * MCP Server: MT4 Connector * * Exposes MT4 trading capabilities as MCP tools for AI agents. * Communicates with mt4-gateway service to execute trading operations. * * @version 0.1.0 * @author Trading Platform Trading Platform */ import express, { Request, Response, NextFunction } from 'express'; import dotenv from 'dotenv'; import { mcpToolSchemas, toolHandlers } from './tools'; import { getMT4Client } from './services/mt4-client'; // Load environment variables dotenv.config(); const app = express(); const PORT = process.env.PORT || 3605; const SERVICE_NAME = 'mcp-mt4-connector'; const VERSION = '0.1.0'; // ========================================== // Middleware // ========================================== app.use(express.json()); // Request logging app.use((req: Request, _res: Response, next: NextFunction) => { console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`); next(); }); // ========================================== // Health & Status Endpoints // ========================================== /** * Health check endpoint */ app.get('/health', async (_req: Request, res: Response) => { try { const client = getMT4Client(); const mt4Connected = await client.isConnected(); res.json({ status: 'ok', service: SERVICE_NAME, version: VERSION, timestamp: new Date().toISOString(), dependencies: { mt4Gateway: mt4Connected ? 'connected' : 'disconnected', }, }); } catch (error) { res.json({ status: 'degraded', service: SERVICE_NAME, version: VERSION, timestamp: new Date().toISOString(), dependencies: { mt4Gateway: 'error', }, error: error instanceof Error ? error.message : 'Unknown error', }); } }); /** * List available MCP tools */ app.get('/tools', (_req: Request, res: Response) => { res.json({ tools: mcpToolSchemas, count: mcpToolSchemas.length, }); }); /** * Get specific tool schema */ app.get('/tools/:toolName', (req: Request, res: Response) => { const { toolName } = req.params; const tool = mcpToolSchemas.find(t => t.name === toolName); if (!tool) { res.status(404).json({ error: `Tool '${toolName}' not found`, availableTools: mcpToolSchemas.map(t => t.name), }); return; } res.json(tool); }); // ========================================== // MCP Tool Execution Endpoints // ========================================== /** * Execute an MCP tool * POST /tools/:toolName * Body: { parameters: {...} } */ app.post('/tools/:toolName', async (req: Request, res: Response) => { const { toolName } = req.params; const { parameters = {} } = req.body; // Validate tool exists const handler = toolHandlers[toolName]; if (!handler) { res.status(404).json({ success: false, error: `Tool '${toolName}' not found`, availableTools: Object.keys(toolHandlers), }); return; } try { console.log(`[${new Date().toISOString()}] Executing tool: ${toolName}`); console.log(`Parameters: ${JSON.stringify(parameters)}`); const result = await handler(parameters); res.json({ success: true, tool: toolName, result, }); } catch (error) { console.error(`[${new Date().toISOString()}] Tool error: ${toolName}`, error); // Handle Zod validation errors if (error && typeof error === 'object' && 'issues' in error) { res.status(400).json({ success: false, error: 'Validation error', details: (error as { issues: unknown[] }).issues, }); return; } res.status(500).json({ success: false, error: error instanceof Error ? error.message : 'Unknown error', }); } }); // ========================================== // MCP Protocol Endpoints (Standard) // ========================================== /** * MCP Initialize * Returns server capabilities */ app.post('/mcp/initialize', (_req: Request, res: Response) => { res.json({ protocolVersion: '2024-11-05', capabilities: { tools: {}, }, serverInfo: { name: SERVICE_NAME, version: VERSION, }, }); }); /** * MCP List Tools * Returns all available tools in MCP format */ app.post('/mcp/tools/list', (_req: Request, res: Response) => { res.json({ tools: mcpToolSchemas.map(tool => ({ name: tool.name, description: tool.description, inputSchema: tool.inputSchema, })), }); }); /** * MCP Call Tool * Execute a tool with parameters */ app.post('/mcp/tools/call', async (req: Request, res: Response) => { const { name, arguments: args = {} } = req.body; if (!name) { res.status(400).json({ error: { code: 'invalid_request', message: 'Tool name is required', }, }); return; } const handler = toolHandlers[name]; if (!handler) { res.status(404).json({ error: { code: 'unknown_tool', message: `Tool '${name}' not found`, }, }); return; } try { const result = await handler(args); res.json(result); } catch (error) { // Handle Zod validation errors if (error && typeof error === 'object' && 'issues' in error) { res.status(400).json({ error: { code: 'invalid_params', message: 'Invalid tool parameters', data: (error as { issues: unknown[] }).issues, }, }); return; } res.status(500).json({ error: { code: 'internal_error', message: error instanceof Error ? error.message : 'Unknown error', }, }); } }); // ========================================== // Error Handler // ========================================== app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => { console.error(`[${new Date().toISOString()}] Unhandled error:`, err); res.status(500).json({ error: 'Internal server error', message: err.message, }); }); // ========================================== // Start Server // ========================================== app.listen(PORT, () => { console.log(''); console.log('╔══════════════════════════════════════════════════════════╗'); console.log('║ MCP MT4 Connector - Trading Platform ║'); console.log('╠══════════════════════════════════════════════════════════╣'); console.log(`║ Service: ${SERVICE_NAME.padEnd(45)}║`); console.log(`║ Version: ${VERSION.padEnd(45)}║`); console.log(`║ Port: ${String(PORT).padEnd(48)}║`); console.log('╠══════════════════════════════════════════════════════════╣'); console.log('║ Endpoints: ║'); console.log(`║ - Health: http://localhost:${PORT}/health`.padEnd(63) + '║'); console.log(`║ - Tools: http://localhost:${PORT}/tools`.padEnd(63) + '║'); console.log('╠══════════════════════════════════════════════════════════╣'); console.log('║ MCP Tools Available: ║'); mcpToolSchemas.forEach(tool => { console.log(`║ - ${tool.name.padEnd(54)}║`); }); console.log('╚══════════════════════════════════════════════════════════╝'); console.log(''); }); // ========================================== // Graceful Shutdown // ========================================== process.on('SIGTERM', () => { console.log('Received SIGTERM, shutting down gracefully...'); process.exit(0); }); process.on('SIGINT', () => { console.log('Received SIGINT, shutting down gracefully...'); process.exit(0); });