Skip to content

Commit a69abc0

Browse files
committed
better market data
1 parent dc2095c commit a69abc0

File tree

4 files changed

+202
-1
lines changed

4 files changed

+202
-1
lines changed

mcp-inspector.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"mcpServers": {
3+
"interactive-brokers": {
4+
"type": "sse",
5+
"url": "http://localhost:8123/mcp"
6+
}
7+
}
8+
}

src/ib-client.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,8 +337,11 @@ export class IBClient {
337337
const conid = contract.conid;
338338

339339
// Get market data snapshot
340+
// Using corrected field IDs based on IB Client Portal API documentation:
341+
// 31=Last Price, 70=Day High, 71=Day Low, 82=Change, 83=Change%,
342+
// 84=Bid, 85=Ask Size, 86=Ask, 87=Volume, 88=Bid Size
340343
const response = await this.client.get(
341-
`/iserver/marketdata/snapshot?conids=${conid}&fields=31,84,86,87,88,85,70,71,72,73,74,75,76,77,78`
344+
`/iserver/marketdata/snapshot?conids=${conid}&fields=31,70,71,82,83,84,85,86,87,88`
342345
);
343346

344347
return {

src/index-http.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import express from "express";
2+
import cors from "cors";
3+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
4+
import { createIBMCPServer } from "./server.js";
5+
6+
try {
7+
const PORT = process.env.PORT || process.env.MCP_PORT || 8000;
8+
9+
console.log(`🚀 Starting Interactive Brokers MCP Server in HTTP/SSE mode on port ${PORT}`);
10+
11+
const app = express();
12+
app.use(cors());
13+
app.use(express.json());
14+
15+
// Store active SSE transports
16+
const transports: { [sessionId: string]: SSEServerTransport } = {};
17+
18+
// Health check endpoint
19+
app.get('/health', (req, res) => {
20+
res.json({
21+
status: 'ok',
22+
service: 'interactive-brokers-mcp',
23+
transport: 'http/sse'
24+
});
25+
});
26+
27+
// SSE endpoint - establishes the server-to-client event stream
28+
app.get('/mcp', async (req, res) => {
29+
console.log('📡 New SSE connection request received');
30+
31+
try {
32+
// Create SSE transport
33+
const transport = new SSEServerTransport('/mcp/messages', res);
34+
const sessionId = transport.sessionId;
35+
transports[sessionId] = transport;
36+
37+
console.log(`✅ SSE transport created with session ID: ${sessionId}`);
38+
39+
// Create a new MCP server instance for this connection
40+
const server = createIBMCPServer({ config: {} });
41+
42+
// Handle transport closure
43+
res.on("close", () => {
44+
console.log(`🔌 SSE connection closed for session: ${sessionId}`);
45+
delete transports[sessionId];
46+
});
47+
48+
// Connect the server to the transport
49+
await server.connect(transport);
50+
console.log(`🔗 MCP server connected to SSE transport: ${sessionId}`);
51+
} catch (error) {
52+
console.error('❌ Failed to connect MCP server to SSE transport:', error);
53+
if (!res.headersSent) {
54+
res.status(500).send('Internal server error');
55+
}
56+
}
57+
});
58+
59+
// Message endpoint - receives client-to-server messages
60+
app.post('/mcp/messages', async (req, res) => {
61+
const sessionId = req.query.sessionId as string;
62+
console.log(`📨 Received message for session: ${sessionId}`);
63+
64+
const transport = transports[sessionId];
65+
if (transport) {
66+
try {
67+
await transport.handlePostMessage(req, res, req.body);
68+
} catch (error) {
69+
console.error('❌ Error handling POST message:', error);
70+
if (!res.headersSent) {
71+
res.status(500).json({ error: 'Internal server error' });
72+
}
73+
}
74+
} else {
75+
console.warn(`⚠️ No transport found for session ID: ${sessionId}`);
76+
res.status(400).json({ error: 'No transport found for sessionId' });
77+
}
78+
});
79+
80+
// Start the server
81+
const server = app.listen(PORT, () => {
82+
console.log(`✅ HTTP/SSE server listening on http://localhost:${PORT}`);
83+
console.log(`📡 SSE endpoint: http://localhost:${PORT}/mcp`);
84+
console.log(`📨 Messages endpoint: http://localhost:${PORT}/mcp/messages`);
85+
console.log(`🏥 Health check: http://localhost:${PORT}/health`);
86+
});
87+
88+
// Handle server errors
89+
server.on('error', (error: any) => {
90+
if (error.code === 'EADDRINUSE') {
91+
console.error(`❌ Port ${PORT} is already in use`);
92+
process.exit(1);
93+
} else {
94+
console.error('❌ Server error:', error);
95+
process.exit(1);
96+
}
97+
});
98+
99+
} catch (error) {
100+
console.error('❌ Fatal error starting HTTP/SSE server:', error);
101+
process.exit(1);
102+
}

src/server.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2+
import { z } from "zod";
3+
import { IBClient } from "./ib-client.js";
4+
import { IBGatewayManager } from "./gateway-manager.js";
5+
import { config } from "./config.js";
6+
import { registerTools } from "./tools.js";
7+
import { Logger } from "./logger.js";
8+
9+
export const configSchema = z.object({
10+
// Authentication configuration
11+
IB_USERNAME: z.string().optional(),
12+
IB_PASSWORD_AUTH: z.string().optional(),
13+
IB_AUTH_TIMEOUT: z.number().optional(),
14+
IB_HEADLESS_MODE: z.boolean().optional(),
15+
16+
// Paper trading configuration
17+
IB_PAPER_TRADING: z.boolean().optional(),
18+
});
19+
20+
// Global gateway manager instance
21+
let gatewayManager: IBGatewayManager | null = null;
22+
23+
// Initialize and start IB Gateway (fast startup for MCP plugin compatibility)
24+
async function initializeGateway(ibClient?: IBClient) {
25+
if (!gatewayManager) {
26+
gatewayManager = new IBGatewayManager();
27+
28+
try {
29+
Logger.info('⚡ Quick Gateway initialization for MCP plugin...');
30+
await gatewayManager.quickStartGateway();
31+
Logger.info('✅ Gateway initialization completed (background startup if needed)');
32+
33+
// Update client port if provided
34+
if (ibClient) {
35+
ibClient.updatePort(gatewayManager.getCurrentPort());
36+
}
37+
} catch (error) {
38+
Logger.error('❌ Failed to initialize Gateway:', error);
39+
// Don't throw error during quick startup - tools will handle it
40+
Logger.warn('⚠️ Gateway initialization failed, tools will attempt connection when called');
41+
}
42+
}
43+
return gatewayManager;
44+
}
45+
46+
export function createIBMCPServer({ config: userConfig }: { config: z.infer<typeof configSchema> }) {
47+
// Merge user config with environment config
48+
const mergedConfig = {
49+
...config,
50+
...userConfig
51+
};
52+
53+
// Log the merged config for debugging (but redact sensitive info)
54+
const logConfig = { ...mergedConfig };
55+
if (logConfig.IB_PASSWORD_AUTH) logConfig.IB_PASSWORD_AUTH = '[REDACTED]';
56+
if (logConfig.IB_PASSWORD) logConfig.IB_PASSWORD = '[REDACTED]';
57+
Logger.info(`🔍 Final merged config: ${JSON.stringify(logConfig, null, 2)}`);
58+
59+
// Create IB Client with default port initially - this will be updated once gateway starts
60+
const ibClient = new IBClient({
61+
host: mergedConfig.IB_GATEWAY_HOST,
62+
port: mergedConfig.IB_GATEWAY_PORT,
63+
});
64+
65+
// Initialize gateway on first server creation and update client port
66+
initializeGateway(ibClient).catch(error => {
67+
Logger.error('Failed to initialize gateway:', error);
68+
});
69+
70+
Logger.info('Gateway starting...');
71+
72+
// Create MCP server
73+
const server = new McpServer({
74+
name: "interactive-brokers-mcp",
75+
version: "1.0.0"
76+
});
77+
78+
// Register all tools with merged config
79+
registerTools(server, ibClient, gatewayManager || undefined, mergedConfig);
80+
81+
Logger.info('Tools registered');
82+
83+
return server;
84+
}
85+
86+
export { gatewayManager };
87+
88+

0 commit comments

Comments
 (0)