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 { zodToJsonSchema } from 'zod-to-json-schema';
import pino from 'pino';
import { ExchangeAdapter } from './exchange.js';
import { createTools } from './tools/index.js';
import { ToolRegistry } from './tool-registry.js';
const logger = pino(
{ level: process.env.LOG_LEVEL || 'info' },
pino.destination(2)
);
export class TradingMCPServer {
constructor(config = {}) {
this.config = {
exchange: config.exchange || 'mexc',
apiKey: config.apiKey,
secret: config.secret,
password: config.password,
mode: config.mode || 'demo',
marketType: config.marketType || 'spot'
};
this.server = new Server(
{ name: 'trading-mcp-server', version: '1.2.0' },
{ capabilities: { tools: {} } }
);
this.exchange = null;
this.toolRegistry = new ToolRegistry();
}
async initialize() {
logger.info({ exchange: this.config.exchange, mode: this.config.mode }, 'Initializing');
if (!this.config.apiKey || !this.config.secret) {
throw new Error('API key and secret are required');
}
this.exchange = new ExchangeAdapter(
this.config.exchange,
{
apiKey: this.config.apiKey,
secret: this.config.secret,
password: this.config.password
},
this.config.marketType
);
const tools = createTools(this.exchange, this.config.mode);
this.toolRegistry.registerTools(tools);
this.setupHandlers();
logger.info({ toolCount: this.toolRegistry.getToolCount() }, 'Initialized');
}
setupHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: this.toolRegistry.getAllTools().map(({ name, description, inputSchema }) => {
let schema = { type: 'object', properties: {}, required: [] };
try {
if (inputSchema?.parse) {
const jsonSchema = zodToJsonSchema(inputSchema, { target: 'openApi3' });
schema = {
type: 'object',
properties: jsonSchema.properties || {},
required: jsonSchema.required || []
};
}
} catch (e) {
logger.warn({ tool: name }, 'Schema conversion failed');
}
return { name, description, inputSchema: schema };
})
};
});
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
logger.info({ tool: name }, 'Executing tool');
try {
const result = await this.toolRegistry.executeTool(name, args || {});
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
} catch (error) {
logger.error({ err: error, tool: name }, 'Tool failed');
return {
content: [{
type: 'text',
text: JSON.stringify({
success: false,
error: { message: error.message }
}, null, 2)
}],
isError: true
};
}
});
}
async run() {
await this.initialize();
const transport = new StdioServerTransport();
await this.server.connect(transport);
logger.info('MCP server running');
process.on('SIGINT', () => this.shutdown());
process.on('SIGTERM', () => this.shutdown());
}
async shutdown() {
logger.info('Shutting down');
await this.exchange?.close();
process.exit(0);
}
}