Fix asyncio error when running FastMCP 1.x servers (#2084)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Chris Guidry 2025-10-14 14:24:51 -04:00 committed by GitHub
commit 7f4119dcf6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 217 additions and 8 deletions

View file

@ -172,7 +172,7 @@ async def run_command(
# handle v1 servers
if isinstance(server, FastMCP1x):
run_v1_server(server, host=host, port=port, transport=transport)
await run_v1_server_async(server, host=host, port=port, transport=transport)
return
kwargs = {}
@ -197,24 +197,29 @@ async def run_command(
sys.exit(1)
def run_v1_server(
async def run_v1_server_async(
server: FastMCP1x,
host: str | None = None,
port: int | None = None,
transport: TransportType | None = None,
) -> None:
from functools import partial
"""Run a FastMCP 1.x server using async methods.
Args:
server: FastMCP 1.x server instance
host: Host to bind to
port: Port to bind to
transport: Transport protocol to use
"""
if host:
server.settings.host = host
if port:
server.settings.port = port
match transport:
case "stdio":
runner = partial(server.run)
await server.run_stdio_async()
case "http" | "streamable-http" | None:
runner = partial(server.run, transport="streamable-http")
await server.run_streamable_http_async()
case "sse":
runner = partial(server.run, transport="sse")
runner()
await server.run_sse_async()

View file

@ -274,6 +274,210 @@ mcp = fastmcp.FastMCP("TestServer")
assert exc_info.value.code == 1
class TestV1ServerAsync:
"""Test FastMCP 1.x server async support."""
async def test_run_v1_server_stdio(self, tmp_path):
"""Test that v1 server uses async stdio method."""
from unittest.mock import AsyncMock, patch
from mcp.server.fastmcp import FastMCP as FastMCP1x
from fastmcp.cli.run import run_command
# Create a v1 FastMCP server file with both sync and async tools
test_file = tmp_path / "v1_server.py"
test_file.write_text("""
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("V1Server")
@mcp.tool()
def sync_echo(text: str) -> str:
'''Sync tool for testing'''
return f"sync: {text}"
@mcp.tool()
async def async_echo(text: str) -> str:
'''Async tool for testing'''
return f"async: {text}"
""")
# Mock the async run method
with patch.object(
FastMCP1x, "run_stdio_async", new_callable=AsyncMock
) as run_mock:
await run_command(str(test_file), transport="stdio")
run_mock.assert_called_once()
async def test_run_v1_server_http(self, tmp_path):
"""Test that v1 server uses async http method."""
from unittest.mock import AsyncMock, patch
from mcp.server.fastmcp import FastMCP as FastMCP1x
from fastmcp.cli.run import run_command
# Create a v1 FastMCP server file with both sync and async tools
test_file = tmp_path / "v1_server.py"
test_file.write_text("""
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("V1Server")
@mcp.tool()
def sync_echo(text: str) -> str:
'''Sync tool for testing'''
return f"sync: {text}"
@mcp.tool()
async def async_echo(text: str) -> str:
'''Async tool for testing'''
return f"async: {text}"
""")
# Mock the async run method
with patch.object(
FastMCP1x, "run_streamable_http_async", new_callable=AsyncMock
) as run_mock:
await run_command(str(test_file), transport="http")
run_mock.assert_called_once()
async def test_run_v1_server_streamable_http(self, tmp_path):
"""Test that v1 server uses async streamable-http method."""
from unittest.mock import AsyncMock, patch
from mcp.server.fastmcp import FastMCP as FastMCP1x
from fastmcp.cli.run import run_command
# Create a v1 FastMCP server file with both sync and async tools
test_file = tmp_path / "v1_server.py"
test_file.write_text("""
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("V1Server")
@mcp.tool()
def sync_echo(text: str) -> str:
'''Sync tool for testing'''
return f"sync: {text}"
@mcp.tool()
async def async_echo(text: str) -> str:
'''Async tool for testing'''
return f"async: {text}"
""")
# Mock the async run method
with patch.object(
FastMCP1x, "run_streamable_http_async", new_callable=AsyncMock
) as run_mock:
await run_command(str(test_file), transport="streamable-http")
run_mock.assert_called_once()
async def test_run_v1_server_sse(self, tmp_path):
"""Test that v1 server uses async sse method."""
from unittest.mock import AsyncMock, patch
from mcp.server.fastmcp import FastMCP as FastMCP1x
from fastmcp.cli.run import run_command
# Create a v1 FastMCP server file with both sync and async tools
test_file = tmp_path / "v1_server.py"
test_file.write_text("""
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("V1Server")
@mcp.tool()
def sync_echo(text: str) -> str:
'''Sync tool for testing'''
return f"sync: {text}"
@mcp.tool()
async def async_echo(text: str) -> str:
'''Async tool for testing'''
return f"async: {text}"
""")
# Mock the async run method
with patch.object(
FastMCP1x, "run_sse_async", new_callable=AsyncMock
) as run_mock:
await run_command(str(test_file), transport="sse")
run_mock.assert_called_once()
async def test_run_v1_server_default_transport(self, tmp_path):
"""Test that v1 server uses streamable-http by default."""
from unittest.mock import AsyncMock, patch
from mcp.server.fastmcp import FastMCP as FastMCP1x
from fastmcp.cli.run import run_command
# Create a v1 FastMCP server file with both sync and async tools
test_file = tmp_path / "v1_server.py"
test_file.write_text("""
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("V1Server")
@mcp.tool()
def sync_echo(text: str) -> str:
'''Sync tool for testing'''
return f"sync: {text}"
@mcp.tool()
async def async_echo(text: str) -> str:
'''Async tool for testing'''
return f"async: {text}"
""")
# Mock the async run method
with patch.object(
FastMCP1x, "run_streamable_http_async", new_callable=AsyncMock
) as run_mock:
await run_command(str(test_file))
run_mock.assert_called_once()
async def test_run_v1_server_with_host_port(self, tmp_path):
"""Test that v1 server receives host/port settings."""
from unittest.mock import AsyncMock, patch
from mcp.server.fastmcp import FastMCP as FastMCP1x
from fastmcp.cli.run import run_command
# Create a v1 FastMCP server file with both sync and async tools
test_file = tmp_path / "v1_server.py"
test_file.write_text("""
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("V1Server")
@mcp.tool()
def sync_echo(text: str) -> str:
'''Sync tool for testing'''
return f"sync: {text}"
@mcp.tool()
async def async_echo(text: str) -> str:
'''Async tool for testing'''
return f"async: {text}"
""")
# Mock the async run method
with patch.object(
FastMCP1x, "run_streamable_http_async", new_callable=AsyncMock
) as run_mock:
await run_command(
str(test_file), transport="http", host="0.0.0.0", port=9000
)
run_mock.assert_called_once()
class TestSkipSource:
"""Test the --skip-source functionality."""