diff --git a/docs/patterns/cli.mdx b/docs/patterns/cli.mdx
index e394b99f5..69ef01131 100644
--- a/docs/patterns/cli.mdx
+++ b/docs/patterns/cli.mdx
@@ -57,10 +57,11 @@ By default, this command runs the server directly in your current Python environ
#### Server Specification
-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
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.
diff --git a/src/fastmcp/cli/cli.py b/src/fastmcp/cli/cli.py
index 1d0a45973..11730d6b1 100644
--- a/src/fastmcp/cli/cli.py
+++ b/src/fastmcp/cli/cli.py
@@ -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
diff --git a/src/fastmcp/cli/run.py b/src/fastmcp/cli/run.py
index f0464d2e4..a31b41e16 100644
--- a/src/fastmcp/cli/run.py
+++ b/src/fastmcp/cli/run.py
@@ -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)
diff --git a/src/fastmcp/mcp_config.py b/src/fastmcp/mcp_config.py
index 8d5576ec5..e6758e0c7 100644
--- a/src/fastmcp/mcp_config.py
+++ b/src/fastmcp/mcp_config.py
@@ -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):
diff --git a/tests/cli/test_run.py b/tests/cli/test_run.py
index 7a685ce09..6ae06f1eb 100644
--- a/tests/cli/test_run.py
+++ b/tests/cli/test_run.py
@@ -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."""