diff --git a/.github/workflows/run-static.yml b/.github/workflows/run-static.yml index 30451ba18..0b172865d 100644 --- a/.github/workflows/run-static.yml +++ b/.github/workflows/run-static.yml @@ -77,6 +77,7 @@ jobs: # Check if docs.json content changed (ignoring formatting) if ! jq --sort-keys . docs/docs.json.orig | diff -q - <(jq --sort-keys . docs/docs.json) > /dev/null 2>&1; then echo "❌ docs.json content has changed!" + echo "" echo "Run `just api-ref-all` to regenerate the SDK docs, then commit the changes. Note: you may need to install `just` (https://github.com/casey/just)" exit 1 fi diff --git a/docs/assets/images/fastmcp_cloud/create_project.png b/docs/assets/images/fastmcp_cloud/create_project.png new file mode 100644 index 000000000..e34c8a1f4 Binary files /dev/null and b/docs/assets/images/fastmcp_cloud/create_project.png differ diff --git a/docs/assets/images/fastmcp_cloud/deployment.png b/docs/assets/images/fastmcp_cloud/deployment.png new file mode 100644 index 000000000..44011d900 Binary files /dev/null and b/docs/assets/images/fastmcp_cloud/deployment.png differ diff --git a/docs/assets/images/fastmcp_cloud/quickstart.png b/docs/assets/images/fastmcp_cloud/quickstart.png new file mode 100644 index 000000000..31c44472e Binary files /dev/null and b/docs/assets/images/fastmcp_cloud/quickstart.png differ diff --git a/docs/deployment/fastmcp-cloud.mdx b/docs/deployment/fastmcp-cloud.mdx new file mode 100644 index 000000000..1d735a490 --- /dev/null +++ b/docs/deployment/fastmcp-cloud.mdx @@ -0,0 +1,54 @@ +--- +title: FastMCP Cloud +sidebarTitle: FastMCP Cloud +description: The fastest way to deploy your MCP server +icon: cloud +tag: NEW +--- + +[FastMCP Cloud](https://fastmcp.cloud) is a managed platform for hosting MCP servers, built by the FastMCP team. While the FastMCP framework will always be fully open-source, we created FastMCP Cloud to solve the deployment challenges we've seen developers face. Our goal is to provide the absolute fastest way to make your MCP server available to LLM clients like Claude and Cursor. + +FastMCP Cloud is a young product and we welcome your feedback. Please join our [Discord](https://discord.com/invite/aGsSC3yDF4) to share your thoughts and ideas, and you can expect to see new features and improvements every week. + + +FastMCP Cloud is completely free while in beta! + + +## Getting Started + +Deploying to FastMCP Cloud takes just three simple steps. + +### Step 1: Create a Project + +Visit [fastmcp.cloud](https://fastmcp.cloud) and sign in with your GitHub account. Create a project either by selecting an existing repository or using the FastMCP Cloud quickstart repo. +FastMCP Cloud Quickstart Screen + +### Step 2: Configure your Server + +The configuration screen lets you specify: +- **Name**: The name of your project. This will be used to generate a unique URL for your server. +- **Entrypoint**: The Python file containing your FastMCP server (e.g., `echo.py`). This field has the same syntax as the `fastmcp run` command, for example `echo.py:my_server` to specify a specific object in the file. +- **Authentication**: Whether to require OAuth for client connections. + +FastMCP Cloud Configuration Screen + +### Step 3: Deploy + +Once you create your project, FastMCP Cloud will: +1. Create the repository (if using quickstart) +2. Build your FastMCP server +3. Deploy it to a unique URL +4. Make it immediately available for connections + +FastMCP Cloud Deployment Screen + + +The deployed server is live and accessible at a URL like: + +``` +https://your-project-name.fastmcp.app/mcp +``` + +You should be able to connect to it as soon as you see the deployment succeed! + +Any time you open a PR to your repo, FastMCP Cloud will create a new, unique URL for that branch. This allows you to test your changes before merging to production. Each merge to main will trigger a new deployment of the latest version of your server. \ No newline at end of file diff --git a/docs/deployment/running-server.mdx b/docs/deployment/running-server.mdx index 5d98687ed..28f1217a7 100644 --- a/docs/deployment/running-server.mdx +++ b/docs/deployment/running-server.mdx @@ -1,17 +1,15 @@ --- -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. +title: Running Your Server +sidebarTitle: Running +description: Learn how to run your FastMCP server locally for development and testing 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. +FastMCP servers can be run in different ways depending on your needs. This guide focuses on running servers locally for development and testing. For production deployment to a URL, see the [Self-Hosted Deployment](/deployment/self-hosted) guide. ## The `run()` Method -FastMCP servers can be run directly from Python by calling the `run()` method on a `FastMCP` instance. +Every FastMCP server needs to be started to accept connections. The simplest way to run a server is by calling the `run()` method on your FastMCP instance. This method starts the server and blocks until it's stopped, handling all the connection management for you. 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. @@ -29,33 +27,101 @@ def hello(name: str) -> str: 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 Protocols + +MCP servers communicate with clients through different transport protocols. Think of transports as the "language" your server speaks to communicate with clients. FastMCP supports three main transport protocols, each designed for specific use cases and deployment scenarios. + +The choice of transport determines how clients connect to your server, what network capabilities are available, and how many clients can connect simultaneously. Understanding these transports helps you choose the right approach for your application. + +### STDIO Transport (Default) + +STDIO (Standard Input/Output) is the default transport for FastMCP servers. When you call `run()` without arguments, your server uses STDIO transport. This transport communicates through standard input and output streams, making it perfect for command-line tools and desktop applications like Claude Desktop. + +With STDIO transport, the client spawns a new server process for each session and manages its lifecycle. The server reads MCP messages from stdin and writes responses to stdout. This is why STDIO servers don't stay running - they're started on-demand by the client. + +```python +from fastmcp import FastMCP + +mcp = FastMCP("MyServer") + +@mcp.tool +def hello(name: str) -> str: + return f"Hello, {name}!" + +if __name__ == "__main__": + mcp.run() # Uses STDIO transport by default +``` + +STDIO is ideal for: +- Local development and testing +- Claude Desktop integration +- Command-line tools +- Single-user applications + +### HTTP Transport (Streamable) + +HTTP transport turns your MCP server into a web service accessible via a URL. This transport uses the Streamable HTTP protocol, which allows clients to connect over the network. Unlike STDIO where each client gets its own process, an HTTP server can handle multiple clients simultaneously. + +The Streamable HTTP protocol provides full bidirectional communication between client and server, supporting all MCP operations including streaming responses. This makes it the recommended choice for network-based deployments. + +To use HTTP transport, specify it in the `run()` method along with networking options: + +```python +from fastmcp import FastMCP + +mcp = FastMCP("MyServer") + +@mcp.tool +def hello(name: str) -> str: + return f"Hello, {name}!" + +if __name__ == "__main__": + # Start an HTTP server on port 8000 + mcp.run(transport="http", host="127.0.0.1", port=8000) +``` + +Your server is now accessible at `http://localhost:8000/mcp/`. This URL is the MCP endpoint that clients will connect to. HTTP transport enables: +- Network accessibility +- Multiple concurrent clients +- Integration with web infrastructure +- Remote deployment capabilities + +For production HTTP deployment with authentication and advanced configuration, see the [Self-Hosted Deployment](/deployment/self-hosted) guide. + +### SSE Transport (Legacy) + +Server-Sent Events (SSE) transport was the original HTTP-based transport for MCP. While still supported for backward compatibility, it has limitations compared to the newer Streamable HTTP transport. SSE only supports server-to-client streaming, making it less efficient for bidirectional communication. + +```python +if __name__ == "__main__": + # SSE transport - use HTTP instead for new projects + mcp.run(transport="sse", host="127.0.0.1", port=8000) +``` + +We recommend using HTTP transport instead of SSE for all new projects. SSE remains available only for compatibility with older clients that haven't upgraded to Streamable HTTP. + +### Choosing the Right Transport + +Each transport serves different needs. STDIO is perfect when you need simple, local execution - it's what Claude Desktop and most command-line tools expect. HTTP transport is essential when you need network access, want to serve multiple clients, or plan to deploy your server remotely. SSE exists only for backward compatibility and shouldn't be used in new projects. + +Consider your deployment scenario: Are you building a tool for local use? STDIO is your best choice. Need a centralized service that multiple clients can access? HTTP transport is the way to go. ## The FastMCP CLI -FastMCP also provides a command-line interface for running servers without modifying the source code. After installing FastMCP, you can run your server directly from the command line: +FastMCP provides a powerful command-line interface for running servers without modifying the source code. The CLI can automatically find and run your server with different transports, manage dependencies, and handle development workflows: ```bash fastmcp run server.py ``` - -**Important**: When using `fastmcp run`, it **ignores** the `if __name__ == "__main__"` block entirely. Instead, it looks for a FastMCP object named `mcp`, `server`, or `app` and calls its `run()` method directly with the transport options you specify. +The CLI automatically finds a FastMCP instance in your file (named `mcp`, `server`, or `app`) and runs it with the specified options. This is particularly useful for testing different transports or configurations without changing your code. -This means you can use `fastmcp run` to override the transport specified in your code, which is particularly useful for testing or changing deployment methods without modifying the code. - +### Dependency Management -You can specify transport options and other configuration: - -```bash -fastmcp run server.py --transport sse --port 9000 -``` - -### Dependency Management with CLI - -When using the FastMCP CLI, you can pass additional options to configure how `uv` runs your server: +The CLI integrates with `uv` to manage Python environments and dependencies: ```bash # Run with a specific Python version @@ -75,28 +141,9 @@ fastmcp run server.py --project /path/to/project ``` -When using `--python`, `--with`, `--project`, or `--with-requirements`, the server runs via `uv run` subprocess instead of using your local environment. The `uv` command will manage dependencies based on your project configuration. +When using `--python`, `--with`, `--project`, or `--with-requirements`, the server runs via `uv run` subprocess instead of using your local environment. - -The `--python` option is particularly useful when you need to run a server with a specific Python version that differs from your system's default. This addresses common compatibility issues where servers require a particular Python version to function correctly. - - -For development and testing, you can use the `dev` command to run your server with the MCP Inspector: - -```bash -fastmcp dev server.py -``` - -The `dev` command also supports the same dependency management options: - -```bash -# Dev server with specific Python version and packages -fastmcp dev server.py --python 3.11 --with pandas -``` - -See the [CLI documentation](/patterns/cli) for detailed information about all available commands and options. - ### Passing Arguments to Servers When servers accept command line arguments (using argparse, click, or other libraries), you can pass them after `--`: @@ -108,174 +155,13 @@ fastmcp run database_server.py -- --database-path /tmp/db.sqlite --debug This is useful for servers that need configuration files, database paths, API keys, or other runtime options. -## Transport Options +For more CLI features including development mode with the MCP Inspector, see the [CLI documentation](/patterns/cli). -Below is a comparison of available transport options to help you choose the right one for your needs: +### Async Usage -| 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 web-based deployments | -| **SSE** | Existing web-based deployments that rely on SSE | Deprecated - prefer Streamable HTTP for new projects | +FastMCP servers are built on async Python, but the framework provides both synchronous and asynchronous APIs to fit your application's needs. The `run()` method we've been using is actually a synchronous wrapper around the async server implementation. -### 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 the recommended transport for web-based deployments. - -To run a server using Streamable HTTP, you can use the `run()` method with the `transport` argument set to `"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="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()) -``` - - - -For backward compatibility, wherever `"http"` is accepted as a transport name, you can also pass `"streamable-http"` as a fully supported alias. This is particularly useful when upgrading from FastMCP 1.x in the official Python SDK and FastMCP \<= 2.9, where `"streamable-http"` was the standard name. - - -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="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 - - -The SSE transport is deprecated and may be removed in a future version. -New applications should use Streamable HTTP transport instead. - - -Server-Sent Events (SSE) is an HTTP-based protocol for server-to-client streaming. While FastMCP still supports SSE, it is deprecated and 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", - ) -``` -```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()) -``` - - - - -## Async Usage - -FastMCP provides both synchronous and asynchronous APIs for running your server. The `run()` method seen in previous examples is a synchronous method that internally uses `anyio.run()` to run the asynchronous server. For applications that are already running in an async context, FastMCP provides the `run_async()` method. +For applications that are already running in an async context, FastMCP provides the `run_async()` method: ```python {10-12} from fastmcp import FastMCP @@ -289,14 +175,14 @@ def hello(name: str) -> str: async def main(): # Use run_async() in async contexts - await mcp.run_async(transport="http") + await mcp.run_async(transport="http", port=8000) if __name__ == "__main__": asyncio.run(main()) ``` -The `run()` method cannot be called from inside an async function because it already creates its own async event loop internally. If you attempt to call `run()` from inside an async function, you'll get an error about the event loop already running. +The `run()` method cannot be called from inside an async function because it creates its own async event loop internally. If you attempt to call `run()` from inside an async function, you'll get an error about the event loop already running. Always use `run_async()` inside async functions and `run()` in synchronous contexts. @@ -305,7 +191,7 @@ Both `run()` and `run_async()` accept the same transport arguments, so all the e ## Custom Routes -You can also add custom web routes to your FastMCP server, which will be exposed alongside the MCP endpoint. To do so, use the `@custom_route` decorator. Note that this is less flexible than using a full ASGI framework, but can be useful for adding simple endpoints like health checks to your standalone server. +When using HTTP transport, you might want to add custom web endpoints alongside your MCP server. This is useful for health checks, status pages, or simple APIs. FastMCP lets you add custom routes using the `@custom_route` decorator: ```python from fastmcp import FastMCP @@ -318,6 +204,55 @@ mcp = FastMCP("MyServer") async def health_check(request: Request) -> PlainTextResponse: return PlainTextResponse("OK") +@mcp.tool +def process(data: str) -> str: + return f"Processed: {data}" + if __name__ == "__main__": - mcp.run() -``` \ No newline at end of file + mcp.run(transport="http") # Health check at http://localhost:8000/health +``` + +Custom routes are served by the same web server as your MCP endpoint. They're available at the root of your domain while the MCP endpoint is at `/mcp/`. For more complex web applications, consider [mounting your MCP server into a FastAPI or Starlette app](/deployment/self-hosted#integration-with-web-frameworks). + +## Alternative Initialization Patterns + +The `if __name__ == "__main__"` pattern works well for standalone scripts, but some deployment scenarios require different approaches. FastMCP handles these cases automatically. + +### CLI-Only Servers + +When using the FastMCP CLI, you don't need the `if __name__` block at all. The CLI will find your FastMCP instance and run it: + +```python +# server.py +from fastmcp import FastMCP + +mcp = FastMCP("MyServer") # CLI looks for 'mcp', 'server', or 'app' + +@mcp.tool +def process(data: str) -> str: + return f"Processed: {data}" + +# No if __name__ block needed - CLI will find and run 'mcp' +``` + +### ASGI Applications + +For ASGI deployment (running with Uvicorn or similar), you'll want to create an ASGI application object. This approach is common in production deployments where you need more control over the server configuration: + +```python +# app.py +from fastmcp import FastMCP + +def create_app(): + mcp = FastMCP("MyServer") + + @mcp.tool + def process(data: str) -> str: + return f"Processed: {data}" + + return mcp.http_app() + +app = create_app() # Uvicorn will use this +``` + +See the [Self-Hosted Deployment](/deployment/self-hosted) guide for more ASGI deployment patterns. \ No newline at end of file diff --git a/docs/deployment/self-hosted.mdx b/docs/deployment/self-hosted.mdx new file mode 100644 index 000000000..12b7a5f11 --- /dev/null +++ b/docs/deployment/self-hosted.mdx @@ -0,0 +1,209 @@ +--- +title: Self-Hosted Remote MCP +sidebarTitle: Self-Hosted +description: Deploy your FastMCP server as a remote MCP service accessible via URL +icon: server +--- + + +STDIO transport is perfect for local development and desktop applications. But to unlock the full potential of MCP—centralized services, multi-client access, and network availability—you need remote HTTP deployment. + + +This guide walks you through deploying your FastMCP server as a remote MCP service that's accessible via a URL. Once deployed, your MCP server will be available over the network, allowing multiple clients to connect simultaneously and enabling integration with cloud-based LLM applications. This guide focuses specifically on remote MCP deployment, not local STDIO servers. + +## Choosing Your Approach + +FastMCP provides two ways to deploy your server as an HTTP service. Understanding the trade-offs helps you choose the right approach for your needs. + +The **direct HTTP server** approach is simpler and perfect for getting started quickly. You modify your server's `run()` method to use HTTP transport, and FastMCP handles all the web server configuration. This approach works well for standalone deployments where you want your MCP server to be the only service running on a port. + +The **ASGI application** approach gives you more control and flexibility. Instead of running the server directly, you create an ASGI application that can be served by production-grade servers like Uvicorn or Gunicorn. This approach is better when you need advanced server features like multiple workers, custom middleware, or when you're integrating with existing web applications. + +### Direct HTTP Server + +The simplest way to get your MCP server online is to use the built-in `run()` method with HTTP transport. This approach handles all the server configuration for you and is ideal when you want a standalone MCP server without additional complexity. + +```python server.py +from fastmcp import FastMCP + +mcp = FastMCP("My Server") + +@mcp.tool +def process_data(input: str) -> str: + """Process data on the server""" + return f"Processed: {input}" + +if __name__ == "__main__": + mcp.run(transport="http", host="0.0.0.0", port=8000) +``` + +Run your server with a simple Python command: +```bash +python server.py +``` + +Your server is now accessible at `http://localhost:8000/mcp/` (or use your server's actual IP address for remote access). + +This approach is ideal when you want to get online quickly with minimal configuration. It's perfect for internal tools, development environments, or simple deployments where you don't need advanced server features. The built-in server handles all the HTTP details, letting you focus on your MCP implementation. + +### ASGI Application + +For production deployments, you'll often want more control over how your server runs. FastMCP can create a standard ASGI application that works with any ASGI server like Uvicorn, Gunicorn, or Hypercorn. This approach is particularly useful when you need to configure advanced server options, run multiple workers, or integrate with existing infrastructure. + +```python app.py +from fastmcp import FastMCP + +mcp = FastMCP("My Server") + +@mcp.tool +def process_data(input: str) -> str: + """Process data on the server""" + return f"Processed: {input}" + +# Create ASGI application +app = mcp.http_app() +``` + +Run with any ASGI server - here's an example with Uvicorn: +```bash +uvicorn app:app --host 0.0.0.0 --port 8000 +``` + +Your server is accessible at the same URL: `http://localhost:8000/mcp/` (or use your server's actual IP address for remote access). + +The ASGI approach shines in production environments where you need reliability and performance. You can run multiple worker processes to handle concurrent requests, add custom middleware for logging or monitoring, integrate with existing deployment pipelines, or mount your MCP server as part of a larger application. This flexibility makes it the preferred choice for serious deployments. + +## Configuring Your Server + +### Custom Path + +By default, your MCP server is accessible at `/mcp/` on your domain. You can customize this path to fit your URL structure or avoid conflicts with existing endpoints. This is particularly useful when integrating MCP into an existing application or following specific API conventions. + +```python +# Option 1: With mcp.run() +mcp.run(transport="http", host="0.0.0.0", port=8000, path="/api/mcp/") + +# Option 2: With ASGI app +app = mcp.http_app(path="/api/mcp/") +``` + +Now your server is accessible at `http://localhost:8000/api/mcp/`. + +### Authentication + + +Authentication is **highly recommended** for remote MCP servers. Some LLM clients require authentication for remote servers and will refuse to connect without it. + + +FastMCP supports multiple authentication methods to secure your remote server. See the [Authentication Overview](/servers/auth/authentication) for complete configuration options including Bearer tokens, JWT, and OAuth. + +### Health Checks + +Health check endpoints are essential for monitoring your deployed server and ensuring it's responding correctly. FastMCP allows you to add custom routes alongside your MCP endpoints, making it easy to implement health checks that work with both deployment approaches. + +```python +from starlette.responses import JSONResponse + +@mcp.custom_route("/health", methods=["GET"]) +async def health_check(request): + return JSONResponse({"status": "healthy", "service": "mcp-server"}) +``` + +This health endpoint will be available at `http://localhost:8000/health` and can be used by load balancers, monitoring systems, or deployment platforms to verify your server is running. + +## Integration with Web Frameworks + +If you already have a web application running, you can add MCP capabilities by mounting a FastMCP server as a sub-application. This allows you to expose MCP tools alongside your existing API endpoints, sharing the same domain and infrastructure. The MCP server becomes just another route in your application, making it easy to manage and deploy. + +For detailed integration guides, see: +- [FastAPI Integration](/integrations/fastapi) +- [Starlette Integration](/integrations/starlette) + +Here's a quick example showing how to add MCP to an existing FastAPI application: + +```python +from fastapi import FastAPI +from fastmcp import FastMCP + +# Your existing API +api = FastAPI() + +@api.get("/api/status") +def status(): + return {"status": "ok"} + +# Create your MCP server +mcp = FastMCP("API Tools") + +@mcp.tool +def query_database(query: str) -> dict: + """Run a database query""" + return {"result": "data"} + +# Mount MCP at /mcp +api.mount("/mcp", mcp.http_app()) + +# Run with: uvicorn app:api --host 0.0.0.0 --port 8000 +``` + +Your existing API remains at `http://localhost:8000/api/` while MCP is available at `http://localhost:8000/mcp/`. + +## Production Deployment + +### Running with Uvicorn + +When deploying to production, you'll want to optimize your server for performance and reliability. Uvicorn provides several options to improve your server's capabilities, including running multiple worker processes to handle concurrent requests and enabling enhanced logging for monitoring. + +```bash +# Install uvicorn with standard extras for better performance +pip install 'uvicorn[standard]' + +# Run with multiple workers for better concurrency +uvicorn app:app --host 0.0.0.0 --port 8000 --workers 4 + +# Enable detailed logging for monitoring +uvicorn app:app --host 0.0.0.0 --port 8000 --log-level info +``` + +### Environment Variables + +Production deployments should never hardcode sensitive information like API keys or authentication tokens. Instead, use environment variables to configure your server at runtime. This keeps your code secure and makes it easy to deploy the same code to different environments with different configurations. + +Here's an example using bearer token authentication (though OAuth is recommended for production): + +```python +import os +from fastmcp import FastMCP +from fastmcp.server.auth import BearerTokenAuth + +# Read configuration from environment +auth_token = os.environ.get("MCP_AUTH_TOKEN") +if auth_token: + auth = BearerTokenAuth(token=auth_token) + mcp = FastMCP("Production Server", auth=auth) +else: + mcp = FastMCP("Production Server") + +app = mcp.http_app() +``` + +Deploy with your secrets safely stored in environment variables: +```bash +MCP_AUTH_TOKEN=secret uvicorn app:app --host 0.0.0.0 --port 8000 +``` + +## Testing Your Deployment + +Once your server is deployed, you'll need to verify it's accessible and functioning correctly. For comprehensive testing strategies including connectivity tests, client testing, and authentication testing, see the [Testing Your Server](/deployment/testing) guide. + +## Hosting Your Server + +This guide has shown you how to create an HTTP-accessible MCP server, but you'll still need a hosting provider to make it available on the internet. Your FastMCP server can run anywhere that supports Python web applications: + +- **Cloud VMs** (AWS EC2, Google Compute Engine, Azure VMs) +- **Container platforms** (Cloud Run, Container Instances, ECS) +- **Platform-as-a-Service** (Railway, Render, Vercel) +- **Edge platforms** (Cloudflare Workers) +- **Kubernetes clusters** (self-managed or managed) + +The key requirements are Python 3.10+ support and the ability to expose an HTTP port. Most providers will require you to package your server (requirements.txt, Dockerfile, etc.) according to their deployment format. For managed, zero-configuration deployment, see [FastMCP Cloud](/deployment/fastmcp-cloud). \ No newline at end of file diff --git a/docs/deployment/testing.mdx b/docs/deployment/testing.mdx new file mode 100644 index 000000000..f5db67b58 --- /dev/null +++ b/docs/deployment/testing.mdx @@ -0,0 +1,159 @@ +--- +title: Testing Your Server +sidebarTitle: Testing +description: Unit test your MCP servers with the FastMCP Client's deterministic testing capabilities +icon: vial +--- + +The [FastMCP Client](/clients/client) is a deterministic testing tool that gives you complete programmatic control over MCP server interactions. You call specific tools with exact arguments, verify responses, and test edge cases - making it ideal for unit testing your MCP servers. + +## In-Memory Testing + +The FastMCP Client's standout feature is in-memory testing. Instead of deploying your server or managing network connections, you pass your server instance directly to the client. This creates a zero-overhead connection that runs entirely in memory. + +What makes this approach so powerful is that everything runs in the same Python process. You can set breakpoints anywhere - in your test code or inside your server handlers - and step through with your debugger. There's no server startup scripts, no port management, no cleanup between tests. Tests execute instantly without network overhead. + +```python +from fastmcp import FastMCP, Client + +# Create your server +server = FastMCP("WeatherServer") + +@server.tool +def get_temperature(city: str) -> dict: + """Get current temperature for a city""" + temps = {"NYC": 72, "LA": 85, "Chicago": 68} + return {"city": city, "temp": temps.get(city, 70)} + +@server.resource("weather://forecast") +def get_forecast() -> dict: + """Get 5-day forecast""" + return {"days": 5, "conditions": "sunny"} + +async def test_weather_operations(): + # Pass server directly - no deployment needed + async with Client(server) as client: + # Test tool execution + result = await client.call_tool("get_temperature", {"city": "NYC"}) + assert result.data == {"city": "NYC", "temp": 72} + + # Test resource retrieval + forecast = await client.read_resource("weather://forecast") + assert forecast.contents[0].data == {"days": 5, "conditions": "sunny"} +``` + +The in-memory approach transforms MCP testing from a deployment challenge into standard unit testing. You focus on testing your server's behavior, not wrestling with infrastructure. + +## Testing with Frameworks + +The FastMCP Client works seamlessly with any Python testing framework. Whether you prefer pytest, unittest, or another framework, the pattern remains consistent: create a server, pass it to the client, and verify behavior. + +```python +import pytest +from fastmcp import FastMCP, Client + +@pytest.fixture +def weather_server(): + server = FastMCP("WeatherServer") + + @server.tool + def get_temperature(city: str) -> dict: + temps = {"NYC": 72, "LA": 85, "Chicago": 68} + return {"city": city, "temp": temps.get(city, 70)} + + return server + +@pytest.mark.asyncio +async def test_temperature_tool(weather_server): + async with Client(weather_server) as client: + result = await client.call_tool("get_temperature", {"city": "LA"}) + assert result.data == {"city": "LA", "temp": 85} + +@pytest.mark.asyncio +async def test_unknown_city(weather_server): + async with Client(weather_server) as client: + result = await client.call_tool("get_temperature", {"city": "Paris"}) + assert result.data["temp"] == 70 # Default temperature +``` + +## Mocking External Dependencies + +FastMCP servers are standard Python objects, so you can mock external dependencies using your preferred mocking approach. Replace databases, APIs, or any external service with test doubles to keep your tests fast and deterministic. + +```python +from unittest.mock import AsyncMock + +async def test_database_tool(): + server = FastMCP("DataServer") + + # Mock the database + mock_db = AsyncMock() + mock_db.fetch_users.return_value = [ + {"id": 1, "name": "Alice"}, + {"id": 2, "name": "Bob"} + ] + + @server.tool + async def list_users() -> list: + return await mock_db.fetch_users() + + async with Client(server) as client: + result = await client.call_tool("list_users", {}) + assert len(result.data) == 2 + assert result.data[0]["name"] == "Alice" + mock_db.fetch_users.assert_called_once() +``` + +## Testing Deployed Servers + +While in-memory testing covers most unit testing needs, you'll occasionally need to test against a deployed server - to verify authentication, test network behavior, or validate deployments. + +### HTTP Transport Testing + +When you need to test actual network behavior or verify a deployment, connect to your running server using its URL: + +```python +from fastmcp import Client + +async def test_deployed_server(): + # Connect to a running server + async with Client("http://localhost:8000/mcp/") as client: + await client.ping() + + # Test with real network transport + tools = await client.list_tools() + assert len(tools) > 0 + + result = await client.call_tool("greet", {"name": "World"}) + assert "Hello" in result.data +``` + +### Testing Authentication + +The FastMCP Client handles authentication transparently, making it easy to test secured servers: + +```python +async def test_authenticated_server(): + # Bearer token authentication + async with Client( + "https://api.example.com/mcp", + headers={"Authorization": "Bearer test-token"} + ) as client: + await client.ping() + tools = await client.list_tools() + + # OAuth flow (opens browser for authorization) + async with Client("https://api.example.com/mcp", auth="oauth") as client: + result = await client.call_tool("protected_tool", {}) + assert result.data is not None +``` + +## Best Practices + +1. **Default to in-memory testing** - It's faster, more reliable, and easier to debug +2. **Test behavior, not implementation** - Call tools and verify responses rather than testing internals +3. **Use framework fixtures** - Create reusable server configurations for your test suite +4. **Mock external dependencies** - Keep tests fast and deterministic by mocking databases, APIs, etc. +5. **Test error cases** - Verify your server handles invalid inputs and edge cases properly + +The FastMCP Client transforms MCP server testing from a deployment challenge into a straightforward unit testing task. With in-memory connections and deterministic control, you can build comprehensive test suites that run in milliseconds. \ No newline at end of file diff --git a/docs/docs.json b/docs/docs.json index c8d1ea394..6101649c7 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -20,7 +20,7 @@ "background": "/assets/brand/card-background.png" }, "banner": { - "content": "Remote MCP that just works.   [Try FastMCP Cloud ->](https://fastmcp.link/IhmBxWn)" + "content": "Remote MCP that just works: [FastMCP Cloud is here!](https://fastmcp.link/IhmBxWn) " }, "colors": { "dark": "#f72585", @@ -70,14 +70,7 @@ { "group": "Servers", "pages": [ - { - "group": "Essentials", - "icon": "cube", - "pages": [ - "servers/server", - "deployment/running-server" - ] - }, + "servers/server", { "group": "Core Components", "icon": "toolbox", @@ -110,6 +103,16 @@ "servers/auth/token-verification", "servers/auth/full-oauth-server" ] + }, + { + "group": "Deployment", + "icon": "rocket", + "pages": [ + "deployment/running-server", + "deployment/testing", + "deployment/self-hosted", + "deployment/fastmcp-cloud" + ] } ] }, @@ -119,10 +122,7 @@ { "group": "Essentials", "icon": "cube", - "pages": [ - "clients/client", - "clients/transports" - ] + "pages": ["clients/client", "clients/transports"] }, { "group": "Core Operations", @@ -148,10 +148,7 @@ { "group": "Authentication", "icon": "user-shield", - "pages": [ - "clients/auth/oauth", - "clients/auth/bearer" - ] + "pages": ["clients/auth/oauth", "clients/auth/bearer"] } ] }, @@ -166,6 +163,7 @@ "integrations/cursor", "integrations/eunomia-authorization", "integrations/fastapi", + "deployment/fastmcp-cloud", "integrations/gemini", "integrations/mcp-json-configuration", "integrations/openai", @@ -179,7 +177,6 @@ "pages": [ "patterns/tool-transformation", "patterns/decorating-methods", - "patterns/testing", "patterns/cli", "patterns/contrib" ] @@ -197,17 +194,12 @@ }, { "anchor": "What's New", - "pages": [ - "updates", - "changelog" - ] + "pages": ["updates", "changelog"] }, { "anchor": "Community", "icon": "users", - "pages": [ - "community/showcase" - ] + "pages": ["community/showcase"] } ] }, diff --git a/docs/patterns/testing.mdx b/docs/patterns/testing.mdx deleted file mode 100644 index fc3a077e8..000000000 --- a/docs/patterns/testing.mdx +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: Testing MCP Servers -sidebarTitle: Testing -description: Learn how to test your FastMCP servers effectively -icon: vial ---- - - -Testing your MCP servers thoroughly is essential for ensuring they work correctly when deployed. FastMCP makes this easy through a variety of testing patterns. - -## In-Memory Testing - -The most efficient way to test an MCP server is to pass your FastMCP server instance directly to a Client. This enables in-memory testing without having to start a separate server process, which is particularly useful because managing an MCP server programmatically can be challenging. - -Here is an example of using a `Client` to test a server with pytest: - -```python -import pytest -from fastmcp import FastMCP, Client - -@pytest.fixture -def mcp_server(): - server = FastMCP("TestServer") - - @server.tool - def greet(name: str) -> str: - return f"Hello, {name}!" - - return server - -async def test_tool_functionality(mcp_server): - # Pass the server directly to the Client constructor - async with Client(mcp_server) as client: - result = await client.call_tool("greet", {"name": "World"}) - assert result.data == "Hello, World!" -``` - -This pattern creates a direct connection between the client and server, allowing you to test your server's functionality efficiently. - - -If you're using pytest for async tests, as shown above, you may need to configure appropriate markers or set `asyncio_mode = "auto"` in your pytest configuration in order to handle async test functions automatically. - - - -## Mocking - -FastMCP servers are designed to work seamlessly with standard Python testing tools and patterns. There's nothing special about testing FastMCP servers - you can use all the familiar Python mocking, patching, and testing techniques you already know. - diff --git a/docs/python-sdk/fastmcp-cli-run.mdx b/docs/python-sdk/fastmcp-cli-run.mdx index 4aaf9995d..0e48d7e93 100644 --- a/docs/python-sdk/fastmcp-cli-run.mdx +++ b/docs/python-sdk/fastmcp-cli-run.mdx @@ -53,7 +53,7 @@ Import a MCP server from a file. - The server object (or result of calling a factory function) -### `run_with_uv` +### `run_with_uv` ```python run_with_uv(server_spec: str, python_version: str | None = None, with_packages: list[str] | None = None, with_requirements: Path | None = None, project: Path | None = None, transport: TransportType | None = None, host: str | None = None, port: int | None = None, path: str | None = None, log_level: LogLevelType | None = None, show_banner: bool = True) -> None @@ -76,7 +76,7 @@ Run a MCP server using uv run subprocess. - `show_banner`: Whether to show the server banner -### `create_client_server` +### `create_client_server` ```python create_client_server(url: str) -> Any @@ -92,7 +92,7 @@ Create a FastMCP server from a client URL. - A FastMCP server instance -### `create_mcp_config_server` +### `create_mcp_config_server` ```python create_mcp_config_server(mcp_config_path: Path) -> FastMCP[None] @@ -102,7 +102,7 @@ create_mcp_config_server(mcp_config_path: Path) -> FastMCP[None] Create a FastMCP server from a MCPConfig. -### `import_server_with_args` +### `import_server_with_args` ```python import_server_with_args(file: Path, server_or_factory: str | None = None, server_args: list[str] | None = None) -> Any @@ -120,7 +120,7 @@ Import a server with optional command line arguments. - The imported server object -### `run_command` +### `run_command` ```python run_command(server_spec: str, transport: TransportType | None = None, host: str | None = None, port: int | None = None, path: str | None = None, log_level: LogLevelType | None = None, server_args: list[str] | None = None, show_banner: bool = True, use_direct_import: bool = False) -> None @@ -141,7 +141,7 @@ Run a MCP server or connect to a remote one. - `use_direct_import`: Whether to use direct import instead of subprocess -### `run_v1_server` +### `run_v1_server` ```python run_v1_server(server: FastMCP1x, host: str | None = None, port: int | None = None, transport: TransportType | None = None) -> None diff --git a/docs/python-sdk/fastmcp-server-auth-auth.mdx b/docs/python-sdk/fastmcp-server-auth-auth.mdx index fcc2a5667..b576b6515 100644 --- a/docs/python-sdk/fastmcp-server-auth-auth.mdx +++ b/docs/python-sdk/fastmcp-server-auth-auth.mdx @@ -107,7 +107,7 @@ the authorization servers that issue valid tokens. **Methods:** -#### `verify_token` +#### `verify_token` ```python verify_token(self, token: str) -> AccessToken | None @@ -116,7 +116,7 @@ verify_token(self, token: str) -> AccessToken | None Verify token using the configured token verifier. -#### `get_routes` +#### `get_routes` ```python get_routes(self) -> list[Route] @@ -129,7 +129,7 @@ Subclasses can override this method to add additional routes by calling super().get_routes() and extending the returned list. -### `OAuthProvider` +### `OAuthProvider` OAuth Authorization Server provider. @@ -140,7 +140,7 @@ authorization flows, token issuance, and token verification. **Methods:** -#### `verify_token` +#### `verify_token` ```python verify_token(self, token: str) -> AccessToken | None @@ -158,7 +158,7 @@ to our existing load_access_token method. - AccessToken object if valid, None if invalid or expired -#### `get_routes` +#### `get_routes` ```python get_routes(self) -> list[Route] diff --git a/docs/python-sdk/fastmcp-server-context.mdx b/docs/python-sdk/fastmcp-server-context.mdx index cc3c79237..fc6219fef 100644 --- a/docs/python-sdk/fastmcp-server-context.mdx +++ b/docs/python-sdk/fastmcp-server-context.mdx @@ -111,7 +111,7 @@ Read a resource by URI. - The resource content as either text or bytes -#### `log` +#### `log` ```python log(self, message: str, level: LoggingLevel | None = None, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None @@ -127,7 +127,7 @@ Send a log message to the client. - `extra`: Optional mapping for additional arguments -#### `client_id` +#### `client_id` ```python client_id(self) -> str | None @@ -136,7 +136,7 @@ client_id(self) -> str | None Get the client ID if available. -#### `request_id` +#### `request_id` ```python request_id(self) -> str @@ -145,7 +145,7 @@ request_id(self) -> str Get the unique ID for this request. -#### `session_id` +#### `session_id` ```python session_id(self) -> str @@ -162,7 +162,7 @@ the same client session. - for other transports. -#### `session` +#### `session` ```python session(self) -> ServerSession @@ -171,7 +171,7 @@ session(self) -> ServerSession Access to the underlying session for advanced usage. -#### `debug` +#### `debug` ```python debug(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None @@ -180,7 +180,7 @@ debug(self, message: str, logger_name: str | None = None, extra: Mapping[str, An Send a debug log message. -#### `info` +#### `info` ```python info(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None @@ -189,7 +189,7 @@ info(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any Send an info log message. -#### `warning` +#### `warning` ```python warning(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None @@ -198,7 +198,7 @@ warning(self, message: str, logger_name: str | None = None, extra: Mapping[str, Send a warning log message. -#### `error` +#### `error` ```python error(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None @@ -207,7 +207,7 @@ error(self, message: str, logger_name: str | None = None, extra: Mapping[str, An Send an error log message. -#### `list_roots` +#### `list_roots` ```python list_roots(self) -> list[Root] @@ -216,7 +216,7 @@ list_roots(self) -> list[Root] List the roots available to the server, as indicated by the client. -#### `send_tool_list_changed` +#### `send_tool_list_changed` ```python send_tool_list_changed(self) -> None @@ -225,7 +225,7 @@ send_tool_list_changed(self) -> None Send a tool list changed notification to the client. -#### `send_resource_list_changed` +#### `send_resource_list_changed` ```python send_resource_list_changed(self) -> None @@ -234,7 +234,7 @@ send_resource_list_changed(self) -> None Send a resource list changed notification to the client. -#### `send_prompt_list_changed` +#### `send_prompt_list_changed` ```python send_prompt_list_changed(self) -> None @@ -243,7 +243,7 @@ send_prompt_list_changed(self) -> None Send a prompt list changed notification to the client. -#### `sample` +#### `sample` ```python sample(self, messages: str | list[str | SamplingMessage], system_prompt: str | None = None, include_context: IncludeContext | None = None, temperature: float | None = None, max_tokens: int | None = None, model_preferences: ModelPreferences | str | list[str] | None = None) -> ContentBlock @@ -256,25 +256,25 @@ completion from the client. The client must be appropriately configured, or the request will error. -#### `elicit` +#### `elicit` ```python elicit(self, message: str, response_type: None) -> AcceptedElicitation[dict[str, Any]] | DeclinedElicitation | CancelledElicitation ``` -#### `elicit` +#### `elicit` ```python elicit(self, message: str, response_type: type[T]) -> AcceptedElicitation[T] | DeclinedElicitation | CancelledElicitation ``` -#### `elicit` +#### `elicit` ```python elicit(self, message: str, response_type: list[str]) -> AcceptedElicitation[str] | DeclinedElicitation | CancelledElicitation ``` -#### `elicit` +#### `elicit` ```python elicit(self, message: str, response_type: type[T] | list[str] | None = None) -> AcceptedElicitation[T] | AcceptedElicitation[dict[str, Any]] | AcceptedElicitation[str] | DeclinedElicitation | CancelledElicitation @@ -303,7 +303,7 @@ type or dataclass or BaseModel. If it is a primitive type, an object schema with a single "value" field will be generated. -#### `get_http_request` +#### `get_http_request` ```python get_http_request(self) -> Request @@ -312,7 +312,7 @@ get_http_request(self) -> Request Get the active starlette request. -#### `set_state` +#### `set_state` ```python set_state(self, key: str, value: Any) -> None @@ -321,7 +321,7 @@ set_state(self, key: str, value: Any) -> None Set a value in the context state. -#### `get_state` +#### `get_state` ```python get_state(self, key: str) -> Any diff --git a/docs/python-sdk/fastmcp-utilities-components.mdx b/docs/python-sdk/fastmcp-utilities-components.mdx index e18484bec..c7494f52f 100644 --- a/docs/python-sdk/fastmcp-utilities-components.mdx +++ b/docs/python-sdk/fastmcp-utilities-components.mdx @@ -55,7 +55,7 @@ Create a copy of the component. - `key`: The key to use for the copy. -#### `enable` +#### `enable` ```python enable(self) -> None @@ -64,7 +64,7 @@ enable(self) -> None Enable the component. -#### `disable` +#### `disable` ```python disable(self) -> None @@ -73,7 +73,7 @@ disable(self) -> None Disable the component. -#### `copy` +#### `copy` ```python copy(self) -> Self @@ -82,7 +82,7 @@ copy(self) -> Self Create a copy of the component. -### `MirroredComponent` +### `MirroredComponent` Base class for components that are mirrored from a remote server. @@ -93,7 +93,7 @@ to create a local version you can modify. **Methods:** -#### `enable` +#### `enable` ```python enable(self) -> None @@ -102,7 +102,7 @@ enable(self) -> None Enable the component. -#### `disable` +#### `disable` ```python disable(self) -> None @@ -111,7 +111,7 @@ disable(self) -> None Disable the component. -#### `copy` +#### `copy` ```python copy(self) -> Self diff --git a/docs/servers/server.mdx b/docs/servers/server.mdx index 971119e6d..0d78272eb 100644 --- a/docs/servers/server.mdx +++ b/docs/servers/server.mdx @@ -215,6 +215,31 @@ The server can also be run using the FastMCP CLI. 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. +## Custom Routes + +When running your server with HTTP transport, you can add custom web routes alongside your MCP endpoint using the `@custom_route` decorator. This is useful for simple endpoints like health checks that need to be served alongside your MCP server: + +```python +from fastmcp import FastMCP +from starlette.requests import Request +from starlette.responses import PlainTextResponse + +mcp = FastMCP("MyServer") + +@mcp.custom_route("/health", methods=["GET"]) +async def health_check(request: Request) -> PlainTextResponse: + return PlainTextResponse("OK") + +if __name__ == "__main__": + mcp.run(transport="http") # Health check at http://localhost:8000/health +``` + +Custom routes are served alongside your MCP endpoint and are useful for: +- Health check endpoints for monitoring +- Simple status or info endpoints +- Basic webhooks or callbacks + +For more complex web applications, consider [mounting your MCP server into a FastAPI or Starlette app](/deployment/self-hosted#integration-with-web-frameworks). ## Composing Servers diff --git a/src/fastmcp/cli/run.py b/src/fastmcp/cli/run.py index 9a0a47971..900110021 100644 --- a/src/fastmcp/cli/run.py +++ b/src/fastmcp/cli/run.py @@ -80,9 +80,6 @@ async def import_server(file: Path, server_or_factory: str | None = None) -> Any logger.error("Could not load module", extra={"file": str(file)}) sys.exit(1) - assert spec is not None - assert spec.loader is not None - module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) @@ -102,8 +99,6 @@ async def import_server(file: Path, server_or_factory: str | None = None) -> Any ) sys.exit(1) - assert server_or_factory is not None - # Handle module:object syntax if ":" in server_or_factory: module_name, object_name = server_or_factory.split(":", 1) diff --git a/src/fastmcp/experimental/server/openapi/routing.py b/src/fastmcp/experimental/server/openapi/routing.py index ca7ebdb94..092b2445b 100644 --- a/src/fastmcp/experimental/server/openapi/routing.py +++ b/src/fastmcp/experimental/server/openapi/routing.py @@ -110,8 +110,6 @@ def _determine_route_type( # Tags don't match, continue to next mapping continue - # We know mcp_type is not None here due to post_init validation - assert route_map.mcp_type is not None logger.debug( f"Route {route.method} {route.path} mapped to {route_map.mcp_type.name}" ) diff --git a/src/fastmcp/experimental/server/openapi/server.py b/src/fastmcp/experimental/server/openapi/server.py index 101427375..9df9b046f 100644 --- a/src/fastmcp/experimental/server/openapi/server.py +++ b/src/fastmcp/experimental/server/openapi/server.py @@ -163,8 +163,6 @@ class FastMCPOpenAPI(FastMCP): # Determine route type based on mappings or default rules route_map = _determine_route_type(route, route_maps) - # TODO: remove this once RouteType is removed and mcp_type is typed as MCPType without | None - assert route_map.mcp_type is not None route_type = route_map.mcp_type # Call route_map_fn if provided diff --git a/src/fastmcp/server/auth/auth.py b/src/fastmcp/server/auth/auth.py index 2dd316793..3c2f3f59d 100644 --- a/src/fastmcp/server/auth/auth.py +++ b/src/fastmcp/server/auth/auth.py @@ -135,6 +135,8 @@ class RemoteAuthProvider(AuthProvider): the authorization servers that issue valid tokens. """ + resource_server_url: AnyHttpUrl + def __init__( self, token_verifier: TokenVerifier, @@ -169,7 +171,6 @@ class RemoteAuthProvider(AuthProvider): Subclasses can override this method to add additional routes by calling super().get_routes() and extending the returned list. """ - assert self.resource_server_url is not None return create_protected_resource_routes( resource_url=self.resource_server_url, diff --git a/src/fastmcp/server/context.py b/src/fastmcp/server/context.py index d5f238206..7d7c2f48e 100644 --- a/src/fastmcp/server/context.py +++ b/src/fastmcp/server/context.py @@ -197,7 +197,8 @@ class Context: Returns: The resource content as either text or bytes """ - assert self.fastmcp is not None, "Context is not available outside of a request" + if self.fastmcp is None: + raise ValueError("Context is not available outside of a request") return await self.fastmcp._mcp_read_resource(uri) async def log( diff --git a/src/fastmcp/utilities/components.py b/src/fastmcp/utilities/components.py index 8306b1e67..a0c4468ac 100644 --- a/src/fastmcp/utilities/components.py +++ b/src/fastmcp/utilities/components.py @@ -117,7 +117,8 @@ class FastMCPComponent(FastMCPBaseModel): def __eq__(self, other: object) -> bool: if type(self) is not type(other): return False - assert isinstance(other, type(self)) + if not isinstance(other, type(self)): + return False return self.model_dump() == other.model_dump() def __repr__(self) -> str: diff --git a/src/fastmcp/utilities/json_schema_type.py b/src/fastmcp/utilities/json_schema_type.py index 62577402b..b6ba9266a 100644 --- a/src/fastmcp/utilities/json_schema_type.py +++ b/src/fastmcp/utilities/json_schema_type.py @@ -449,7 +449,8 @@ def _create_pydantic_model( ) -> type: """Create Pydantic BaseModel from object schema with additionalProperties.""" name = name or schema.get("title", "Root") - assert name is not None # Should not be None after the or operation + if name is None: + raise ValueError("Name is required") sanitized_name = _sanitize_name(name) schema_hash = _hash_schema(schema) cache_key = (schema_hash, sanitized_name) @@ -507,7 +508,8 @@ def _create_dataclass( """Create dataclass from object schema.""" name = name or schema.get("title", "Root") # Sanitize name for class creation - assert name is not None # Should not be None after the or operation + if name is None: + raise ValueError("Name is required") sanitized_name = _sanitize_name(name) schema_hash = _hash_schema(schema) cache_key = (schema_hash, sanitized_name)