Repository: jlowin/fastmcp (python)

Issue:
fastmcp cli documentation references unsupported arguments host and port
https://github.com/jlowin/fastmcp/blob/7c6435b924bf718533d613c32be80c25837ac9b3/docs/servers/fastmcp.mdx?plain=1#L184

`--host` and `--port` are not yet incorporated into the cli

https://github.com/jlowin/fastmcp/blob/7c6435b924bf718533d613c32be80c25837ac9b3/src/fastmcp/cli/cli.py#L292

Pull request description:
Add transport kwargs for mcp.run() and fastmcp run
supports e.g. `mcp.run(transport='sse', host=<>, port=<>)` and also `fastmcp run file.py --host <> --port <>`

Closes #158 and supersedes #148 

Patch:
diff --git a/docs/servers/fastmcp.mdx b/docs/servers/fastmcp.mdx
index cf0fef7..c86f22e 100644
--- a/docs/servers/fastmcp.mdx
+++ b/docs/servers/fastmcp.mdx
@@ -111,7 +111,12 @@ def greet(name: str) -> str:
 
 if __name__ == "__main__":
     # This code only runs when the file is executed directly
+    
+    # Basic run with default settings (stdio transport)
     mcp.run()
+    
+    # Or with specific transport and parameters
+    # mcp.run(transport="sse", host="127.0.0.1", port=9000)
 ```
 
 This pattern is important because:
@@ -156,18 +161,47 @@ With SSE:
 - The server stays running until explicitly terminated
 - This is ideal for remote access to services
 
-You can configure the host, port, and log level when running the server:
+You can configure transport parameters directly when running the server:
 
 ```python
-# Configure with parameters
-mcp.run(transport="sse", host="127.0.0.1", port=8888)
+# Configure with specific parameters
+mcp.run(
+    transport="sse", 
+    host="127.0.0.1",  # Override default host
+    port=8888,         # Override default port
+    log_level="debug"  # Set logging level
+)
 
-# Or run asynchronously with the same parameters
+# You can also run asynchronously with the same parameters
 import asyncio
-asyncio.run(mcp.run_sse_async(host="127.0.0.1", port=8888, log_level="debug"))
+asyncio.run(
+    mcp.run_sse_async(
+        host="127.0.0.1", 
+        port=8888, 
+        log_level="debug"
+    )
+)
 ```
 
-These parameters override any settings defined when creating the FastMCP instance.
+Transport parameters passed to `run()` or `run_sse_async()` override any settings defined when creating the FastMCP instance. The most common parameters for SSE transport are:
+
+- `host`: Host to bind to (default: "0.0.0.0")
+- `port`: Port to bind to (default: 8000)
+- `log_level`: Logging level (default: "INFO")
+
+#### Advanced Transport Configuration
+
+Under the hood, FastMCP's `run()` method accepts arbitrary keyword arguments (`**transport_kwargs`) that are passed to the transport-specific run methods:
+
+```python
+# For SSE transport, kwargs are passed to run_sse_async()
+mcp.run(transport="sse", **transport_kwargs)
+
+# For stdio transport, kwargs are passed to run_stdio_async()
+mcp.run(transport="stdio", **transport_kwargs)
+```
+
+This means that any future transport-specific options will be automatically available through the same interface without requiring changes to your code.
 
 ### Using the FastMCP CLI
 
@@ -180,8 +214,11 @@ fastmcp run my_server.py:mcp
 # Explicitly specify a transport
 fastmcp run my_server.py:mcp --transport sse
 
-# Configure SSE transport
+# Configure SSE transport with host and port
 fastmcp run my_server.py:mcp --transport sse --host 127.0.0.1 --port 8888
+
+# With log level
+fastmcp run my_server.py:mcp --transport sse --log-level DEBUG
 ```
 
 The CLI can dynamically find and run FastMCP server objects in your files, but including the `if __name__ == "__main__":` block ensures compatibility with all clients.
diff --git a/src/fastmcp/cli/cli.py b/src/fastmcp/cli/cli.py
index 4e2a618..4ee9fdb 100644
--- a/src/fastmcp/cli/cli.py
+++ b/src/fastmcp/cli/cli.py
@@ -297,6 +297,29 @@ def run(
             help="Transport protocol to use (stdio or sse)",
         ),
     ] = None,
