/** * MCP Investment Server * Agent allocations and profit distribution */ import 'dotenv/config'; import express, { Request, Response, NextFunction } from 'express'; import helmet from 'helmet'; import cors from 'cors'; import { serverConfig, closePool } from './config'; import { logger } from './utils/logger'; import { allToolSchemas, allToolHandlers, listTools } from './tools'; import { authMiddleware, adminMiddleware } from './middleware'; const app = express(); // Middleware app.use(helmet()); app.use(cors()); app.use(express.json({ limit: '1mb' })); // Request logging app.use((req: Request, _res: Response, next: NextFunction) => { logger.debug('Incoming request', { method: req.method, path: req.path, }); next(); }); // Health check app.get('/health', (_req: Request, res: Response) => { res.json({ status: 'healthy', service: 'mcp-investment', timestamp: new Date().toISOString() }); }); // MCP Endpoints // List available tools app.get('/mcp/tools', (_req: Request, res: Response) => { res.json({ tools: listTools() }); }); // Execute tool app.post('/mcp/tools/:toolName', async (req: Request, res: Response) => { const { toolName } = req.params; const handler = allToolHandlers[toolName]; if (!handler) { res.status(404).json({ success: false, error: `Tool '${toolName}' not found`, code: 'TOOL_NOT_FOUND' }); return; } try { const result = await handler(req.body); res.json(result); } catch (error) { logger.error('Tool execution error', { toolName, error }); res.status(500).json({ success: false, error: 'Internal server error', code: 'INTERNAL_ERROR' }); } }); // ============================================================================ // REST API Endpoints - Protected by Auth // ============================================================================ // Agent configs (public info, but tenant-scoped) const agentsRouter = express.Router(); agentsRouter.use(authMiddleware); agentsRouter.get('/', async (req: Request, res: Response) => { const result = await allToolHandlers.investment_list_agents({ tenantId: req.tenantId }); res.json(result); }); agentsRouter.get('/:agentType', async (req: Request, res: Response) => { const result = await allToolHandlers.investment_get_agent({ tenantId: req.tenantId, agentType: req.params.agentType, }); res.json(result); }); agentsRouter.get('/:agentType/performance', async (req: Request, res: Response) => { const result = await allToolHandlers.investment_get_agent_performance({ tenantId: req.tenantId, agentType: req.params.agentType, periodDays: req.query.periodDays ? parseInt(req.query.periodDays as string, 10) : undefined, }); res.json(result); }); agentsRouter.get('/:agentType/distributions', async (req: Request, res: Response) => { const result = await allToolHandlers.investment_get_distributions({ tenantId: req.tenantId, agentType: req.params.agentType, limit: req.query.limit ? parseInt(req.query.limit as string, 10) : undefined, }); res.json(result); }); app.use('/api/v1/agents', agentsRouter); // Allocations - Protected const allocationsRouter = express.Router(); allocationsRouter.use(authMiddleware); // Create allocation allocationsRouter.post('/', async (req: Request, res: Response) => { const result = await allToolHandlers.investment_create_allocation({ ...req.body, tenantId: req.tenantId, userId: req.userId, }); res.status(result.success ? 201 : 400).json(result); }); // List allocations allocationsRouter.get('/', async (req: Request, res: Response) => { const result = await allToolHandlers.investment_list_allocations({ tenantId: req.tenantId, userId: req.query.userId || req.userId, // Default to current user agentType: req.query.agentType, status: req.query.status, limit: req.query.limit ? parseInt(req.query.limit as string, 10) : undefined, offset: req.query.offset ? parseInt(req.query.offset as string, 10) : undefined, }); res.json(result); }); // Get allocation allocationsRouter.get('/:allocationId', async (req: Request, res: Response) => { const result = await allToolHandlers.investment_get_allocation({ tenantId: req.tenantId, allocationId: req.params.allocationId, }); res.json(result); }); // Fund allocation allocationsRouter.post('/:allocationId/fund', async (req: Request, res: Response) => { const result = await allToolHandlers.investment_fund_allocation({ ...req.body, tenantId: req.tenantId, allocationId: req.params.allocationId, userId: req.userId, }); res.json(result); }); // Withdraw from allocation allocationsRouter.post('/:allocationId/withdraw', async (req: Request, res: Response) => { const result = await allToolHandlers.investment_withdraw({ ...req.body, tenantId: req.tenantId, allocationId: req.params.allocationId, userId: req.userId, }); res.json(result); }); // Update allocation status allocationsRouter.patch('/:allocationId/status', async (req: Request, res: Response) => { const result = await allToolHandlers.investment_update_status({ ...req.body, tenantId: req.tenantId, allocationId: req.params.allocationId, }); res.json(result); }); // Get allocation transactions allocationsRouter.get('/:allocationId/transactions', async (req: Request, res: Response) => { const result = await allToolHandlers.investment_get_transactions({ tenantId: req.tenantId, allocationId: req.params.allocationId, limit: req.query.limit ? parseInt(req.query.limit as string, 10) : undefined, offset: req.query.offset ? parseInt(req.query.offset as string, 10) : undefined, }); res.json(result); }); app.use('/api/v1/allocations', allocationsRouter); // User endpoints - Protected const userRouter = express.Router(); userRouter.use(authMiddleware); // Get current user's allocations userRouter.get('/me/allocations', async (req: Request, res: Response) => { const result = await allToolHandlers.investment_get_user_allocations({ tenantId: req.tenantId, userId: req.userId, }); res.json(result); }); // Get current user's investment summary userRouter.get('/me/investment-summary', async (req: Request, res: Response) => { const result = await allToolHandlers.investment_get_user_summary({ tenantId: req.tenantId, userId: req.userId, }); res.json(result); }); // Get specific user's allocations (for admins or own data) userRouter.get('/:userId/allocations', async (req: Request, res: Response) => { // Users can only see their own allocations unless they're owners if (req.params.userId !== req.userId && !req.isOwner) { res.status(403).json({ success: false, error: 'Forbidden', code: 'ACCESS_DENIED' }); return; } const result = await allToolHandlers.investment_get_user_allocations({ tenantId: req.tenantId, userId: req.params.userId, }); res.json(result); }); // Get specific user's investment summary userRouter.get('/:userId/investment-summary', async (req: Request, res: Response) => { if (req.params.userId !== req.userId && !req.isOwner) { res.status(403).json({ success: false, error: 'Forbidden', code: 'ACCESS_DENIED' }); return; } const result = await allToolHandlers.investment_get_user_summary({ tenantId: req.tenantId, userId: req.params.userId, }); res.json(result); }); app.use('/api/v1/users', userRouter); // Admin endpoints - Protected + Admin only const adminRouter = express.Router(); adminRouter.use(authMiddleware); adminRouter.use(adminMiddleware); // Distribute profits (admin only) adminRouter.post('/distribute-profits', async (req: Request, res: Response) => { const result = await allToolHandlers.investment_distribute_profits({ ...req.body, tenantId: req.tenantId, distributedBy: req.userId, }); res.json(result); }); app.use('/api/v1/admin', adminRouter); // ============================================================================ // Error Handling // ============================================================================ app.use((_req: Request, res: Response) => { res.status(404).json({ success: false, error: 'Not found' }); }); app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => { logger.error('Unhandled error', { error: err.message, stack: err.stack }); res.status(500).json({ success: false, error: 'Internal server error' }); }); // ============================================================================ // Server Startup // ============================================================================ async function shutdown() { logger.info('Shutting down...'); await closePool(); process.exit(0); } process.on('SIGTERM', shutdown); process.on('SIGINT', shutdown); app.listen(serverConfig.port, () => { logger.info(`MCP Investment Server running on port ${serverConfig.port}`, { env: serverConfig.env, tools: Object.keys(allToolSchemas).length, }); console.log(` ╔════════════════════════════════════════════════════════════╗ ║ MCP INVESTMENT SERVER ║ ╠════════════════════════════════════════════════════════════╣ ║ Port: ${serverConfig.port} ║ ║ Tools: ${String(Object.keys(allToolSchemas).length).padEnd(12)} ║ ╠════════════════════════════════════════════════════════════╣ ║ /api/v1/agents/* - Agent info ║ ║ /api/v1/allocations/* - Investment allocations ║ ║ /api/v1/users/* - User investments ║ ║ /api/v1/admin/* - Admin operations ║ ╚════════════════════════════════════════════════════════════╝ `); }); export default app;