Jeremiah Lowin commited on
Commit
27ecafc
·
2 Parent(s): f99530e1c000c2

Merge branch 'main' into updates

Browse files
LICENSE ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
README.md CHANGED
@@ -1,187 +1,362 @@
1
- # FastMCP
 
2
 
3
- > **Note**: This is experimental software. The Model Context Protocol itself is only a few days old and the specification is still evolving.
4
 
5
- A fast, pythonic way to build Model Context Protocol (MCP) servers.
 
 
6
 
7
- 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.
8
 
 
 
 
 
 
 
 
 
 
 
 
9
  ## Table of Contents
10
 
11
- - [FastMCP](#fastmcp)
12
- - [Table of Contents](#table-of-contents)
13
- - [Installation](#installation)
14
- - [Quick Start](#quick-start)
15
- - [Core Concepts](#core-concepts)
16
- - [Resources](#resources)
17
- - [Tools](#tools)
 
 
 
 
18
  - [Development](#development)
19
- - [Running the Dev Inspector](#running-the-dev-inspector)
20
- - [Installing in Claude](#installing-in-claude)
21
- - [License](#license)
 
22
 
23
  ## Installation
24
 
25
- MCP servers require you to use [uv](https://github.com/astral-sh/uv) as your dependency manager.
26
-
27
- Install uv with brew:
28
  ```bash
29
- brew install uv
 
 
30
  ```
31
- *(Editor's note: I was unable to get MCP servers working unless uv was installed with brew.)*
32
 
33
- Install FastMCP:
34
  ```bash
35
- uv pip install fastmcp
36
  ```
37
 
38
- ## Quick Start
39
 
40
- Here's a simple example that exposes your desktop directory as a resource and provides a basic addition tool:
41
 
42
  ```python
43
- from pathlib import Path
44
  from fastmcp import FastMCP
45
 
46
- # Create server
 
47
  mcp = FastMCP("Demo")
48
 
49
- @mcp.resource("dir://desktop")
50
- def desktop() -> list[str]:
51
- """List the files in the user's desktop"""
52
- desktop = Path.home() / "Desktop"
53
- return [str(f) for f in desktop.iterdir()]
54
 
 
55
  @mcp.tool()
56
  def add(a: int, b: int) -> int:
57
  """Add two numbers"""
58
  return a + b
59
 
60
- if __name__ == "__main__":
61
- mcp.run()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  ```
63
 
 
 
 
 
 
 
 
 
 
 
64
  ## Core Concepts
65
 
66
- FastMCP makes it easy to expose two types of functionality to LLMs: Resources and Tools.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
  ### Resources
69
 
70
- Resources are data sources that can be accessed by the LLM. They're perfect for providing context like files, API responses, or database queries.
71
 
72
- 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:
 
 
 
73
 
 
74
  ```python
75
- # Static resource
76
- @mcp.resource("resource://static")
77
- def get_static() -> str:
78
- """Return static content"""
79
- return "Static content"
80
-
81
- # Dynamic resource
82
- @mcp.resource("resource://{city}/weather")
83
- def get_weather(city: str) -> str:
84
- """Get weather for a city"""
85
- return f"Weather for {city}"
86
-
87
- # Multiple parameters are supported
88
- @mcp.resource("db://users/{user_id}/posts/{post_id}")
89
- def get_user_post(user_id: int, post_id: int) -> dict:
90
- """Get a specific post by a user"""
91
- return {
92
- "user_id": user_id,
93
- "post_id": post_id,
94
- "content": "Post content..."
95
- }
96
-
97
- # File resources
98
- @mcp.resource("file://config.json")
99
  def get_config() -> str:
100
- """Read the config file"""
101
- return Path("config.json").read_text()
102
  ```
103
 
104
- Resources can return:
105
- - Strings for text content
106
- - Bytes for binary content
107
- - Other types will be converted to JSON
 
 
 
108
 
109
- 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.
110
 
111
- > **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.
112
 
 
 
 
 
 
 
 
113
 
114
- ### Tools
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
- 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:
117
 
118
  ```python
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  @mcp.tool()
120
- def search_docs(query: str, max_results: int = 5) -> list[dict]:
121
- """Search documentation for relevant entries"""
122
- results = perform_search(query, limit=max_results)
123
- return [{"title": r.title, "excerpt": r.excerpt} for r in results]
 
 
 
124
 
125
  @mcp.tool()
126
- def analyze_image(image_path: str) -> dict:
127
- """Analyze an image and return metadata"""
128
- from PIL import Image
129
- img = Image.open(image_path)
130
- return {
131
- "size": img.size,
132
- "mode": img.mode,
133
- "format": img.format
134
- }
135
  ```
136
 
137
- Tools support:
138
- - Type hints for parameters
139
- - Default values
140
- - Async functions
141
- - Return value conversion to JSON
142
 
143
- ## Development
144
 
145
- FastMCP includes developer tools to make testing and debugging easier.
146
 
147
- ### Running the Dev Inspector
 
148
 
149
- The MCP Inspector helps you test your server during development:
 
 
 
 
 
 
 
 
 
 
 
150
 
151
- ```bash
152
- # Basic usage
153
- fastmcp dev your_server.py
 
 
 
 
154
 
155
- # Install package in editable mode from current directory
156
- fastmcp dev your_server.py --with-editable .
157
 
158
- # Install additional packages
159
- fastmcp dev your_server.py --with pandas --with numpy
160
 
161
- # Combine both
162
- fastmcp dev your_server.py --with-editable . --with pandas --with numpy
 
 
 
 
 
 
 
 
 
163
  ```
164
 
165
- 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.
166
 
167
- ### Installing in Claude
 
 
168
 
169
- To use your server with Claude Desktop:
 
 
 
 
170
 
 
171
  ```bash
172
- # Basic usage
173
- fastmcp install your_server.py --name "My Server"
174
 
175
- # Install package in editable mode
176
- fastmcp install your_server.py --with-editable .
177
 
178
- # Install additional packages
179
- fastmcp install your_server.py --with pandas --with numpy
180
 
181
- # Combine options
182
- fastmcp install your_server.py --with-editable . --with pandas --with numpy
183
  ```
184
 
185
- ## License
 
 
 
 
 
 
 
 
 
 
 
 
 
186
 
187
- Apache 2.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- omit in toc -->
2
+ # FastMCP
3
 
4
+ <div align="center">
5
 
6
+ [![PyPI - Version](https://img.shields.io/pypi/v/fastmcp.svg)](https://pypi.org/project/fastmcp)
7
+ [![Tests](https://github.com/jlowin/fastmcp/actions/workflows/run-tests.yml/badge.svg)](https://github.com/jlowin/fastmcp/actions/workflows/run-tests.yml)
8
+ [![License](https://img.shields.io/github/license/jlowin/fastmcp.svg)](https://github.com/jlowin/fastmcp/blob/main/LICENSE)
9
 
10
+ </div>
11
 
12
+ 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.
13
+
14
+ 🚧 *Note: FastMCP is under active development, as is the low-level MCP Python SDK* 🏗️
15
+
16
+ Key features:
17
+ * **Intuitive**: Designed to feel familiar to Python developers, with powerful type hints and editor support
18
+ * **Simple**: Build compliant MCP servers with minimal boilerplate
19
+ * **Fast**: High-performance async implementation
20
+ * **Full-featured**: Complete implementation of the MCP specification
21
+
22
+ <!-- omit in toc -->
23
  ## Table of Contents
24
 
25
+ - [Installation](#installation)
26
+ - [Quickstart](#quickstart)
27
+ - [What is MCP?](#what-is-mcp)
28
+ - [Core Concepts](#core-concepts)
29
+ - [Server](#server)
30
+ - [Resources](#resources)
31
+ - [Tools](#tools)
32
+ - [Prompts](#prompts)
33
+ - [Images](#images)
34
+ - [Context](#context)
35
+ - [Deployment](#deployment)
36
  - [Development](#development)
37
+ - [Claude Desktop](#claude-desktop)
38
+ - [Examples](#examples)
39
+ - [Echo Server](#echo-server)
40
+ - [SQLite Explorer](#sqlite-explorer)
41
 
42
  ## Installation
43
 
 
 
 
44
  ```bash
45
+ # We strongly recommend installing with uv
46
+ brew install uv # on macOS
47
+ uv pip install fastmcp
48
  ```
 
49
 
50
+ Or with pip:
51
  ```bash
52
+ pip install fastmcp
53
  ```
54
 
55
+ ## Quickstart
56
 
57
+ Let's create a simple MCP server that exposes a calculator tool and some data:
58
 
59
  ```python
 
60
  from fastmcp import FastMCP
61
 
62
+
63
+ # Create an MCP server
64
  mcp = FastMCP("Demo")
65
 
 
 
 
 
 
66
 
67
+ # Add an addition tool
68
  @mcp.tool()
69
  def add(a: int, b: int) -> int:
70
  """Add two numbers"""
71
  return a + b
72
 
73
+
74
+ # Add a dynamic greeting resource
75
+ @mcp.resource("greeting://{name}")
76
+ def get_greeting(name: str) -> str:
77
+ """Get a personalized greeting"""
78
+ return f"Hello, {name}!"
79
+ ```
80
+
81
+ To use this server, you have two options:
82
+
83
+ 1. Install it in Claude Desktop:
84
+ ```bash
85
+ fastmcp install server.py
86
+ ```
87
+
88
+ 2. Test it with the MCP Inspector:
89
+ ```bash
90
+ fastmcp dev server.py
91
  ```
92
 
93
+ ![MCP Inspector](docs/images/mcp-inspector.png)
94
+
95
+ ## What is MCP?
96
+
97
+ 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:
98
+
99
+ - Expose data through **Resources** (like GET endpoints)
100
+ - Provide functionality through **Tools** (like POST endpoints)
101
+ - Define interaction patterns through **Prompts** (reusable templates for LLM interactions)
102
+
103
  ## Core Concepts
104
 
105
+ *Note: All code examples below assume you've created a FastMCP server instance called `mcp`.*
106
+
107
+ ### Server
108
+
109
+ The FastMCP server is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing:
110
+
111
+ ```python
112
+ from fastmcp import FastMCP
113
+
114
+ # Create a named server
115
+ mcp = FastMCP("My App")
116
+
117
+ # Configure host/port for HTTP transport (optional)
118
+ mcp = FastMCP("My App", host="localhost", port=8000)
119
+ ```
120
 
121
  ### Resources
122
 
123
+ 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:
124
 
125
+ - File contents
126
+ - Database schemas
127
+ - API responses
128
+ - System information
129
 
130
+ Resources can be static:
131
  ```python
132
+ @mcp.resource("config://app")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  def get_config() -> str:
134
+ """Static configuration data"""
135
+ return "App configuration here"
136
  ```
137
 
138
+ Or dynamic with parameters (FastMCP automatically handles these as MCP templates):
139
+ ```python
140
+ @mcp.resource("users://{user_id}/profile")
141
+ def get_user_profile(user_id: str) -> str:
142
+ """Dynamic user data"""
143
+ return f"Profile data for user {user_id}"
144
+ ```
145
 
146
+ ### Tools
147
 
148
+ 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.
149
 
150
+ Simple calculation example:
151
+ ```python
152
+ @mcp.tool()
153
+ def calculate_bmi(weight_kg: float, height_m: float) -> float:
154
+ """Calculate BMI given weight in kg and height in meters"""
155
+ return weight_kg / (height_m ** 2)
156
+ ```
157
 
158
+ HTTP request example:
159
+ ```python
160
+ import httpx
161
+
162
+ @mcp.tool()
163
+ async def fetch_weather(city: str) -> str:
164
+ """Fetch current weather for a city"""
165
+ async with httpx.AsyncClient() as client:
166
+ response = await client.get(
167
+ f"https://api.weather.com/{city}"
168
+ )
169
+ return response.text
170
+ ```
171
+
172
+ ### Prompts
173
 
174
+ 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:
175
 
176
  ```python
177
+ @mcp.prompt()
178
+ def review_code(code: str) -> str:
179
+ return f"Please review this code:\n\n{code}"
180
+ ```
181
+
182
+ Or a more structured sequence of messages:
183
+ ```python
184
+ from fastmcp.prompts.base import UserMessage, AssistantMessage
185
+
186
+ @mcp.prompt()
187
+ def debug_error(error: str) -> list[Message]:
188
+ return [
189
+ UserMessage("I'm seeing this error:"),
190
+ UserMessage(error),
191
+ AssistantMessage("I'll help debug that. What have you tried so far?")
192
+ ]
193
+ ```
194
+
195
+
196
+ ### Images
197
+
198
+ FastMCP provides an `Image` class that automatically handles image data in your server:
199
+
200
+ ```python
201
+ from fastmcp import FastMCP, Image
202
+ from PIL import Image as PILImage
203
+
204
  @mcp.tool()
205
+ def create_thumbnail(image_path: str) -> Image:
206
+ """Create a thumbnail from an image"""
207
+ img = PILImage.open(image_path)
208
+ img.thumbnail((100, 100))
209
+
210
+ # FastMCP automatically handles conversion and MIME types
211
+ return Image(data=img.tobytes(), format="png")
212
 
213
  @mcp.tool()
214
+ def load_image(path: str) -> Image:
215
+ """Load an image from disk"""
216
+ # FastMCP handles reading and format detection
217
+ return Image(path=path)
 
 
 
 
 
218
  ```
219
 
220
+ Images can be used as the result of both tools and resources.
 
 
 
 
221
 
222
+ ### Context
223
 
224
+ The Context object gives your tools and resources access to MCP capabilities. To use it, add a parameter annotated with `fastmcp.Context`:
225
 
226
+ ```python
227
+ from fastmcp import FastMCP, Context
228
 
229
+ @mcp.tool()
230
+ async def long_task(files: list[str], ctx: Context) -> str:
231
+ """Process multiple files with progress tracking"""
232
+ for i, file in enumerate(files):
233
+ ctx.info(f"Processing {file}")
234
+ await ctx.report_progress(i, len(files))
235
+
236
+ # Read another resource if needed
237
+ data = await ctx.read_resource(f"file://{file}")
238
+
239
+ return "Processing complete"
240
+ ```
241
 
242
+ The Context object provides:
243
+ - Progress reporting through `report_progress()`
244
+ - Logging via `debug()`, `info()`, `warning()`, and `error()`
245
+ - Resource access through `read_resource()`
246
+ - Request metadata via `request_id` and `client_id`
247
+
248
+ ## Deployment
249
 
250
+ The FastMCP CLI helps you develop and deploy MCP servers.
 
251
 
252
+ 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`.
 
253
 
254
+ 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`.
255
+
256
+ ### Development
257
+
258
+ Test and debug your server with the MCP Inspector:
259
+ ```bash
260
+ # Provide the fully qualified path to your server
261
+ fastmcp dev server.py:my_mcp_server
262
+
263
+ # Or just the file if your server is named 'mcp', 'server', or 'app'
264
+ fastmcp dev server.py
265
  ```
266
 
267
+ 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:
268
 
269
+ ```bash
270
+ # With additional packages
271
+ fastmcp dev server.py --with pandas --with numpy
272
 
273
+ # Using your project's dependencies and up-to-date code
274
+ fastmcp dev server.py --with-editable .
275
+ ```
276
+
277
+ ### Claude Desktop
278
 
279
+ Install your server in Claude Desktop:
280
  ```bash
281
+ # Basic usage (name is taken from your FastMCP instance)
282
+ fastmcp install server.py
283
 
284
+ # With a custom name
285
+ fastmcp install server.py --name "My Server"
286
 
287
+ # With dependencies
288
+ fastmcp install server.py --with pandas --with numpy
289
 
290
+ # Replace an existing server
291
+ fastmcp install server.py --force
292
  ```
293
 
294
+ The server name in Claude will be:
295
+ 1. The `--name` parameter if provided
296
+ 2. The `name` from your FastMCP instance
297
+ 3. The filename if the server can't be imported
298
+
299
+ ## Examples
300
+
301
+ ### Echo Server
302
+ A simple server demonstrating resources, tools, and prompts:
303
+
304
+ ```python
305
+ from fastmcp import FastMCP
306
+
307
+ mcp = FastMCP("Echo")
308
 
309
+ @mcp.resource("echo://{message}")
310
+ def echo_resource(message: str) -> str:
311
+ """Echo a message as a resource"""
312
+ return f"Resource echo: {message}"
313
+
314
+ @mcp.tool()
315
+ def echo_tool(message: str) -> str:
316
+ """Echo a message as a tool"""
317
+ return f"Tool echo: {message}"
318
+
319
+ @mcp.prompt()
320
+ def echo_prompt(message: str) -> str:
321
+ """Create an echo prompt"""
322
+ return f"Please process this message: {message}"
323
+ ```
324
+
325
+ ### SQLite Explorer
326
+ A more complex example showing database integration:
327
+
328
+ ```python
329
+ from fastmcp import FastMCP
330
+ import sqlite3
331
+
332
+ mcp = FastMCP("SQLite Explorer")
333
+
334
+ @mcp.resource("schema://main")
335
+ def get_schema() -> str:
336
+ """Provide the database schema as a resource"""
337
+ conn = sqlite3.connect("database.db")
338
+ schema = conn.execute(
339
+ "SELECT sql FROM sqlite_master WHERE type='table'"
340
+ ).fetchall()
341
+ return "\n".join(sql[0] for sql in schema if sql[0])
342
+
343
+ @mcp.tool()
344
+ def query_data(sql: str) -> str:
345
+ """Execute SQL queries safely"""
346
+ conn = sqlite3.connect("database.db")
347
+ try:
348
+ result = conn.execute(sql).fetchall()
349
+ return "\n".join(str(row) for row in result)
350
+ except Exception as e:
351
+ return f"Error: {str(e)}"
352
+
353
+ @mcp.prompt()
354
+ def analyze_table(table: str) -> str:
355
+ """Create a prompt template for analyzing tables"""
356
+ return f"""Please analyze this database table:
357
+ Table: {table}
358
+ Schema:
359
+ {get_schema()}
360
+
361
+ What insights can you provide about the structure and relationships?"""
362
+ ```
docs/assets/demo-inspector.png ADDED

Git LFS Details

  • SHA256: f090afaf6d72fc42ee1ec073298fba541a69c7ea105f5dc33edb6bb60dabc218
  • Pointer size: 131 Bytes
  • Size of remote file: 813 kB
examples/desktop.py CHANGED
@@ -23,7 +23,3 @@ def desktop() -> list[str]:
23
  def add(a: int, b: int) -> int:
24
  """Add two numbers"""
25
  return a + b
26
-
27
-
28
- if __name__ == "__main__":
29
- mcp.run()
 
23
  def add(a: int, b: int) -> int:
24
  """Add two numbers"""
25
  return a + b
 
 
 
 
examples/echo.py CHANGED
@@ -14,8 +14,13 @@ def echo_tool(text: str) -> str:
14
  return text
15
 
16
 
 
 
 
 
 
17
  @mcp.resource("echo://{text}")
18
- def echo_resource(text: str) -> str:
19
  """Echo the input text"""
20
  return f"Echo: {text}"
21
 
@@ -23,7 +28,3 @@ def echo_resource(text: str) -> str:
23
  @mcp.prompt("echo")
24
  def echo_prompt(text: str) -> str:
25
  return text
26
-
27
-
28
- if __name__ == "__main__":
29
- mcp.run()
 
14
  return text
15
 
16
 
17
+ @mcp.resource("echo://static")
18
+ def echo_resource() -> str:
19
+ return "Echo!"
20
+
21
+
22
  @mcp.resource("echo://{text}")
23
+ def echo_template(text: str) -> str:
24
  """Echo the input text"""
25
  return f"Echo: {text}"
26
 
 
28
  @mcp.prompt("echo")
29
  def echo_prompt(text: str) -> str:
30
  return text
 
 
 
 
examples/readme-quickstart.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastmcp import FastMCP
2
+
3
+
4
+ # Create an MCP server
5
+ mcp = FastMCP("Demo")
6
+
7
+
8
+ # Add an addition tool
9
+ @mcp.tool()
10
+ def add(a: int, b: int) -> int:
11
+ """Add two numbers"""
12
+ return a + b
13
+
14
+
15
+ # Add a dynamic greeting resource
16
+ @mcp.resource("greeting://{name}")
17
+ def get_greeting(name: str) -> str:
18
+ """Get a personalized greeting"""
19
+ return f"Hello, {name}!"
examples/screenshot.py CHANGED
@@ -26,7 +26,3 @@ def take_screenshot() -> Image:
26
  # if the file exceeds ~1MB, it will be rejected by Claude
27
  screenshot.convert("RGB").save(buffer, format="JPEG", quality=60, optimize=True)
28
  return Image(data=buffer.getvalue(), format="jpeg")
29
-
30
-
31
- if __name__ == "__main__":
32
- mcp.run()
 
26
  # if the file exceeds ~1MB, it will be rejected by Claude
27
  screenshot.convert("RGB").save(buffer, format="JPEG", quality=60, optimize=True)
28
  return Image(data=buffer.getvalue(), format="jpeg")
 
 
 
 
examples/simple_echo.py CHANGED
@@ -13,7 +13,3 @@ mcp = FastMCP("Echo Server")
13
  def echo(text: str) -> str:
14
  """Echo the input text"""
15
  return text
16
-
17
-
18
- if __name__ == "__main__":
19
- mcp.run()
 
13
  def echo(text: str) -> str:
14
  """Echo the input text"""
15
  return text
 
 
 
 
src/fastmcp/cli/claude.py CHANGED
@@ -25,8 +25,8 @@ def get_claude_config_path() -> Path | None:
25
 
26
 
27
  def update_claude_config(
28
- file: Path,
29
- server_name: Optional[str] = None,
30
  *,
31
  with_editable: Optional[Path] = None,
32
  with_packages: Optional[list[str]] = None,
@@ -35,9 +35,8 @@ def update_claude_config(
35
  """Add the MCP server to Claude's configuration.
36
 
37
  Args:
38
- file: Path to the server file
39
- server_name: Optional custom name for the server. If not provided,
40
- defaults to the file stem
41
  with_editable: Optional directory to install in editable mode
42
  with_packages: Optional list of additional packages to install
43
  force: If True, replace existing server with same name
@@ -55,46 +54,49 @@ def update_claude_config(
55
  if "mcpServers" not in config:
56
  config["mcpServers"] = {}
57
 
58
- # Use provided server_name or fall back to file stem
59
- name = server_name or file.stem
60
- if name in config["mcpServers"]:
61
  if not force:
62
  logger.warning(
63
- f"Server '{name}' already exists in Claude config. "
64
  "Use `--force` to replace.",
65
  extra={"config_file": str(config_file)},
66
  )
67
  return False
68
  logger.info(
69
- f"Replacing existing server '{name}' in Claude config",
70
  extra={"config_file": str(config_file)},
71
  )
72
 
73
  # Build uv run command
74
- args = ["run"]
75
 
76
  if with_editable:
77
  args.extend(["--with-editable", str(with_editable)])
78
 
79
- # Always include fastmcp
80
- args.extend(["--with", "fastmcp"])
81
-
82
- # Add additional packages
83
  if with_packages:
84
  for pkg in with_packages:
85
  if pkg:
86
  args.extend(["--with", pkg])
87
 
88
- args.append(str(file))
 
 
 
 
 
 
 
 
 
89
 
90
- config["mcpServers"][name] = {
91
  "command": "uv",
92
  "args": args,
93
  }
94
 
95
  config_file.write_text(json.dumps(config, indent=2))
96
  logger.info(
97
- f"Added server '{name}' to Claude config",
98
  extra={"config_file": str(config_file)},
99
  )
100
  return True
 
25
 
26
 
27
  def update_claude_config(
28
+ file_spec: str,
29
+ server_name: str,
30
  *,
31
  with_editable: Optional[Path] = None,
32
  with_packages: Optional[list[str]] = None,
 
35
  """Add the MCP server to Claude's configuration.
36
 
37
  Args:
38
+ file_spec: Path to the server file, optionally with :object suffix
39
+ server_name: Name for the server in Claude's config
 
40
  with_editable: Optional directory to install in editable mode
41
  with_packages: Optional list of additional packages to install
42
  force: If True, replace existing server with same name
 
54
  if "mcpServers" not in config:
55
  config["mcpServers"] = {}
56
 
57
+ if server_name in config["mcpServers"]:
 
 
58
  if not force:
59
  logger.warning(
60
+ f"Server '{server_name}' already exists in Claude config. "
61
  "Use `--force` to replace.",
62
  extra={"config_file": str(config_file)},
63
  )
64
  return False
65
  logger.info(
66
+ f"Replacing existing server '{server_name}' in Claude config",
67
  extra={"config_file": str(config_file)},
68
  )
69
 
70
  # Build uv run command
71
+ args = ["run", "--with", "fastmcp"]
72
 
73
  if with_editable:
74
  args.extend(["--with-editable", str(with_editable)])
75
 
 
 
 
 
76
  if with_packages:
77
  for pkg in with_packages:
78
  if pkg:
79
  args.extend(["--with", pkg])
80
 
81
+ # Convert file path to absolute before adding to command
82
+ # Split off any :object suffix first
83
+ if ":" in file_spec:
84
+ file_path, server_object = file_spec.rsplit(":", 1)
85
+ file_spec = f"{Path(file_path).resolve()}:{server_object}"
86
+ else:
87
+ file_spec = str(Path(file_spec).resolve())
88
+
89
+ # Add fastmcp run command
90
+ args.extend(["fastmcp", "run", file_spec])
91
 
92
+ config["mcpServers"][server_name] = {
93
  "command": "uv",
94
  "args": args,
95
  }
96
 
97
  config_file.write_text(json.dumps(config, indent=2))
98
  logger.info(
99
+ f"Added server '{server_name}' to Claude config",
100
  extra={"config_file": str(config_file)},
101
  )
102
  return True
src/fastmcp/cli/cli.py CHANGED
@@ -24,11 +24,11 @@ app = typer.Typer(
24
 
25
 
26
  def _build_uv_command(
27
- file: Path,
28
  with_editable: Optional[Path] = None,
29
  with_packages: Optional[list[str]] = None,
30
  ) -> list[str]:
31
- """Build the uv run command."""
32
  cmd = ["uv"]
33
 
34
  cmd.extend(["run", "--with", "fastmcp"])
@@ -41,7 +41,8 @@ def _build_uv_command(
41
  if pkg:
42
  cmd.extend(["--with", pkg])
43
 
44
- cmd.append(str(file))
 
45
  return cmd
46
 
47
 
@@ -89,7 +90,7 @@ def _import_server(file: Path, server_object: Optional[str] = None):
89
  module = importlib.util.module_from_spec(spec)
90
  spec.loader.exec_module(module)
91
 
92
- # If no object specified, try __main__ block
93
  if not server_object:
94
  # Look for the most common server object names
95
  for name in ["mcp", "server", "app"]:
@@ -97,7 +98,9 @@ def _import_server(file: Path, server_object: Optional[str] = None):
97
  return getattr(module, name)
98
 
99
  logger.error(
100
- f"No server object found in {file}. Please specify the object name with file:object syntax.",
 
 
101
  extra={"file": str(file)},
102
  )
103
  sys.exit(1)
@@ -178,7 +181,7 @@ def dev(
178
  )
179
 
180
  try:
181
- uv_cmd = _build_uv_command(file, with_editable, with_packages)
182
  # Run the MCP Inspector command
183
  process = subprocess.run(
184
  ["npx", "@modelcontextprotocol/inspector"] + uv_cmd,
@@ -229,7 +232,12 @@ def run(
229
  ),
230
  ] = None,
231
  ) -> None:
232
- """Run a FastMCP server."""
 
 
 
 
 
233
  file, server_object = _parse_file_path(file_spec)
234
 
235
  logger.debug(
@@ -338,7 +346,7 @@ def install(
338
  name = file.stem
339
 
340
  if claude.update_claude_config(
341
- file,
342
  name,
343
  with_editable=with_editable,
344
  with_packages=with_packages,
@@ -348,7 +356,3 @@ def install(
348
  else:
349
  print(f"Failed to install {name} in Claude app")
350
  sys.exit(1)
351
-
352
-
353
- if __name__ == "__main__":
354
- app()
 
24
 
25
 
26
  def _build_uv_command(
27
+ file_spec: str,
28
  with_editable: Optional[Path] = None,
29
  with_packages: Optional[list[str]] = None,
30
  ) -> list[str]:
31
+ """Build the uv run command that runs a FastMCP server through fastmcp run."""
32
  cmd = ["uv"]
33
 
34
  cmd.extend(["run", "--with", "fastmcp"])
 
41
  if pkg:
42
  cmd.extend(["--with", pkg])
43
 
44
+ # Add fastmcp run command
45
+ cmd.extend(["fastmcp", "run", file_spec])
46
  return cmd
47
 
48
 
 
90
  module = importlib.util.module_from_spec(spec)
91
  spec.loader.exec_module(module)
92
 
93
+ # If no object specified, try common server names
94
  if not server_object:
95
  # Look for the most common server object names
96
  for name in ["mcp", "server", "app"]:
 
98
  return getattr(module, name)
99
 
100
  logger.error(
101
+ f"No server object found in {file}. Please either:\n"
102
+ "1. Use a standard variable name (mcp, server, or app)\n"
103
+ "2. Specify the object name with file:object syntax",
104
  extra={"file": str(file)},
105
  )
106
  sys.exit(1)
 
181
  )
182
 
183
  try:
184
+ uv_cmd = _build_uv_command(file_spec, with_editable, with_packages)
185
  # Run the MCP Inspector command
186
  process = subprocess.run(
187
  ["npx", "@modelcontextprotocol/inspector"] + uv_cmd,
 
232
  ),
233
  ] = None,
234
  ) -> None:
235
+ """Run a FastMCP server.
236
+
237
+ The server can be specified in two ways:
238
+ 1. Module approach: server.py - runs the module directly, expecting a server.run() call
239
+ 2. Import approach: server.py:app - imports and runs the specified server object
240
+ """
241
  file, server_object = _parse_file_path(file_spec)
242
 
243
  logger.debug(
 
346
  name = file.stem
347
 
348
  if claude.update_claude_config(
349
+ file_spec,
350
  name,
351
  with_editable=with_editable,
352
  with_packages=with_packages,
 
356
  else:
357
  print(f"Failed to install {name} in Claude app")
358
  sys.exit(1)
 
 
 
 
src/fastmcp/resources/base.py CHANGED
@@ -53,13 +53,8 @@ class Resource(BaseModel, abc.ABC):
53
  """Set default name from URI if not provided."""
54
  if name:
55
  return name
56
- # Extract everything after the protocol (e.g., "desktop" from "resource://desktop")
57
  if uri := info.data.get("uri"):
58
- uri_str = str(uri)
59
- if "://" in uri_str:
60
- name = uri_str.split("://", 1)[1]
61
- if name:
62
- return name
63
  raise ValueError("Either name or uri must be provided")
64
 
65
  @abc.abstractmethod
 
53
  """Set default name from URI if not provided."""
54
  if name:
55
  return name
 
56
  if uri := info.data.get("uri"):
57
+ return str(uri)
 
 
 
 
58
  raise ValueError("Either name or uri must be provided")
59
 
60
  @abc.abstractmethod
tests/resources/test_resources.py CHANGED
@@ -46,7 +46,7 @@ class TestResourceValidation:
46
  uri="resource://my-resource",
47
  fn=dummy_func,
48
  )
49
- assert resource.name == "my-resource"
50
 
51
  def test_resource_name_validation(self):
52
  """Test name validation."""
 
46
  uri="resource://my-resource",
47
  fn=dummy_func,
48
  )
49
+ assert resource.name == "resource://my-resource"
50
 
51
  def test_resource_name_validation(self):
52
  """Test name validation."""