diff --git a/LICENSE b/LICENSE
new file mode 100644
index 000000000..261eeb9e9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
index fa9f48e22..83d50a97c 100644
--- a/README.md
+++ b/README.md
@@ -1,187 +1,362 @@
-# FastMCP
+
+# FastMCP
-> **Note**: This is experimental software. The Model Context Protocol itself is only a few days old and the specification is still evolving.
+
-A fast, pythonic way to build Model Context Protocol (MCP) servers.
+[](https://pypi.org/project/fastmcp)
+[](https://github.com/jlowin/fastmcp/actions/workflows/run-tests.yml)
+[](https://github.com/jlowin/fastmcp/blob/main/LICENSE)
-Anthropic's new [Model Context Protocol](https://modelcontextprotocol.io) is a powerful way to give broadcast new functionality and context to LLMs. However, developing MCP servers can be cumbersome. FastMCP provides a simple, intuitive interface for creating MCP servers in Python.
+
+FastMCP is a high-level, intuitive framework for building [Model Context Protocol (MCP)](https://modelcontextprotocol.io) servers with Python. While MCP is a powerful protocol that enables LLMs to interact with local data and tools in a secure, standardized way, the specification can be cumbersome to implement directly. FastMCP lets you build fully compliant MCP servers in the most Pythonic way possible - in many cases, simply decorating a function is all that's required.
+
+🚧 *Note: FastMCP is under active development, as is the low-level MCP Python SDK* 🏗️
+
+Key features:
+* **Intuitive**: Designed to feel familiar to Python developers, with powerful type hints and editor support
+* **Simple**: Build compliant MCP servers with minimal boilerplate
+* **Fast**: High-performance async implementation
+* **Full-featured**: Complete implementation of the MCP specification
+
+
## Table of Contents
-- [FastMCP](#fastmcp)
- - [Table of Contents](#table-of-contents)
- - [Installation](#installation)
- - [Quick Start](#quick-start)
- - [Core Concepts](#core-concepts)
- - [Resources](#resources)
- - [Tools](#tools)
+- [Installation](#installation)
+- [Quickstart](#quickstart)
+- [What is MCP?](#what-is-mcp)
+- [Core Concepts](#core-concepts)
+ - [Server](#server)
+ - [Resources](#resources)
+ - [Tools](#tools)
+ - [Prompts](#prompts)
+ - [Images](#images)
+ - [Context](#context)
+- [Deployment](#deployment)
- [Development](#development)
- - [Running the Dev Inspector](#running-the-dev-inspector)
- - [Installing in Claude](#installing-in-claude)
- - [License](#license)
+ - [Claude Desktop](#claude-desktop)
+- [Examples](#examples)
+ - [Echo Server](#echo-server)
+ - [SQLite Explorer](#sqlite-explorer)
## Installation
-MCP servers require you to use [uv](https://github.com/astral-sh/uv) as your dependency manager.
-
-Install uv with brew:
-```bash
-brew install uv
-```
-*(Editor's note: I was unable to get MCP servers working unless uv was installed with brew.)*
-
-Install FastMCP:
```bash
+# We strongly recommend installing with uv
+brew install uv # on macOS
uv pip install fastmcp
```
-## Quick Start
+Or with pip:
+```bash
+pip install fastmcp
+```
-Here's a simple example that exposes your desktop directory as a resource and provides a basic addition tool:
+## Quickstart
+
+Let's create a simple MCP server that exposes a calculator tool and some data:
```python
-from pathlib import Path
from fastmcp import FastMCP
-# Create server
+
+# Create an MCP server
mcp = FastMCP("Demo")
-@mcp.resource("dir://desktop")
-def desktop() -> list[str]:
- """List the files in the user's desktop"""
- desktop = Path.home() / "Desktop"
- return [str(f) for f in desktop.iterdir()]
+# Add an addition tool
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
-if __name__ == "__main__":
- mcp.run()
+
+# Add a dynamic greeting resource
+@mcp.resource("greeting://{name}")
+def get_greeting(name: str) -> str:
+ """Get a personalized greeting"""
+ return f"Hello, {name}!"
```
+To use this server, you have two options:
+
+1. Install it in Claude Desktop:
+```bash
+fastmcp install server.py
+```
+
+2. Test it with the MCP Inspector:
+```bash
+fastmcp dev server.py
+```
+
+
+
+## What is MCP?
+
+The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. Think of it like a web API, but specifically designed for LLM interactions. MCP servers can:
+
+- Expose data through **Resources** (like GET endpoints)
+- Provide functionality through **Tools** (like POST endpoints)
+- Define interaction patterns through **Prompts** (reusable templates for LLM interactions)
+
## Core Concepts
-FastMCP makes it easy to expose two types of functionality to LLMs: Resources and Tools.
+*Note: All code examples below assume you've created a FastMCP server instance called `mcp`.*
+
+### Server
+
+The FastMCP server is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing:
+
+```python
+from fastmcp import FastMCP
+
+# Create a named server
+mcp = FastMCP("My App")
+
+# Configure host/port for HTTP transport (optional)
+mcp = FastMCP("My App", host="localhost", port=8000)
+```
### Resources
-Resources are data sources that can be accessed by the LLM. They're perfect for providing context like files, API responses, or database queries.
+Resources are how you expose data to LLMs. They're similar to GET endpoints in a REST API - they provide data but shouldn't perform significant computation or have side effects. Some examples:
-FastMCP provides a simple `@resource` decorator that handles both static and dynamic resources. While the MCP spec distinguishes between resources and templates, FastMCP automatically handles this distinction based on your function signature:
+- File contents
+- Database schemas
+- API responses
+- System information
+Resources can be static:
```python
-# Static resource
-@mcp.resource("resource://static")
-def get_static() -> str:
- """Return static content"""
- return "Static content"
-
-# Dynamic resource
-@mcp.resource("resource://{city}/weather")
-def get_weather(city: str) -> str:
- """Get weather for a city"""
- return f"Weather for {city}"
-
-# Multiple parameters are supported
-@mcp.resource("db://users/{user_id}/posts/{post_id}")
-def get_user_post(user_id: int, post_id: int) -> dict:
- """Get a specific post by a user"""
- return {
- "user_id": user_id,
- "post_id": post_id,
- "content": "Post content..."
- }
-
-# File resources
-@mcp.resource("file://config.json")
+@mcp.resource("config://app")
def get_config() -> str:
- """Read the config file"""
- return Path("config.json").read_text()
+ """Static configuration data"""
+ return "App configuration here"
```
-Resources can return:
-- Strings for text content
-- Bytes for binary content
-- Other types will be converted to JSON
-
-When your resource URI includes parameters in curly braces (like `{city}`) and your function accepts matching arguments, FastMCP automatically sets up a template resource behind the scenes. This means you don't need to worry about the distinction between resources and templates in the MCP spec - just write your function, and FastMCP handles the rest.
-
-> **Note**: If you're familiar with the MCP spec, you might notice that dynamic resources are implemented as templates under the hood. FastMCP simplifies this by providing a unified interface through the `@resource` decorator. This is similar to how web frameworks often unify GET and POST handlers under a single route decorator.
-
+Or dynamic with parameters (FastMCP automatically handles these as MCP templates):
+```python
+@mcp.resource("users://{user_id}/profile")
+def get_user_profile(user_id: str) -> str:
+ """Dynamic user data"""
+ return f"Profile data for user {user_id}"
+```
### Tools
-Tools are functions that can be called by the LLM to perform actions. They're great for calculations, API calls, or any interactive functionality. Tools are defined using the `@tool` decorator:
+Tools let LLMs take actions through your server. Unlike resources, tools are expected to perform computation and have side effects. They're similar to POST endpoints in a REST API.
+Simple calculation example:
```python
@mcp.tool()
-def search_docs(query: str, max_results: int = 5) -> list[dict]:
- """Search documentation for relevant entries"""
- results = perform_search(query, limit=max_results)
- return [{"title": r.title, "excerpt": r.excerpt} for r in results]
+def calculate_bmi(weight_kg: float, height_m: float) -> float:
+ """Calculate BMI given weight in kg and height in meters"""
+ return weight_kg / (height_m ** 2)
+```
+
+HTTP request example:
+```python
+import httpx
@mcp.tool()
-def analyze_image(image_path: str) -> dict:
- """Analyze an image and return metadata"""
- from PIL import Image
- img = Image.open(image_path)
- return {
- "size": img.size,
- "mode": img.mode,
- "format": img.format
- }
+async def fetch_weather(city: str) -> str:
+ """Fetch current weather for a city"""
+ async with httpx.AsyncClient() as client:
+ response = await client.get(
+ f"https://api.weather.com/{city}"
+ )
+ return response.text
```
-Tools support:
-- Type hints for parameters
-- Default values
-- Async functions
-- Return value conversion to JSON
+### Prompts
-## Development
+Prompts are reusable templates that help LLMs interact with your server effectively. They're like "best practices" encoded into your server. A prompt can be as simple as a string:
-FastMCP includes developer tools to make testing and debugging easier.
+```python
+@mcp.prompt()
+def review_code(code: str) -> str:
+ return f"Please review this code:\n\n{code}"
+```
-### Running the Dev Inspector
+Or a more structured sequence of messages:
+```python
+from fastmcp.prompts.base import UserMessage, AssistantMessage
-The MCP Inspector helps you test your server during development:
+@mcp.prompt()
+def debug_error(error: str) -> list[Message]:
+ return [
+ UserMessage("I'm seeing this error:"),
+ UserMessage(error),
+ AssistantMessage("I'll help debug that. What have you tried so far?")
+ ]
+```
+
+
+### Images
+
+FastMCP provides an `Image` class that automatically handles image data in your server:
+
+```python
+from fastmcp import FastMCP, Image
+from PIL import Image as PILImage
+
+@mcp.tool()
+def create_thumbnail(image_path: str) -> Image:
+ """Create a thumbnail from an image"""
+ img = PILImage.open(image_path)
+ img.thumbnail((100, 100))
+
+ # FastMCP automatically handles conversion and MIME types
+ return Image(data=img.tobytes(), format="png")
+
+@mcp.tool()
+def load_image(path: str) -> Image:
+ """Load an image from disk"""
+ # FastMCP handles reading and format detection
+ return Image(path=path)
+```
+
+Images can be used as the result of both tools and resources.
+
+### Context
+
+The Context object gives your tools and resources access to MCP capabilities. To use it, add a parameter annotated with `fastmcp.Context`:
+
+```python
+from fastmcp import FastMCP, Context
+
+@mcp.tool()
+async def long_task(files: list[str], ctx: Context) -> str:
+ """Process multiple files with progress tracking"""
+ for i, file in enumerate(files):
+ ctx.info(f"Processing {file}")
+ await ctx.report_progress(i, len(files))
+
+ # Read another resource if needed
+ data = await ctx.read_resource(f"file://{file}")
+
+ return "Processing complete"
+```
+
+The Context object provides:
+- Progress reporting through `report_progress()`
+- Logging via `debug()`, `info()`, `warning()`, and `error()`
+- Resource access through `read_resource()`
+- Request metadata via `request_id` and `client_id`
+
+## Deployment
+
+The FastMCP CLI helps you develop and deploy MCP servers.
+
+Note that for all deployment commands, you are expected to provide the fully qualified path to your server object. For example, if you have a file `server.py` that contains a FastMCP server named `my_server`, you would provide `path/to/server.py:my_server`.
+
+If your server variable has one of the standard names (`mcp`, `server`, or `app`), you can omit the server name from the path and just provide the file: `path/to/server.py`.
+
+### Development
+
+Test and debug your server with the MCP Inspector:
+```bash
+# Provide the fully qualified path to your server
+fastmcp dev server.py:my_mcp_server
+
+# Or just the file if your server is named 'mcp', 'server', or 'app'
+fastmcp dev server.py
+```
+
+Your server is run in an isolated environment, so you'll need to indicate any dependencies with the `--with` flag. FastMCP is automatically included. If you are working on a uv project, you can use the `--with-editable` flag to mount your current directory:
```bash
-# Basic usage
-fastmcp dev your_server.py
+# With additional packages
+fastmcp dev server.py --with pandas --with numpy
-# Install package in editable mode from current directory
-fastmcp dev your_server.py --with-editable .
-
-# Install additional packages
-fastmcp dev your_server.py --with pandas --with numpy
-
-# Combine both
-fastmcp dev your_server.py --with-editable . --with pandas --with numpy
+# Using your project's dependencies and up-to-date code
+fastmcp dev server.py --with-editable .
```
-The `--with` flag automatically includes `fastmcp` and any additional packages you specify. The `--with-editable` flag installs the package from the specified directory in editable mode, which is useful during development.
-
-### Installing in Claude
-
-To use your server with Claude Desktop:
+### Claude Desktop
+Install your server in Claude Desktop:
```bash
-# Basic usage
-fastmcp install your_server.py --name "My Server"
+# Basic usage (name is taken from your FastMCP instance)
+fastmcp install server.py
-# Install package in editable mode
-fastmcp install your_server.py --with-editable .
+# With a custom name
+fastmcp install server.py --name "My Server"
-# Install additional packages
-fastmcp install your_server.py --with pandas --with numpy
+# With dependencies
+fastmcp install server.py --with pandas --with numpy
-# Combine options
-fastmcp install your_server.py --with-editable . --with pandas --with numpy
+# Replace an existing server
+fastmcp install server.py --force
```
-## License
+The server name in Claude will be:
+1. The `--name` parameter if provided
+2. The `name` from your FastMCP instance
+3. The filename if the server can't be imported
-Apache 2.0
\ No newline at end of file
+## Examples
+
+### Echo Server
+A simple server demonstrating resources, tools, and prompts:
+
+```python
+from fastmcp import FastMCP
+
+mcp = FastMCP("Echo")
+
+@mcp.resource("echo://{message}")
+def echo_resource(message: str) -> str:
+ """Echo a message as a resource"""
+ return f"Resource echo: {message}"
+
+@mcp.tool()
+def echo_tool(message: str) -> str:
+ """Echo a message as a tool"""
+ return f"Tool echo: {message}"
+
+@mcp.prompt()
+def echo_prompt(message: str) -> str:
+ """Create an echo prompt"""
+ return f"Please process this message: {message}"
+```
+
+### SQLite Explorer
+A more complex example showing database integration:
+
+```python
+from fastmcp import FastMCP
+import sqlite3
+
+mcp = FastMCP("SQLite Explorer")
+
+@mcp.resource("schema://main")
+def get_schema() -> str:
+ """Provide the database schema as a resource"""
+ conn = sqlite3.connect("database.db")
+ schema = conn.execute(
+ "SELECT sql FROM sqlite_master WHERE type='table'"
+ ).fetchall()
+ return "\n".join(sql[0] for sql in schema if sql[0])
+
+@mcp.tool()
+def query_data(sql: str) -> str:
+ """Execute SQL queries safely"""
+ conn = sqlite3.connect("database.db")
+ try:
+ result = conn.execute(sql).fetchall()
+ return "\n".join(str(row) for row in result)
+ except Exception as e:
+ return f"Error: {str(e)}"
+
+@mcp.prompt()
+def analyze_table(table: str) -> str:
+ """Create a prompt template for analyzing tables"""
+ return f"""Please analyze this database table:
+Table: {table}
+Schema:
+{get_schema()}
+
+What insights can you provide about the structure and relationships?"""
+```
\ No newline at end of file
diff --git a/docs/assets/demo-inspector.png b/docs/assets/demo-inspector.png
new file mode 100644
index 000000000..5d7715387
Binary files /dev/null and b/docs/assets/demo-inspector.png differ
diff --git a/examples/desktop.py b/examples/desktop.py
index 7c708ce9b..a9b46cc8a 100644
--- a/examples/desktop.py
+++ b/examples/desktop.py
@@ -23,7 +23,3 @@ def desktop() -> list[str]:
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
-
-
-if __name__ == "__main__":
- mcp.run()
diff --git a/examples/echo.py b/examples/echo.py
index bd940807c..48c0883a5 100644
--- a/examples/echo.py
+++ b/examples/echo.py
@@ -14,8 +14,13 @@ def echo_tool(text: str) -> str:
return text
+@mcp.resource("echo://static")
+def echo_resource() -> str:
+ return "Echo!"
+
+
@mcp.resource("echo://{text}")
-def echo_resource(text: str) -> str:
+def echo_template(text: str) -> str:
"""Echo the input text"""
return f"Echo: {text}"
@@ -23,7 +28,3 @@ def echo_resource(text: str) -> str:
@mcp.prompt("echo")
def echo_prompt(text: str) -> str:
return text
-
-
-if __name__ == "__main__":
- mcp.run()
diff --git a/examples/readme-quickstart.py b/examples/readme-quickstart.py
new file mode 100644
index 000000000..26a0cc138
--- /dev/null
+++ b/examples/readme-quickstart.py
@@ -0,0 +1,19 @@
+from fastmcp import FastMCP
+
+
+# Create an MCP server
+mcp = FastMCP("Demo")
+
+
+# Add an addition tool
+@mcp.tool()
+def add(a: int, b: int) -> int:
+ """Add two numbers"""
+ return a + b
+
+
+# Add a dynamic greeting resource
+@mcp.resource("greeting://{name}")
+def get_greeting(name: str) -> str:
+ """Get a personalized greeting"""
+ return f"Hello, {name}!"
diff --git a/examples/screenshot.py b/examples/screenshot.py
index bfef5d8e9..012a253f1 100644
--- a/examples/screenshot.py
+++ b/examples/screenshot.py
@@ -26,7 +26,3 @@ def take_screenshot() -> Image:
# if the file exceeds ~1MB, it will be rejected by Claude
screenshot.convert("RGB").save(buffer, format="JPEG", quality=60, optimize=True)
return Image(data=buffer.getvalue(), format="jpeg")
-
-
-if __name__ == "__main__":
- mcp.run()
diff --git a/examples/simple_echo.py b/examples/simple_echo.py
index 2a81ca4c0..99f52c6de 100644
--- a/examples/simple_echo.py
+++ b/examples/simple_echo.py
@@ -13,7 +13,3 @@ mcp = FastMCP("Echo Server")
def echo(text: str) -> str:
"""Echo the input text"""
return text
-
-
-if __name__ == "__main__":
- mcp.run()
diff --git a/src/fastmcp/cli/claude.py b/src/fastmcp/cli/claude.py
index 998733b60..be216a7e6 100644
--- a/src/fastmcp/cli/claude.py
+++ b/src/fastmcp/cli/claude.py
@@ -25,8 +25,8 @@ def get_claude_config_path() -> Path | None:
def update_claude_config(
- file: Path,
- server_name: Optional[str] = None,
+ file_spec: str,
+ server_name: str,
*,
with_editable: Optional[Path] = None,
with_packages: Optional[list[str]] = None,
@@ -35,9 +35,8 @@ def update_claude_config(
"""Add the MCP server to Claude's configuration.
Args:
- file: Path to the server file
- server_name: Optional custom name for the server. If not provided,
- defaults to the file stem
+ file_spec: Path to the server file, optionally with :object suffix
+ server_name: Name for the server in Claude's config
with_editable: Optional directory to install in editable mode
with_packages: Optional list of additional packages to install
force: If True, replace existing server with same name
@@ -55,46 +54,49 @@ def update_claude_config(
if "mcpServers" not in config:
config["mcpServers"] = {}
- # Use provided server_name or fall back to file stem
- name = server_name or file.stem
- if name in config["mcpServers"]:
+ if server_name in config["mcpServers"]:
if not force:
logger.warning(
- f"Server '{name}' already exists in Claude config. "
+ f"Server '{server_name}' already exists in Claude config. "
"Use `--force` to replace.",
extra={"config_file": str(config_file)},
)
return False
logger.info(
- f"Replacing existing server '{name}' in Claude config",
+ f"Replacing existing server '{server_name}' in Claude config",
extra={"config_file": str(config_file)},
)
# Build uv run command
- args = ["run"]
+ args = ["run", "--with", "fastmcp"]
if with_editable:
args.extend(["--with-editable", str(with_editable)])
- # Always include fastmcp
- args.extend(["--with", "fastmcp"])
-
- # Add additional packages
if with_packages:
for pkg in with_packages:
if pkg:
args.extend(["--with", pkg])
- args.append(str(file))
+ # Convert file path to absolute before adding to command
+ # Split off any :object suffix first
+ if ":" in file_spec:
+ file_path, server_object = file_spec.rsplit(":", 1)
+ file_spec = f"{Path(file_path).resolve()}:{server_object}"
+ else:
+ file_spec = str(Path(file_spec).resolve())
- config["mcpServers"][name] = {
+ # Add fastmcp run command
+ args.extend(["fastmcp", "run", file_spec])
+
+ config["mcpServers"][server_name] = {
"command": "uv",
"args": args,
}
config_file.write_text(json.dumps(config, indent=2))
logger.info(
- f"Added server '{name}' to Claude config",
+ f"Added server '{server_name}' to Claude config",
extra={"config_file": str(config_file)},
)
return True
diff --git a/src/fastmcp/cli/cli.py b/src/fastmcp/cli/cli.py
index 42e63251b..cab03aab1 100644
--- a/src/fastmcp/cli/cli.py
+++ b/src/fastmcp/cli/cli.py
@@ -24,11 +24,11 @@ app = typer.Typer(
def _build_uv_command(
- file: Path,
+ file_spec: str,
with_editable: Optional[Path] = None,
with_packages: Optional[list[str]] = None,
) -> list[str]:
- """Build the uv run command."""
+ """Build the uv run command that runs a FastMCP server through fastmcp run."""
cmd = ["uv"]
cmd.extend(["run", "--with", "fastmcp"])
@@ -41,7 +41,8 @@ def _build_uv_command(
if pkg:
cmd.extend(["--with", pkg])
- cmd.append(str(file))
+ # Add fastmcp run command
+ cmd.extend(["fastmcp", "run", file_spec])
return cmd
@@ -89,7 +90,7 @@ def _import_server(file: Path, server_object: Optional[str] = None):
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
- # If no object specified, try __main__ block
+ # If no object specified, try common server names
if not server_object:
# Look for the most common server object names
for name in ["mcp", "server", "app"]:
@@ -97,7 +98,9 @@ def _import_server(file: Path, server_object: Optional[str] = None):
return getattr(module, name)
logger.error(
- f"No server object found in {file}. Please specify the object name with file:object syntax.",
+ f"No server object found in {file}. Please either:\n"
+ "1. Use a standard variable name (mcp, server, or app)\n"
+ "2. Specify the object name with file:object syntax",
extra={"file": str(file)},
)
sys.exit(1)
@@ -178,7 +181,7 @@ def dev(
)
try:
- uv_cmd = _build_uv_command(file, with_editable, with_packages)
+ uv_cmd = _build_uv_command(file_spec, with_editable, with_packages)
# Run the MCP Inspector command
process = subprocess.run(
["npx", "@modelcontextprotocol/inspector"] + uv_cmd,
@@ -229,7 +232,12 @@ def run(
),
] = None,
) -> None:
- """Run a FastMCP server."""
+ """Run a FastMCP server.
+
+ The server can be specified in two ways:
+ 1. Module approach: server.py - runs the module directly, expecting a server.run() call
+ 2. Import approach: server.py:app - imports and runs the specified server object
+ """
file, server_object = _parse_file_path(file_spec)
logger.debug(
@@ -338,7 +346,7 @@ def install(
name = file.stem
if claude.update_claude_config(
- file,
+ file_spec,
name,
with_editable=with_editable,
with_packages=with_packages,
@@ -348,7 +356,3 @@ def install(
else:
print(f"Failed to install {name} in Claude app")
sys.exit(1)
-
-
-if __name__ == "__main__":
- app()
diff --git a/src/fastmcp/resources/base.py b/src/fastmcp/resources/base.py
index c3e55d22c..bde9b3820 100644
--- a/src/fastmcp/resources/base.py
+++ b/src/fastmcp/resources/base.py
@@ -53,13 +53,8 @@ class Resource(BaseModel, abc.ABC):
"""Set default name from URI if not provided."""
if name:
return name
- # Extract everything after the protocol (e.g., "desktop" from "resource://desktop")
if uri := info.data.get("uri"):
- uri_str = str(uri)
- if "://" in uri_str:
- name = uri_str.split("://", 1)[1]
- if name:
- return name
+ return str(uri)
raise ValueError("Either name or uri must be provided")
@abc.abstractmethod
diff --git a/tests/resources/test_resources.py b/tests/resources/test_resources.py
index 417e79960..0dc5a1b49 100644
--- a/tests/resources/test_resources.py
+++ b/tests/resources/test_resources.py
@@ -46,7 +46,7 @@ class TestResourceValidation:
uri="resource://my-resource",
fn=dummy_func,
)
- assert resource.name == "my-resource"
+ assert resource.name == "resource://my-resource"
def test_resource_name_validation(self):
"""Test name validation."""