mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-22 13:34:17 +02:00
Merge pull request #1138 from strawgate/run-mcp-config
This commit is contained in:
commit
1ea3ee82fe
5 changed files with 124 additions and 8 deletions
|
|
@ -57,10 +57,11 @@ By default, this command runs the server directly in your current Python environ
|
|||
#### Server Specification
|
||||
<VersionBadge version="2.3.5" />
|
||||
|
||||
The server can be specified in three ways:
|
||||
The server can be specified in four ways:
|
||||
1. `server.py` - imports the module and looks for a FastMCP object named `mcp`, `server`, or `app`. Errors if no such object is found.
|
||||
2. `server.py:custom_name` - imports and uses the specified server object
|
||||
3. `http://server-url/path` or `https://server-url/path` - connects to a remote server and creates a proxy
|
||||
4. `mcp.json` - runs servers defined in a standard MCP configuration file
|
||||
|
||||
<Tip>
|
||||
When using `fastmcp run` with a local file, it **ignores** the `if __name__ == "__main__"` block entirely. Instead, it finds your server object and calls its `run()` method directly with the transport options you specify. This means you can use `fastmcp run` to override the transport specified in your code.
|
||||
|
|
@ -114,6 +115,44 @@ fastmcp run server.py --project /path/to/project
|
|||
fastmcp run server.py --with-requirements requirements.txt
|
||||
```
|
||||
|
||||
#### Running MCP Configuration Files
|
||||
|
||||
FastMCP can run servers defined in standard MCP configuration files (typically named `mcp.json`). When you run an mcp.json file, FastMCP creates a proxy server that runs all the servers referenced in the configuration.
|
||||
|
||||
**Example mcp.json:**
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"fetch": {
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"mcp-server-fetch"
|
||||
]
|
||||
},
|
||||
"filesystem": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"@modelcontextprotocol/server-filesystem",
|
||||
"/Users/username/Documents"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Run the configuration:**
|
||||
```bash
|
||||
# Run with default stdio transport
|
||||
fastmcp run mcp.json
|
||||
|
||||
# Run with HTTP transport on custom port
|
||||
fastmcp run mcp.json --transport http --port 8080
|
||||
|
||||
# Run with SSE transport
|
||||
fastmcp run mcp.json --transport sse
|
||||
```
|
||||
|
||||
### `dev`
|
||||
|
||||
Run a MCP server with the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) for testing.
|
||||
|
|
|
|||
|
|
@ -361,16 +361,17 @@ def run(
|
|||
) -> None:
|
||||
"""Run an MCP server or connect to a remote one.
|
||||
|
||||
The server can be specified in three 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
|
||||
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 as a proxy server for the MCP Servers in the MCPConfig file
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
"""FastMCP run command implementation with enhanced type hints."""
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
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")
|
||||
|
|
@ -221,6 +223,17 @@ 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
|
||||
|
||||
with mcp_config_path.open() as src:
|
||||
mcp_config = json.load(src)
|
||||
|
||||
server = FastMCP.as_proxy(mcp_config)
|
||||
return server
|
||||
|
||||
|
||||
def import_server_with_args(
|
||||
file: Path, server_object: str | None = None, server_args: list[str] | None = None
|
||||
) -> Any:
|
||||
|
|
@ -259,7 +272,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
|
||||
|
|
@ -273,6 +286,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)
|
||||
|
|
|
|||
|
|
@ -270,7 +270,7 @@ class MCPConfig(BaseModel):
|
|||
if content := file_path.read_text().strip():
|
||||
return cls.model_validate_json(content)
|
||||
|
||||
return cls(mcpServers={})
|
||||
raise ValueError(f"No MCP servers defined in the config: {file_path}")
|
||||
|
||||
|
||||
class CanonicalMCPConfig(MCPConfig):
|
||||
|
|
|
|||
|
|
@ -1,10 +1,20 @@
|
|||
import inspect
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
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.mcp_config import MCPConfig, StdioMCPServer
|
||||
from fastmcp.server.server import FastMCP
|
||||
|
||||
|
||||
class TestUrlDetection:
|
||||
|
|
@ -80,6 +90,57 @@ class TestFilePathParsing:
|
|||
assert exc_info.value.code == 1
|
||||
|
||||
|
||||
class TestMCPConfig:
|
||||
"""Test MCPConfig functionality."""
|
||||
|
||||
async def test_run_mcp_config(self, tmp_path: 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: Path = tmp_path / "test.py"
|
||||
script_path.write_text(server_script)
|
||||
|
||||
mcp_config_path = tmp_path / "mcp_config.json"
|
||||
|
||||
mcp_config = MCPConfig(
|
||||
mcpServers={
|
||||
"test_server": StdioMCPServer(command="python", args=[str(script_path)])
|
||||
}
|
||||
)
|
||||
mcp_config.write_to_file(mcp_config_path)
|
||||
|
||||
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
|
||||
|
||||
async def test_validate_mcp_config(self, tmp_path: Path):
|
||||
"""Test creating a server from an MCPConfig file."""
|
||||
|
||||
mcp_config_path = tmp_path / "mcp_config.json"
|
||||
|
||||
mcp_config = {"mcpServers": {"test_server": dict(x=1, y=2)}}
|
||||
with mcp_config_path.open("w") as f:
|
||||
json.dump(mcp_config, f)
|
||||
|
||||
with pytest.raises(ValidationError, match="validation errors for MCPConfig"):
|
||||
create_mcp_config_server(mcp_config_path)
|
||||
|
||||
|
||||
class TestServerImport:
|
||||
"""Test server import functionality using real files."""
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue