Skip to content

Issue with ib_insync and asyncio event loop in fastmcp environment #1751

@rossdem699

Description

@rossdem699

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working. Reports of errors, unexpected behavior, or broken functionality.serverRelated to FastMCP server implementation or server-side functionality.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions