Support running fastmcp directly with an MCPConfig

This commit is contained in:
William Easton 2025-07-13 15:19:57 -05:00
commit c6501c1060
No known key found for this signature in database
3 changed files with 69 additions and 3 deletions

View file

@ -291,16 +291,17 @@ def run(
) -> None:
"""Run an MCP server or connect to a remote one.
The server can be specified in three ways:
The server can be specified in four ways:
1. Module approach: server.py - runs the module directly, looking for an object named 'mcp', 'server', or 'app'
2. Import approach: server.py:app - imports and runs the specified server object
3. URL approach: http://server-url - connects to a remote server and creates a proxy
4. MCPConfig file: mcp.json - runs the MCPConfig file directly
Server arguments can be passed after -- :
fastmcp run server.py -- --config config.json --debug
Args:
server_spec: Python file, object specification (file:obj), or URL
server_spec: Python file, object specification (file:obj), MCPConfig file, or URL
"""
# TODO: Handle server_args from extra context
server_args = [] # Will need to handle this with Cyclopts context

View file

@ -6,6 +6,7 @@ import sys
from pathlib import Path
from typing import Any, Literal
from fastmcp.server.server import FastMCP
from fastmcp.utilities.logging import get_logger
logger = get_logger("cli.run")
@ -142,6 +143,19 @@ def create_client_server(url: str) -> Any:
sys.exit(1)
def create_mcp_config_server(mcp_config_path: Path) -> FastMCP[None]:
"""Create a FastMCP server from a MCPConfig."""
from fastmcp import FastMCP
from fastmcp.client import Client
from fastmcp.client.transports import MCPConfigTransport
from fastmcp.mcp_config import MCPConfig
mcp_config = MCPConfig.from_file(mcp_config_path)
client = Client[MCPConfigTransport](mcp_config)
server = FastMCP.as_proxy(client)
return server
def import_server_with_args(
file: Path, server_object: str | None = None, server_args: list[str] | None = None
) -> Any:
@ -179,7 +193,7 @@ def run_command(
"""Run a MCP server or connect to a remote one.
Args:
server_spec: Python file, object specification (file:obj), or URL
server_spec: Python file, object specification (file:obj), MCPConfig file, or URL
transport: Transport protocol to use
host: Host to bind to when using http transport
port: Port to bind to when using http transport
@ -192,6 +206,8 @@ def run_command(
# Handle URL case
server = create_client_server(server_spec)
logger.debug(f"Created client proxy server for {server_spec}")
elif server_spec.endswith(".json"):
server = create_mcp_config_server(Path(server_spec))
else:
# Handle file case
file, server_object = parse_file_path(server_spec)

View file

@ -1,10 +1,16 @@
import inspect
import pytest
from fastmcp.cli.run import (
create_mcp_config_server,
import_server,
is_url,
parse_file_path,
)
from fastmcp.client.client import Client
from fastmcp.client.transports import FastMCPTransport
from fastmcp.server.server import FastMCP
class TestUrlDetection:
@ -80,6 +86,49 @@ class TestFilePathParsing:
assert exc_info.value.code == 1
class TestMCPConfig:
"""Test MCPConfig functionality."""
async def test_run_mcp_config(self, tmp_path):
"""Test creating a server from an MCPConfig file."""
server_script = inspect.cleandoc("""
from fastmcp import FastMCP
mcp = FastMCP()
@mcp.tool
def add(a: int, b: int) -> int:
return a + b
if __name__ == '__main__':
mcp.run()
""")
script_path = tmp_path / "test.py"
script_path.write_text(server_script)
mcp_config_path = tmp_path / "mcp_config.json"
mcp_config_str = f"""
{{
"mcpServers": {{
"test_server": {{
"command": "python",
"args": ["{str(script_path)}"]
}}
}}
}}
"""
mcp_config_path.write_text(mcp_config_str)
server: FastMCP[None] = create_mcp_config_server(mcp_config_path)
client = Client[FastMCPTransport](server)
async with client:
tools = await client.list_tools()
assert len(tools) == 1
class TestServerImport:
"""Test server import functionality using real files."""