Skip to content
This repository was archived by the owner on Jun 27, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 18 additions & 12 deletions swarms/structs/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
from swarms.tools.mcp_client_call import (
execute_tool_call_simple,
get_mcp_tools_sync,
get_or_create_event_loop,
)
from swarms.schemas.mcp_schemas import (
MCPConnection,
Expand Down Expand Up @@ -2729,21 +2730,26 @@ def mcp_tool_handling(
try:

if exists(self.mcp_url):
# Execute the tool call
tool_response = asyncio.run(
execute_tool_call_simple(
response=response,
server_path=self.mcp_url,
# Execute the tool call using a safe event loop. ``get_or_create_event_loop``
# spawns a temporary loop if one is already running which avoids
# ``RuntimeError: This event loop is already running`` when the
# agent is executed inside another async context.
with get_or_create_event_loop() as loop:
tool_response = loop.run_until_complete(
execute_tool_call_simple(
response=response,
server_path=self.mcp_url,
)
)
)
elif exists(self.mcp_config):
# Execute the tool call
tool_response = asyncio.run(
execute_tool_call_simple(
response=response,
connection=self.mcp_config,
# Execute the tool call using a safe event loop
with get_or_create_event_loop() as loop:
tool_response = loop.run_until_complete(
execute_tool_call_simple(
response=response,
connection=self.mcp_config,
)
)
)
else:
raise AgentMCPConnectionError(
"mcp_url must be either a string URL or MCPConnection object"
Expand Down
23 changes: 18 additions & 5 deletions swarms/tools/mcp_client_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,20 +193,33 @@ async def wrapper(*args, **kwargs):

@contextlib.contextmanager
def get_or_create_event_loop():
"""Context manager to handle event loop creation and cleanup."""
"""Return an event loop safe for sync calls.

If there's already a running event loop (e.g. Jupyter, nested asyncio
execution) we spawn a temporary loop so ``run_until_complete`` can be used
without raising ``RuntimeError: This event loop is already running``.
"""

try:
loop = asyncio.get_event_loop()
if loop.is_running():
# Running loop detected - create a new one for synchronous use
temp_loop = asyncio.new_event_loop()
try:
yield temp_loop
finally:
if not temp_loop.is_closed():
temp_loop.close()
return
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)

try:
yield loop
finally:
# Only close the loop if we created it and it's not the main event loop
if loop != asyncio.get_event_loop() and not loop.is_running():
if not loop.is_closed():
loop.close()
if not loop.is_running() and not loop.is_closed():
loop.close()


def connect_to_mcp_server(connection: MCPConnection = None):
Expand Down
14 changes: 14 additions & 0 deletions tests/tools/test_mcp_event_loop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import asyncio
from unittest.mock import patch
from swarms.tools import mcp_client_call

async def _fake_aget_mcp_tools(*args, **kwargs):
return [{"name": "mock"}]

def test_get_mcp_tools_sync_running_loop():
with patch.object(mcp_client_call, "aget_mcp_tools", side_effect=_fake_aget_mcp_tools):
async def main():
tools = mcp_client_call.get_mcp_tools_sync(server_path="http://dummy")
assert tools == [{"name": "mock"}]
asyncio.run(main())

Loading