-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
Description
Hello,
I am experiencing an issue when trying to use the ib_insync library within a fastmcp tool. I am consistently getting a "This event loop is already running" error when I try to connect to Interactive Brokers using ib_insync.
I have spent a significant amount of time trying to debug this issue, and I have tried numerous workarounds, including:
- Using both synchronous and asynchronous versions of the ib_insync connection code.
- Using ib_insync.util.run() to manage the event loop.
- Using ib_insync.util.startLoop() which is designed for environments with a running event loop.
- Using nest_asyncio.apply() to patch the asyncio event loop.
- Using different clientIds for each connection attempt to avoid conflicts.
- Analyzing multiple existing IBKR MCP server implementations on GitHub to find a working solution.
- Refactoring the code to use a professional-grade client from a known-working implementation.
Unfortunately, none of these approaches have solved the problem. The "event loop is already running" error persists in all cases.
This leads me to believe that there is a fundamental incompatibility between the fastmcp environment's asyncio event loop and the ib_insync library. It seems that ib_insync is unable to
integrate with the event loop that is already running in the fastmcp tool's environment.
I would be grateful if you could look into this issue. It seems that it is not possible to use ib_insync within a fastmcp tool at the moment.
Thank you for your time and consideration. FYI - i have been using gemini_cli to try and troubleshoot this issue and it has given up on me!!!
Example Code
1. `stock_tool.py`
This file defines the get_account_details tool that is being called in the fastmcp environment.
1 from fastmcp import FastMCP
2 from ibkr_service import ibkr_service
3
4 mcp = FastMCP(name="Stock and IBKR Tool")
5
6 @mcp.tool()
7 async def get_account_details(account: str = None) -> list:
8 """
9 Connects to IBKR TWS and retrieves account summary details.
10 An optional account code can be provided to filter for a specific account.
11 """
12 try:
13 # The ibkr_service is already a singleton, so we can just call its methods
14 details = await ibkr_service.fetch_portfolio_details()
15 # The user wants the account summary, so we extract it from the details
16 return details.get('account_summary', [])
17 except Exception as e:
18 return [{"error": f"An error occurred: {e}"}]
2. `ibkr_service.py`
This file contains the logic for connecting to Interactive Brokers and demonstrates the use of nest_asyncio.
1 import os
2 from typing import List, Dict
3 from fastapi import HTTPException
4 from ib_async import IB
5 from dotenv import load_dotenv
6 import asyncio
7 import nest_asyncio
8 try:
9 nest_asyncio.apply()
10 except ValueError as e:
11 print(f"nest_asyncio.apply() skipped: {e}")
12
13 load_dotenv(override=True)
14
15 class IBKRService:
16 def __init__(self):
17 self.ib = IB()
18 self.host = os.getenv("IBKR_HOST", "127.0.0.1")
19 raw_port = os.getenv("IBKR_PORT", "7496").split()[0]
20 self.port = int(raw_port)
21 self.connected = False
22
23 async def connect(self):
24 """Connect to IBKR TWS or Gateway"""
25 if self.connected:
26 return
27
28 try:
29 print(f"Connecting to IBKR at {self.host}:{self.port}")
30 await self.ib.connectAsync(
31 host=self.host,
32 port=self.port,
33 clientId=0,
34 readonly=True,
35 timeout=20
36 )
37 self.connected = True
38 print(f"Connected to IBKR at {self.host}:{self.port}")
39 except Exception as e:
40 self.connected = False
41 raise HTTPException(status_code=500, detail=f"Failed to connect to IBKR: {str(e)}")
42
43 def disconnect(self):
44 """Disconnect from IBKR"""
45 if self.connected:
46 self.ib.disconnect()
47 self.connected = False
48 print("Disconnected from IBKR")
49
50 async def fetch_portfolio_details(self) -> Dict:
51 """Fetch portfolio details from IBKR"""
52 try:
53 if not self.connected:
54 await self.connect()
55
56 print("=>>>>>> Fetching portfolio details")
57 # Get portfolio positions
58 positions = await asyncio.to_thread(self.ib.positions)
59 print("=>>>>>> Positions fetched: ", positions)
60
61 # Format portfolio data
62 portfolio_data = []
63 for pos in positions:
64 position_data = {
65 'symbol': pos.contract.symbol,
66 'secType': pos.contract.secType,
67 'exchange': pos.contract.exchange,
68 'currency': pos.contract.currency,
69 'position': pos.position,
70 'avgCost': pos.avgCost
71 }
72 portfolio_data.append(position_data)
73
74 print("=>>>>>> Portfolio data: ", portfolio_data)
75 # Get account summary
76 print("=>>>>>> Fetching account summary")
77 account = await self.ib.accountSummaryAsync()
78
79 return {
80 'positions': portfolio_data,
81 'account_summary': account
82 }
83
84 except Exception as e:
85 raise HTTPException(
86 status_code=500,
87 detail=f"Failed to fetch portfolio: {str(e)}"
88 )
89 finally:
90 self.disconnect()
91
92 # Create a singleton instance
93 ibkr_service = IBKRService()
Explanation to Include
When you submit the issue, you can explain that:
* The get_account_details tool in stock_tool.py is called from the fastmcp environment.
* This tool then calls the fetch_portfolio_details method in ibkr_service.py.
* The ibkr_service.py file uses nest_asyncio.apply() in an attempt to solve the event loop issue, but the error still occurs.
Version Information
✦ Here are the versions you requested:
* Python Version: 3.13.5
* `fastmcp` Version: 2.12.0
* `ib_insync` Version: 0.9.86