+    host: Annotated[
+        str | None,
+        typer.Option(
+            "--host",
+            help="Host to bind to when using sse transport (default: 0.0.0.0)",
+        ),
+    ] = None,
+    port: Annotated[
+        int | None,
+        typer.Option(
+            "--port",
+            "-p",
+            help="Port to bind to when using sse transport (default: 8000)",
+        ),
+    ] = None,
+    log_level: Annotated[
+        str | None,
+        typer.Option(
+            "--log-level",
+            "-l",
+            help="Log level for sse transport (DEBUG, INFO, WARNING, ERROR, CRITICAL)",
+        ),
+    ] = None,
 ) -> None:
     """Run a MCP server.
 
@@ -316,6 +339,9 @@ def run(
             "file": str(file),
             "server_object": server_object,
             "transport": transport,
+            "host": host,
+            "port": port,
+            "log_level": log_level,
         },
     )
 
@@ -329,6 +355,12 @@ def run(
         kwargs = {}
         if transport:
             kwargs["transport"] = transport
+        if host:
+            kwargs["host"] = host
+        if port:
+            kwargs["port"] = port
+        if log_level:
+            kwargs["log_level"] = log_level
 
         server.run(**kwargs)
 
diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py
index a6e7119..4ac62bc 100644
--- a/src/fastmcp/server/server.py
+++ b/src/fastmcp/server/server.py
@@ -146,7 +146,9 @@ class FastMCP(Generic[LifespanResultT]):
     def instructions(self) -> str | None:
         return self._mcp_server.instructions
 
-    async def run_async(self, transport: Literal["stdio", "sse"] | None = None) -> None:
+    async def run_async(
+        self, transport: Literal["stdio", "sse"] | None = None, **transport_kwargs: Any
+    ) -> None:
         """Run the FastMCP server asynchronously.
 
         Args:
@@ -158,18 +160,20 @@ class FastMCP(Generic[LifespanResultT]):
             raise ValueError(f"Unknown transport: {transport}")
 
         if transport == "stdio":
-            await self.run_stdio_async()
+            await self.run_stdio_async(**transport_kwargs)
         else:  # transport == "sse"
-            await self.run_sse_async()
+            await self.run_sse_async(**transport_kwargs)
 
-    def run(self, transport: Literal["stdio", "sse"] | None = None) -> None:
+    def run(
+        self, transport: Literal["stdio", "sse"] | None = None, **transport_kwargs: Any
+    ) -> None:
         """Run the FastMCP server. Note this is a synchronous function.
 
         Args:
             transport: Transport protocol to use ("stdio" or "sse")
         """
         logger.info(f'Starting server "{self.name}"...')
-        anyio.run(self.run_async, transport)
+        anyio.run(self.run_async, transport, **transport_kwargs)
 
     def _setup_handlers(self) -> None:
         """Set up core MCP protocol handlers."""
diff --git a/uv.lock b/uv.lock
index d95c731..7f89175 100644
--- a/uv.lock
+++ b/uv.lock
@@ -254,7 +254,7 @@ wheels = [
 
 [[package]]
 name = "fastmcp"
-version = "2.1.2.dev2+c0de75e"
+version = "2.1.2.dev9+55f3666"
 source = { editable = "." }
 dependencies = [
     { name = "dotenv" },


Test patch:
diff --git a/tests/cli/test_run.py b/tests/cli/test_run.py
new file mode 100644
index 0000000..58bd3c0
--- /dev/null
+++ b/tests/cli/test_run.py
@@ -0,0 +1,77 @@
+from pathlib import Path
+from unittest.mock import Mock, patch
+
+import pytest
+from typer.testing import CliRunner
+
+from fastmcp import FastMCP
+from fastmcp.cli.cli import app
+
+
+@pytest.fixture
+def server_file(tmp_path):
+    """Create a simple server file for testing"""
+    server_path = tmp_path / "test_server.py"
+    server_path.write_text(
+        """
+from fastmcp import FastMCP
+
+mcp = FastMCP(name="TestServer")
+
+@mcp.tool()
+def hello(name: str) -> str:
+    return f"Hello, {name}!"
+
+if __name__ == "__main__":
+    mcp.run()
+"""
+    )
+    return server_path
+
+
+def test_cli_run_transport_kwargs():
+    """Test that transport_kwargs are correctly passed from CLI to server.run()"""
+    runner = CliRunner()
+
+    # Need to mock both the file parsing and the server import
+    with (
+        patch("fastmcp.cli.cli._parse_file_path") as mock_parse_file_path,
+        patch("fastmcp.cli.cli._import_server") as mock_import_server,
+    ):
+        # Make _parse_file_path return a fake path and server object
+        mock_parse_file_path.return_value = (Path("fake_server.py"), "mcp")
+
+        # Create a mock server with a mock run method
+        mock_server = FastMCP(name="MockServer")
+        mock_server.run = Mock()
+
+        # Make _import_server return our mock server
+        mock_import_server.return_value = mock_server
+
+        # Run the CLI command with transport_kwargs
+        result = runner.invoke(
+            app,
+            [
+                "run",
+                "fake_server.py",
+                "--transport",
+                "sse",
+                "--host",
+                "127.0.0.1",
+                "--port",
+                "9000",
+                "--log-level",
+                "DEBUG",
+            ],
+        )
+
+        # Check that the run method was called with the correct kwargs
+        mock_server.run.assert_called_once_with(
+            transport="sse",
+            host="127.0.0.1",
+            port=9000,
+            log_level="DEBUG",
+        )
+
+        # Check CLI command succeeded
+        assert result.exit_code == 0