trading-platform-mcp-invest.../src/index.ts
2026-01-16 08:33:11 -06:00

299 lines
10 KiB
TypeScript

/**
* 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;