diff --git a/docs/deployment/authentication.mdx b/docs/deployment/authentication.mdx
new file mode 100644
index 000000000..25789fdc0
--- /dev/null
+++ b/docs/deployment/authentication.mdx
@@ -0,0 +1,15 @@
+---
+title: Authentication
+sidebarTitle: Authentication
+description: Secure your FastMCP server with authentication.
+icon: lock
+---
+import { VersionBadge } from '/snippets/version-badge.mdx'
+
+
+
+This document will cover how to implement authentication for your FastMCP servers.
+
+FastMCP leverages the OAuth 2.0 support provided by the underlying Model Context Protocol (MCP) SDK.
+
+For now, refer to the [MCP Server Authentication documentation](/servers/fastmcp#authentication) for initial details and the [official MCP SDK documentation](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization) for more.
diff --git a/docs/deployment/running-server.mdx b/docs/deployment/running-server.mdx
new file mode 100644
index 000000000..fa96b3403
--- /dev/null
+++ b/docs/deployment/running-server.mdx
@@ -0,0 +1,192 @@
+---
+title: Running Your FastMCP Server
+sidebarTitle: Running the Server
+description: Learn how to run and deploy your FastMCP server using various transport protocols like STDIO, Streamable HTTP, and SSE.
+icon: circle-play
+---
+import { VersionBadge } from '/snippets/version-badge.mdx'
+
+
+FastMCP servers can be run in different ways depending on your application's needs, from local command-line tools to persistent web services. This guide covers the primary methods for running your server, focusing on the available transport protocols: STDIO, Streamable HTTP, and SSE.
+
+## The `run()` Method
+
+The main way to run a FastMCP server from a Python script is by calling the `run()` method on a `FastMCP` instance.
+
+
+For maximum compatibility, it's best practice to place the `run()` call within an `if __name__ == "__main__":` block. This ensures the server starts only when the script is executed directly, not when imported as a module.
+
+
+```python {9-10} my_server.py
+from fastmcp import FastMCP
+
+mcp = FastMCP(name="MyServer")
+
+@mcp.tool()
+def hello(name: str) -> str:
+ return f"Hello, {name}!"
+
+if __name__ == "__main__":
+ mcp.run()
+```
+You can now run this MCP server by executing `python my_server.py`.
+
+MCP servers can be run with a variety of different transport options, depending on your application's requirements. The `run()` method can take a `transport` argument and other transport-specific keyword arguments to configure how the server operates.
+
+## Transport Options
+
+Below is a comparison of available transport options to help you choose the right one for your needs:
+
+| Transport | Use Cases | Recommendation |
+| --------- | --------- | -------------- |
+| **STDIO** | Local tools, command-line scripts, and integrations with clients like Claude Desktop | Best for local tools and when clients manage server processes |
+| **Streamable HTTP** | Web-based deployments, microservices, exposing MCP over a network | Recommended choice for new web-based deployments |
+| **SSE** | Existing web-based deployments that rely on SSE | Suitable for compatibility with SSE clients; prefer Streamable HTTP for new projects |
+
+### STDIO
+
+The STDIO transport is the default and most widely compatible option for local MCP server execution. It is ideal for local tools, command-line integrations, and clients like Claude Desktop. However, it has the disadvantage of having to run the MCP code locally, which can introduce security concerns with third-party servers.
+
+STDIO is the default transport, so you don't need to specify it when calling `run()`. However, you can specify it explicitly to make your intent clear:
+
+```python {6}
+from fastmcp import FastMCP
+
+mcp = FastMCP()
+
+if __name__ == "__main__":
+ mcp.run(transport="stdio")
+```
+
+When using Stdio transport, you will typically *not* run the server yourself as a separate process. Rather, your *clients* will spin up a new server process for each session. As such, no additional configuration is required.
+
+### Streamable HTTP
+
+
+
+Streamable HTTP is a modern, efficient transport for exposing your MCP server via HTTP. It is generally recommended over SSE for new web-based deployments.
+
+To run a server using Streamable HTTP, you can use the `run()` method with the `transport` argument set to `"streamable-http"`. This will start a Uvicorn server on the default host (`127.0.0.1`), port (`8000`), and path (`/mcp/`).
+
+```python {6} server.py
+from fastmcp import FastMCP
+
+mcp = FastMCP()
+
+if __name__ == "__main__":
+ mcp.run(transport="streamable-http")
+```
+```python {5} client.py
+import asyncio
+from fastmcp import Client
+
+async def example():
+ async with Client("http://127.0.0.1:8000/mcp/") as client:
+ await client.ping()
+
+if __name__ == "__main__":
+ asyncio.run(example())
+```
+
+
+To customize the host, port, path, or log level, provide appropriate keyword arguments to the `run()` method.
+
+
+```python {8-11} server.py
+from fastmcp import FastMCP
+
+mcp = FastMCP()
+
+if __name__ == "__main__":
+ mcp.run(
+ transport="streamable-http",
+ host="127.0.0.1",
+ port=4200,
+ path="/my-custom-path/",
+ log_level="debug",
+ )
+```
+```python {5} client.py
+import asyncio
+from fastmcp import Client
+
+async def example():
+ async with Client("http://127.0.0.1:4200/my-custom-path/") as client:
+ await client.ping()
+
+if __name__ == "__main__":
+ asyncio.run(example())
+```
+
+
+
+### SSE
+
+Server-Sent Events (SSE) is an HTTP-based protocol for server-to-client streaming. While FastMCP supports SSE, Streamable HTTP is preferred for new projects.
+
+To run a server using SSE, you can use the `run()` method with the `transport` argument set to `"sse"`. This will start a Uvicorn server on the default host (`127.0.0.1`), port (`8000`), and with default SSE path (`/sse/`) and message path (`/messages/`).
+
+
+```python {6} server.py
+from fastmcp import FastMCP
+
+mcp = FastMCP()
+
+if __name__ == "__main__":
+ mcp.run(transport="sse")
+```
+```python {3,7} client.py
+import asyncio
+from fastmcp import Client
+from fastmcp.client.transports import SSETransport
+
+async def example():
+ async with Client(
+ transport=SSETransport("http://127.0.0.1:8000/sse/")
+ ) as client:
+ await client.ping()
+
+if __name__ == "__main__":
+ asyncio.run(example())
+```
+
+
+
+Notice that the client in the above example uses an explicit `SSETransport` to connect to the server. FastMCP will attempt to infer the appropriate transport from the provided configuration, but HTTP URLs are assumed to be Streamable HTTP (as of FastMCP 2.3.0).
+
+
+To customize the host, port, or log level, provide appropriate keyword arguments to the `run()` method. You can also adjust the SSE path (which clients should connect to) and the message POST endpoint (which clients use to send subsequent messages).
+
+
+```python {8-12} server.py
+from fastmcp import FastMCP
+
+mcp = FastMCP()
+
+if __name__ == "__main__":
+ mcp.run(
+ transport="sse",
+ host="127.0.0.1",
+ port=4200,
+ log_level="debug",
+ path="/my-custom-sse-path/",
+ message_path="/my-custom-message-path/",
+ )
+```
+```python {7} client.py
+import asyncio
+from fastmcp import Client
+from fastmcp.client.transports import SSETransport
+
+async def example():
+ async with Client(
+ transport=SSETransport("http://127.0.0.1:4200/my-custom-sse-path/")
+ ) as client:
+ await client.ping()
+
+if __name__ == "__main__":
+ asyncio.run(example())
+```
+
+
+Your client only needs to know the host, port, and "main" path; the message path will be transmitted to it as part of the connection handshake.
\ No newline at end of file
diff --git a/docs/docs.json b/docs/docs.json
index e1ee9d4f3..c35620263 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -49,7 +49,16 @@
"servers/tools",
"servers/resources",
"servers/prompts",
- "servers/context"
+ "servers/context",
+ "patterns/proxy",
+ "patterns/composition"
+ ]
+ },
+ {
+ "group": "Deployment",
+ "pages": [
+ "deployment/running-server",
+ "deployment/authentication"
]
},
{
@@ -62,8 +71,6 @@
{
"group": "Patterns",
"pages": [
- "patterns/proxy",
- "patterns/composition",
"patterns/decorating-methods",
"patterns/http-requests",
"patterns/openapi",
diff --git a/docs/getting-started/quickstart.mdx b/docs/getting-started/quickstart.mdx
index 8200f6919..e9fe33a0d 100644
--- a/docs/getting-started/quickstart.mdx
+++ b/docs/getting-started/quickstart.mdx
@@ -1,6 +1,6 @@
---
title: Quickstart
-icon: rocket
+icon: rocket-launch
---
Welcome! This guide will help you quickly set up FastMCP and run your first MCP server.
diff --git a/docs/patterns/proxy.mdx b/docs/patterns/proxy.mdx
index 553f792f5..ee8d9a47a 100644
--- a/docs/patterns/proxy.mdx
+++ b/docs/patterns/proxy.mdx
@@ -1,6 +1,6 @@
---
-title: Proxying Servers
-sidebarTitle: Proxying
+title: Proxy Servers
+sidebarTitle: Proxy Servers
description: Use FastMCP to act as an intermediary or change transport for other MCP servers.
icon: arrows-retweet
---
diff --git a/docs/servers/fastmcp.mdx b/docs/servers/fastmcp.mdx
index 87c322f9d..7b6995228 100644
--- a/docs/servers/fastmcp.mdx
+++ b/docs/servers/fastmcp.mdx
@@ -1,6 +1,6 @@
---
title: The FastMCP Server
-sidebarTitle: FastMCP Server
+sidebarTitle: FastMCP Servers
description: Learn about the core FastMCP server class and how to run it.
icon: server
---
@@ -97,11 +97,7 @@ See [Prompts](/servers/prompts) for detailed documentation.
## Running the Server
-FastMCP servers need a transport mechanism to communicate with clients. In the MCP protocol, servers typically run as separate processes that clients connect to.
-
-### The `__main__` Block Pattern
-
-The standard way to make your server executable is to include a `run()` call inside an `if __name__ == "__main__":` block:
+FastMCP servers need a transport mechanism to communicate with clients. You typically start your server by calling the `mcp.run()` method on your `FastMCP` instance, often within an `if __name__ == "__main__":` block in your main server script. This pattern ensures compatibility with various MCP clients.
```python
# my_server.py
@@ -115,118 +111,17 @@ def greet(name: str) -> str:
return f"Hello, {name}!"
if __name__ == "__main__":
- # This code only runs when the file is executed directly
-
- # Basic run with default settings (stdio transport)
+ # This runs the server, defaulting to STDIO transport
mcp.run()
- # Or with specific transport and parameters
- # mcp.run(transport="sse", host="127.0.0.1", port=9000)
+ # To use a different transport, e.g., Streamable HTTP:
+ # mcp.run(transport="streamable-http", host="127.0.0.1", port=9000)
```
-This pattern is important because:
+FastMCP supports several transport options like STDIO (default, for local tools), Streamable HTTP (recommended for web services), and SSE (legacy web transport). The server can also be run using the FastMCP CLI.
-1. **Client Compatibility**: Standard MCP clients (like Claude Desktop) expect to execute your server file directly with `python my_server.py`
-2. **Process Isolation**: Each server runs in its own process, allowing clients to manage multiple servers independently
-3. **Import Safety**: The main block prevents the server from running when the file is imported by other code
+For detailed information on each transport, how to configure them (host, port, paths), and when to use which, please refer to the [**Running Your FastMCP Server**](/deployment/running-server) guide.
-While this pattern is technically optional when using FastMCP's CLI, it's considered a best practice for maximum compatibility with all MCP clients.
-
-### Transport Options
-
-FastMCP supports two transport mechanisms:
-
-#### STDIO Transport (Default)
-
-The standard input/output (STDIO) transport is the default and most widely compatible option:
-
-```python
-# Run with stdio (default)
-mcp.run() # or explicitly: mcp.run(transport="stdio")
-```
-
-With STDIO:
-- The client starts a new server process for each session
-- Communication happens through standard input/output streams
-- The server process terminates when the client disconnects
-- This is ideal for integrations with tools like Claude Desktop, where each conversation gets its own server instance
-
-#### SSE Transport (Server-Sent Events)
-
-For long-running servers that serve multiple clients, FastMCP supports SSE:
-
-```python
-# Run with SSE on default host/port (0.0.0.0:8000)
-mcp.run(transport="sse")
-```
-
-With SSE:
-- The server runs as a persistent web server
-- Multiple clients can connect simultaneously
-- The server stays running until explicitly terminated
-- This is ideal for remote access to services
-
-You can configure transport parameters directly when running the server:
-
-```python
-# 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
-)
-
-# 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"
- )
-)
-```
-
-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
-
-The FastMCP CLI provides a convenient way to run servers:
-
-```bash
-# Run a server (defaults to stdio transport)
-fastmcp run my_server.py:mcp
-
-# Explicitly specify a transport
-fastmcp run my_server.py:mcp --transport sse
-
-# 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.
## Composing Servers
@@ -289,7 +184,7 @@ print(mcp.settings.on_duplicate_tools) # Output: "error"
### Key Configuration Options
-- **`host`**: Host address for SSE transport (default: "0.0.0.0")
+- **`host`**: Host address for SSE transport (default: "127.0.0.1")
- **`port`**: Port number for SSE transport (default: 8000)
- **`log_level`**: Logging level (default: "INFO")
- **`on_duplicate_tools`**: How to handle duplicate tool registrations
@@ -336,36 +231,24 @@ If the serializer function raises an exception, the tool will fall back to the d
-FastMCP inherits support for OAuth 2.0 authentication from the MCP protocol, allowing servers to protect their tools and resources behind authentication.
-
-### OAuth 2.0 Support
-
-The `mcp.server.auth` module implements an OAuth 2.0 server interface that servers can use by providing an implementation of the `OAuthServerProvider` protocol.
+FastMCP supports OAuth 2.0 authentication, allowing servers to protect their tools and resources. This is configured by providing an `auth_server_provider` and `auth` settings during `FastMCP` initialization.
```python
from fastmcp import FastMCP
-from mcp.server.auth.settings import (
- RevocationOptions,
- ClientRegistrationOptions,
- AuthSettings,
-)
+from mcp.server.auth.settings import AuthSettings #, ... other auth imports
+# from your_auth_implementation import MyOAuthServerProvider # Placeholder
-
-# Create a server with authentication
-mcp = FastMCP(
- name="SecureApp",
- auth_provider=MyOAuthServerProvider(),
- auth=AuthSettings(
- issuer_url="https://myapp.com",
- revocation_options=RevocationOptions(
- enabled=True,
- ),
- client_registration_options=ClientRegistrationOptions(
- enabled=True,
- valid_scopes=["myscope", "myotherscope"],
- default_scopes=["myscope"],
- ),
- required_scopes=["myscope"],
- ),
-)
+# Create a server with authentication (conceptual example)
+# mcp = FastMCP(
+# name="SecureApp",
+# auth_server_provider=MyOAuthServerProvider(),
+# auth=AuthSettings(
+# issuer_url="https://myapp.com",
+# # ... other OAuth settings ...
+# required_scopes=["myscope"],
+# ),
+# )
```
+Due to the low-level nature of the current MCP SDK's auth provider interface, detailed implementation is beyond a quick example. Refer to the [MCP SDK documentation](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization) for specifics on implementing an `OAuthAuthorizationServerProvider`. FastMCP integrates with this by passing the provider and settings to the underlying MCP server.
+
+A dedicated [Authentication guide](/deployment/authentication) will cover this in more detail once higher-level abstractions are available in FastMCP.
diff --git a/src/fastmcp/cli/cli.py b/src/fastmcp/cli/cli.py
index cb4c112ab..cfa34c626 100644
--- a/src/fastmcp/cli/cli.py
+++ b/src/fastmcp/cli/cli.py
@@ -334,7 +334,7 @@ def run(
str | None,
typer.Option(
"--host",
- help="Host to bind to when using sse transport (default: 0.0.0.0)",
+ help="Host to bind to when using sse transport (default: 127.0.0.1)",
),
] = None,
port: Annotated[
diff --git a/src/fastmcp/client/transports.py b/src/fastmcp/client/transports.py
index 3d5e1e149..00d9e5b65 100644
--- a/src/fastmcp/client/transports.py
+++ b/src/fastmcp/client/transports.py
@@ -1,9 +1,11 @@
import abc
import contextlib
import datetime
+import inspect
import os
import shutil
import sys
+import warnings
from collections.abc import AsyncIterator
from pathlib import Path
from typing import Any, TypedDict
@@ -450,6 +452,8 @@ def infer_transport(
This function attempts to infer the correct transport type from the provided
argument, handling various input types and converting them to the appropriate
ClientTransport subclass.
+
+ For HTTP URLs, they are assumed to be Streamable HTTP URLs unless they end in `/sse`.
"""
# the transport is already a ClientTransport
if isinstance(transport, ClientTransport):
@@ -470,10 +474,19 @@ def infer_transport(
# the transport is an http(s) URL
elif isinstance(transport, AnyUrl | str) and str(transport).startswith("http"):
- if str(transport).endswith("/sse"):
- return SSETransport(url=transport)
- else:
- return StreamableHttpTransport(url=transport)
+ if str(transport).rstrip("/").endswith("/sse"):
+ warnings.warn(
+ inspect.cleandoc(
+ """
+ As of FastMCP 2.3.0, HTTP URLs are inferred to use Streamable HTTP.
+ The provided URL ends in `/sse`, so you may encounter unexpected behavior.
+ If you intended to use SSE, please use the `SSETransport` class directly.
+ """
+ ),
+ category=UserWarning,
+ stacklevel=2,
+ )
+ return StreamableHttpTransport(url=transport)
# the transport is a websocket URL
elif isinstance(transport, AnyUrl | str) and str(transport).startswith("ws"):
diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py
index b9bb07bd4..0d7c60ef8 100644
--- a/src/fastmcp/server/server.py
+++ b/src/fastmcp/server/server.py
@@ -718,6 +718,8 @@ class FastMCP(Generic[LifespanResultT]):
host: str | None = None,
port: int | None = None,
log_level: str | None = None,
+ path: str | None = None,
+ message_path: str | None = None,
uvicorn_config: dict | None = None,
) -> None:
"""Run the server using SSE transport."""
@@ -726,7 +728,7 @@ class FastMCP(Generic[LifespanResultT]):
# timeout to make it possible to close immediately. see
# https://github.com/jlowin/fastmcp/issues/296
uvicorn_config.setdefault("timeout_graceful_shutdown", 0)
- app = self.sse_app()
+ app = self.sse_app(path=path, message_path=message_path)
config = uvicorn.Config(
app,
@@ -738,25 +740,29 @@ class FastMCP(Generic[LifespanResultT]):
server = uvicorn.Server(config)
await server.serve()
- def sse_app(self) -> Starlette:
+ def sse_app(
+ self,
+ path: str | None = None,
+ message_path: str | None = None,
+ ) -> Starlette:
"""Return an instance of the SSE server app."""
return create_sse_app(
server=self,
- message_path=self.settings.message_path,
- sse_path=self.settings.sse_path,
+ message_path=message_path or self.settings.message_path,
+ sse_path=path or self.settings.sse_path,
auth_server_provider=self._auth_server_provider,
auth_settings=self.settings.auth,
debug=self.settings.debug,
additional_routes=self._additional_http_routes,
)
- def streamable_http_app(self) -> Starlette:
+ def streamable_http_app(self, path: str | None = None) -> Starlette:
"""Return an instance of the StreamableHTTP server app."""
from fastmcp.server.http import create_streamable_http_app
return create_streamable_http_app(
server=self,
- streamable_http_path=self.settings.streamable_http_path,
+ streamable_http_path=path or self.settings.streamable_http_path,
event_store=None,
auth_server_provider=self._auth_server_provider,
auth_settings=self.settings.auth,
@@ -771,13 +777,14 @@ class FastMCP(Generic[LifespanResultT]):
host: str | None = None,
port: int | None = None,
log_level: str | None = None,
+ path: str | None = None,
uvicorn_config: dict | None = None,
) -> None:
"""Run the server using StreamableHTTP transport."""
uvicorn_config = uvicorn_config or {}
uvicorn_config.setdefault("timeout_graceful_shutdown", 0)
- app = self.streamable_http_app()
+ app = self.streamable_http_app(path=path)
config = uvicorn.Config(
app,
diff --git a/src/fastmcp/settings.py b/src/fastmcp/settings.py
index 21395ce7c..0e4a68f37 100644
--- a/src/fastmcp/settings.py
+++ b/src/fastmcp/settings.py
@@ -59,9 +59,9 @@ class ServerSettings(BaseSettings):
# HTTP settings
host: str = "127.0.0.1"
port: int = 8000
- sse_path: str = "/sse"
+ sse_path: str = "/sse/"
message_path: str = "/messages/"
- streamable_http_path: str = "/mcp"
+ streamable_http_path: str = "/mcp/"
debug: bool = False
# resource settings
diff --git a/test.py b/test.py
new file mode 100644
index 000000000..e5931b8e6
--- /dev/null
+++ b/test.py
@@ -0,0 +1,12 @@
+from fastmcp import FastMCP
+
+mcp = FastMCP()
+
+if __name__ == "__main__":
+ mcp.run(
+ transport="streamable-http",
+ host="127.0.0.1",
+ port=4200,
+ path="/my-custom-path/",
+ log_level="debug",
+ )