fastmcp 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastmcp/__init__.py +8 -2
- fastmcp/cli/__init__.py +3 -1
- fastmcp/cli/claude.py +20 -18
- fastmcp/cli/cli.py +16 -12
- fastmcp/prompts/__init__.py +4 -0
- fastmcp/prompts/base.py +149 -0
- fastmcp/prompts/manager.py +50 -0
- fastmcp/prompts/prompt_manager.py +36 -0
- fastmcp/resources/__init__.py +23 -0
- fastmcp/resources/base.py +63 -0
- fastmcp/resources/resource_manager.py +94 -0
- fastmcp/resources/templates.py +80 -0
- fastmcp/resources/types.py +171 -0
- fastmcp/server.py +446 -112
- fastmcp/tools/__init__.py +4 -0
- fastmcp/tools/base.py +79 -0
- fastmcp/tools/tool_manager.py +55 -0
- fastmcp/utilities/types.py +53 -0
- fastmcp-0.3.0.dist-info/METADATA +385 -0
- fastmcp-0.3.0.dist-info/RECORD +26 -0
- {fastmcp-0.2.0.dist-info → fastmcp-0.3.0.dist-info}/WHEEL +1 -2
- fastmcp-0.3.0.dist-info/licenses/LICENSE +201 -0
- fastmcp/_version.py +0 -16
- fastmcp/app.py +0 -6
- fastmcp/resources.py +0 -232
- fastmcp/tools.py +0 -150
- fastmcp-0.2.0.dist-info/METADATA +0 -143
- fastmcp-0.2.0.dist-info/RECORD +0 -17
- fastmcp-0.2.0.dist-info/top_level.txt +0 -1
- {fastmcp-0.2.0.dist-info → fastmcp-0.3.0.dist-info}/entry_points.txt +0 -0
fastmcp/tools/base.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fastmcp
|
|
2
|
+
from fastmcp.exceptions import ToolError
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field, TypeAdapter, validate_call
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
import inspect
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Callable, Optional
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from fastmcp.server import Context
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Tool(BaseModel):
|
|
16
|
+
"""Internal tool registration info."""
|
|
17
|
+
|
|
18
|
+
fn: Callable = Field(exclude=True)
|
|
19
|
+
name: str = Field(description="Name of the tool")
|
|
20
|
+
description: str = Field(description="Description of what the tool does")
|
|
21
|
+
parameters: dict = Field(description="JSON schema for tool parameters")
|
|
22
|
+
is_async: bool = Field(description="Whether the tool is async")
|
|
23
|
+
context_kwarg: Optional[str] = Field(
|
|
24
|
+
None, description="Name of the kwarg that should receive context"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def from_function(
|
|
29
|
+
cls,
|
|
30
|
+
fn: Callable,
|
|
31
|
+
name: Optional[str] = None,
|
|
32
|
+
description: Optional[str] = None,
|
|
33
|
+
context_kwarg: Optional[str] = None,
|
|
34
|
+
) -> "Tool":
|
|
35
|
+
"""Create a Tool from a function."""
|
|
36
|
+
func_name = name or fn.__name__
|
|
37
|
+
|
|
38
|
+
if func_name == "<lambda>":
|
|
39
|
+
raise ValueError("You must provide a name for lambda functions")
|
|
40
|
+
|
|
41
|
+
func_doc = description or fn.__doc__ or ""
|
|
42
|
+
is_async = inspect.iscoroutinefunction(fn)
|
|
43
|
+
|
|
44
|
+
# Get schema from TypeAdapter - will fail if function isn't properly typed
|
|
45
|
+
parameters = TypeAdapter(fn).json_schema()
|
|
46
|
+
|
|
47
|
+
# Find context parameter if it exists
|
|
48
|
+
if context_kwarg is None:
|
|
49
|
+
sig = inspect.signature(fn)
|
|
50
|
+
for param_name, param in sig.parameters.items():
|
|
51
|
+
if param.annotation is fastmcp.Context:
|
|
52
|
+
context_kwarg = param_name
|
|
53
|
+
break
|
|
54
|
+
|
|
55
|
+
# ensure the arguments are properly cast
|
|
56
|
+
fn = validate_call(fn)
|
|
57
|
+
|
|
58
|
+
return cls(
|
|
59
|
+
fn=fn,
|
|
60
|
+
name=func_name,
|
|
61
|
+
description=func_doc,
|
|
62
|
+
parameters=parameters,
|
|
63
|
+
is_async=is_async,
|
|
64
|
+
context_kwarg=context_kwarg,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
async def run(self, arguments: dict, context: Optional["Context"] = None) -> Any:
|
|
68
|
+
"""Run the tool with arguments."""
|
|
69
|
+
try:
|
|
70
|
+
# Inject context if needed
|
|
71
|
+
if self.context_kwarg:
|
|
72
|
+
arguments[self.context_kwarg] = context
|
|
73
|
+
|
|
74
|
+
# Call function with proper async handling
|
|
75
|
+
if self.is_async:
|
|
76
|
+
return await self.fn(**arguments)
|
|
77
|
+
return self.fn(**arguments)
|
|
78
|
+
except Exception as e:
|
|
79
|
+
raise ToolError(f"Error executing tool {self.name}: {e}") from e
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from fastmcp.exceptions import ToolError
|
|
2
|
+
|
|
3
|
+
from fastmcp.tools.base import Tool
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from typing import Any, Callable, Dict, Optional, TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from fastmcp.utilities.logging import get_logger
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from fastmcp.server import Context
|
|
12
|
+
|
|
13
|
+
logger = get_logger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ToolManager:
|
|
17
|
+
"""Manages FastMCP tools."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, warn_on_duplicate_tools: bool = True):
|
|
20
|
+
self._tools: Dict[str, Tool] = {}
|
|
21
|
+
self.warn_on_duplicate_tools = warn_on_duplicate_tools
|
|
22
|
+
|
|
23
|
+
def get_tool(self, name: str) -> Optional[Tool]:
|
|
24
|
+
"""Get tool by name."""
|
|
25
|
+
return self._tools.get(name)
|
|
26
|
+
|
|
27
|
+
def list_tools(self) -> list[Tool]:
|
|
28
|
+
"""List all registered tools."""
|
|
29
|
+
return list(self._tools.values())
|
|
30
|
+
|
|
31
|
+
def add_tool(
|
|
32
|
+
self,
|
|
33
|
+
fn: Callable,
|
|
34
|
+
name: Optional[str] = None,
|
|
35
|
+
description: Optional[str] = None,
|
|
36
|
+
) -> Tool:
|
|
37
|
+
"""Add a tool to the server."""
|
|
38
|
+
tool = Tool.from_function(fn, name=name, description=description)
|
|
39
|
+
existing = self._tools.get(tool.name)
|
|
40
|
+
if existing:
|
|
41
|
+
if self.warn_on_duplicate_tools:
|
|
42
|
+
logger.warning(f"Tool already exists: {tool.name}")
|
|
43
|
+
return existing
|
|
44
|
+
self._tools[tool.name] = tool
|
|
45
|
+
return tool
|
|
46
|
+
|
|
47
|
+
async def call_tool(
|
|
48
|
+
self, name: str, arguments: dict, context: Optional["Context"] = None
|
|
49
|
+
) -> Any:
|
|
50
|
+
"""Call a tool by name with arguments."""
|
|
51
|
+
tool = self.get_tool(name)
|
|
52
|
+
if not tool:
|
|
53
|
+
raise ToolError(f"Unknown tool: {name}")
|
|
54
|
+
|
|
55
|
+
return await tool.run(arguments, context=context)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Common types used across FastMCP."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional, Union
|
|
6
|
+
|
|
7
|
+
from mcp.types import ImageContent
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Image:
|
|
11
|
+
"""Helper class for returning images from tools."""
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
path: Optional[Union[str, Path]] = None,
|
|
16
|
+
data: Optional[bytes] = None,
|
|
17
|
+
format: Optional[str] = None,
|
|
18
|
+
):
|
|
19
|
+
if path is None and data is None:
|
|
20
|
+
raise ValueError("Either path or data must be provided")
|
|
21
|
+
if path is not None and data is not None:
|
|
22
|
+
raise ValueError("Only one of path or data can be provided")
|
|
23
|
+
|
|
24
|
+
self.path = Path(path) if path else None
|
|
25
|
+
self.data = data
|
|
26
|
+
self._format = format
|
|
27
|
+
self._mime_type = self._get_mime_type()
|
|
28
|
+
|
|
29
|
+
def _get_mime_type(self) -> str:
|
|
30
|
+
"""Get MIME type from format or guess from file extension."""
|
|
31
|
+
if self._format:
|
|
32
|
+
return f"image/{self._format.lower()}"
|
|
33
|
+
|
|
34
|
+
if self.path:
|
|
35
|
+
suffix = self.path.suffix.lower()
|
|
36
|
+
return {
|
|
37
|
+
".png": "image/png",
|
|
38
|
+
".jpg": "image/jpeg",
|
|
39
|
+
".jpeg": "image/jpeg",
|
|
40
|
+
".gif": "image/gif",
|
|
41
|
+
".webp": "image/webp",
|
|
42
|
+
}.get(suffix, "application/octet-stream")
|
|
43
|
+
return "image/png" # default for raw binary data
|
|
44
|
+
|
|
45
|
+
def to_image_content(self) -> ImageContent:
|
|
46
|
+
"""Convert to MCP ImageContent."""
|
|
47
|
+
if self.path:
|
|
48
|
+
with open(self.path, "rb") as f:
|
|
49
|
+
data = base64.b64encode(f.read()).decode()
|
|
50
|
+
else:
|
|
51
|
+
data = base64.b64encode(self.data).decode()
|
|
52
|
+
|
|
53
|
+
return ImageContent(type="image", data=data, mimeType=self._mime_type)
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: fastmcp
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: A more ergonomic interface for MCP servers
|
|
5
|
+
Author: Jeremiah Lowin
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: httpx>=0.26.0
|
|
9
|
+
Requires-Dist: mcp<2.0.0,>=1.0.0
|
|
10
|
+
Requires-Dist: pydantic-settings>=2.6.1
|
|
11
|
+
Requires-Dist: pydantic<3.0.0,>=2.5.3
|
|
12
|
+
Requires-Dist: typer>=0.9.0
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: copychat>=0.5.2; extra == 'dev'
|
|
15
|
+
Requires-Dist: ipython>=8.12.3; extra == 'dev'
|
|
16
|
+
Requires-Dist: pdbpp>=0.10.3; extra == 'dev'
|
|
17
|
+
Requires-Dist: pre-commit; extra == 'dev'
|
|
18
|
+
Requires-Dist: pytest-asyncio>=0.23.5; extra == 'dev'
|
|
19
|
+
Requires-Dist: pytest-xdist>=3.6.1; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest>=8.3.3; extra == 'dev'
|
|
21
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
<!-- omit in toc -->
|
|
25
|
+
# FastMCP
|
|
26
|
+
|
|
27
|
+
<div align="center">
|
|
28
|
+
|
|
29
|
+
[](https://pypi.org/project/fastmcp)
|
|
30
|
+
[](https://github.com/jlowin/fastmcp/actions/workflows/run-tests.yml)
|
|
31
|
+
[](https://github.com/jlowin/fastmcp/blob/main/LICENSE)
|
|
32
|
+
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
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.
|
|
36
|
+
|
|
37
|
+
🚧 *Note: FastMCP is under active development, as is the low-level MCP Python SDK* 🏗️
|
|
38
|
+
|
|
39
|
+
Key features:
|
|
40
|
+
* **Intuitive**: Designed to feel familiar to Python developers, with powerful type hints and editor support
|
|
41
|
+
* **Simple**: Build compliant MCP servers with minimal boilerplate
|
|
42
|
+
* **Fast**: High-performance async implementation
|
|
43
|
+
* **Full-featured**: Complete implementation of the MCP specification
|
|
44
|
+
|
|
45
|
+
<!-- omit in toc -->
|
|
46
|
+
## Table of Contents
|
|
47
|
+
|
|
48
|
+
- [Installation](#installation)
|
|
49
|
+
- [Quickstart](#quickstart)
|
|
50
|
+
- [What is MCP?](#what-is-mcp)
|
|
51
|
+
- [Core Concepts](#core-concepts)
|
|
52
|
+
- [Server](#server)
|
|
53
|
+
- [Resources](#resources)
|
|
54
|
+
- [Tools](#tools)
|
|
55
|
+
- [Prompts](#prompts)
|
|
56
|
+
- [Images](#images)
|
|
57
|
+
- [Context](#context)
|
|
58
|
+
- [Deployment](#deployment)
|
|
59
|
+
- [Development](#development)
|
|
60
|
+
- [Claude Desktop](#claude-desktop)
|
|
61
|
+
- [Examples](#examples)
|
|
62
|
+
- [Echo Server](#echo-server)
|
|
63
|
+
- [SQLite Explorer](#sqlite-explorer)
|
|
64
|
+
|
|
65
|
+
## Installation
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# We strongly recommend installing with uv
|
|
69
|
+
brew install uv # on macOS
|
|
70
|
+
uv pip install fastmcp
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Or with pip:
|
|
74
|
+
```bash
|
|
75
|
+
pip install fastmcp
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Quickstart
|
|
79
|
+
|
|
80
|
+
Let's create a simple MCP server that exposes a calculator tool and some data:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from fastmcp import FastMCP
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# Create an MCP server
|
|
87
|
+
mcp = FastMCP("Demo")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# Add an addition tool
|
|
91
|
+
@mcp.tool()
|
|
92
|
+
def add(a: int, b: int) -> int:
|
|
93
|
+
"""Add two numbers"""
|
|
94
|
+
return a + b
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# Add a dynamic greeting resource
|
|
98
|
+
@mcp.resource("greeting://{name}")
|
|
99
|
+
def get_greeting(name: str) -> str:
|
|
100
|
+
"""Get a personalized greeting"""
|
|
101
|
+
return f"Hello, {name}!"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
To use this server, you have two options:
|
|
105
|
+
|
|
106
|
+
1. Install it in [Claude Desktop](https://claude.ai/download):
|
|
107
|
+
```bash
|
|
108
|
+
fastmcp install server.py
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
2. Test it with the MCP Inspector:
|
|
112
|
+
```bash
|
|
113
|
+
fastmcp dev server.py
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+

|
|
117
|
+
|
|
118
|
+
## What is MCP?
|
|
119
|
+
|
|
120
|
+
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:
|
|
121
|
+
|
|
122
|
+
- Expose data through **Resources** (like GET endpoints)
|
|
123
|
+
- Provide functionality through **Tools** (like POST endpoints)
|
|
124
|
+
- Define interaction patterns through **Prompts** (reusable templates for LLM interactions)
|
|
125
|
+
|
|
126
|
+
## Core Concepts
|
|
127
|
+
|
|
128
|
+
*Note: All code examples below assume you've created a FastMCP server instance called `mcp`.*
|
|
129
|
+
|
|
130
|
+
### Server
|
|
131
|
+
|
|
132
|
+
The FastMCP server is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from fastmcp import FastMCP
|
|
136
|
+
|
|
137
|
+
# Create a named server
|
|
138
|
+
mcp = FastMCP("My App")
|
|
139
|
+
|
|
140
|
+
# Configure host/port for HTTP transport (optional)
|
|
141
|
+
mcp = FastMCP("My App", host="localhost", port=8000)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Resources
|
|
145
|
+
|
|
146
|
+
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:
|
|
147
|
+
|
|
148
|
+
- File contents
|
|
149
|
+
- Database schemas
|
|
150
|
+
- API responses
|
|
151
|
+
- System information
|
|
152
|
+
|
|
153
|
+
Resources can be static:
|
|
154
|
+
```python
|
|
155
|
+
@mcp.resource("config://app")
|
|
156
|
+
def get_config() -> str:
|
|
157
|
+
"""Static configuration data"""
|
|
158
|
+
return "App configuration here"
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Or dynamic with parameters (FastMCP automatically handles these as MCP templates):
|
|
162
|
+
```python
|
|
163
|
+
@mcp.resource("users://{user_id}/profile")
|
|
164
|
+
def get_user_profile(user_id: str) -> str:
|
|
165
|
+
"""Dynamic user data"""
|
|
166
|
+
return f"Profile data for user {user_id}"
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Tools
|
|
170
|
+
|
|
171
|
+
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.
|
|
172
|
+
|
|
173
|
+
Simple calculation example:
|
|
174
|
+
```python
|
|
175
|
+
@mcp.tool()
|
|
176
|
+
def calculate_bmi(weight_kg: float, height_m: float) -> float:
|
|
177
|
+
"""Calculate BMI given weight in kg and height in meters"""
|
|
178
|
+
return weight_kg / (height_m ** 2)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
HTTP request example:
|
|
182
|
+
```python
|
|
183
|
+
import httpx
|
|
184
|
+
|
|
185
|
+
@mcp.tool()
|
|
186
|
+
async def fetch_weather(city: str) -> str:
|
|
187
|
+
"""Fetch current weather for a city"""
|
|
188
|
+
async with httpx.AsyncClient() as client:
|
|
189
|
+
response = await client.get(
|
|
190
|
+
f"https://api.weather.com/{city}"
|
|
191
|
+
)
|
|
192
|
+
return response.text
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Prompts
|
|
196
|
+
|
|
197
|
+
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:
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
@mcp.prompt()
|
|
201
|
+
def review_code(code: str) -> str:
|
|
202
|
+
return f"Please review this code:\n\n{code}"
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Or a more structured sequence of messages:
|
|
206
|
+
```python
|
|
207
|
+
from fastmcp.prompts.base import UserMessage, AssistantMessage
|
|
208
|
+
|
|
209
|
+
@mcp.prompt()
|
|
210
|
+
def debug_error(error: str) -> list[Message]:
|
|
211
|
+
return [
|
|
212
|
+
UserMessage("I'm seeing this error:"),
|
|
213
|
+
UserMessage(error),
|
|
214
|
+
AssistantMessage("I'll help debug that. What have you tried so far?")
|
|
215
|
+
]
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
### Images
|
|
220
|
+
|
|
221
|
+
FastMCP provides an `Image` class that automatically handles image data in your server:
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
from fastmcp import FastMCP, Image
|
|
225
|
+
from PIL import Image as PILImage
|
|
226
|
+
|
|
227
|
+
@mcp.tool()
|
|
228
|
+
def create_thumbnail(image_path: str) -> Image:
|
|
229
|
+
"""Create a thumbnail from an image"""
|
|
230
|
+
img = PILImage.open(image_path)
|
|
231
|
+
img.thumbnail((100, 100))
|
|
232
|
+
|
|
233
|
+
# FastMCP automatically handles conversion and MIME types
|
|
234
|
+
return Image(data=img.tobytes(), format="png")
|
|
235
|
+
|
|
236
|
+
@mcp.tool()
|
|
237
|
+
def load_image(path: str) -> Image:
|
|
238
|
+
"""Load an image from disk"""
|
|
239
|
+
# FastMCP handles reading and format detection
|
|
240
|
+
return Image(path=path)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Images can be used as the result of both tools and resources.
|
|
244
|
+
|
|
245
|
+
### Context
|
|
246
|
+
|
|
247
|
+
The Context object gives your tools and resources access to MCP capabilities. To use it, add a parameter annotated with `fastmcp.Context`:
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
from fastmcp import FastMCP, Context
|
|
251
|
+
|
|
252
|
+
@mcp.tool()
|
|
253
|
+
async def long_task(files: list[str], ctx: Context) -> str:
|
|
254
|
+
"""Process multiple files with progress tracking"""
|
|
255
|
+
for i, file in enumerate(files):
|
|
256
|
+
ctx.info(f"Processing {file}")
|
|
257
|
+
await ctx.report_progress(i, len(files))
|
|
258
|
+
|
|
259
|
+
# Read another resource if needed
|
|
260
|
+
data = await ctx.read_resource(f"file://{file}")
|
|
261
|
+
|
|
262
|
+
return "Processing complete"
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
The Context object provides:
|
|
266
|
+
- Progress reporting through `report_progress()`
|
|
267
|
+
- Logging via `debug()`, `info()`, `warning()`, and `error()`
|
|
268
|
+
- Resource access through `read_resource()`
|
|
269
|
+
- Request metadata via `request_id` and `client_id`
|
|
270
|
+
|
|
271
|
+
## Deployment
|
|
272
|
+
|
|
273
|
+
The FastMCP CLI helps you develop and deploy MCP servers.
|
|
274
|
+
|
|
275
|
+
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`.
|
|
276
|
+
|
|
277
|
+
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`.
|
|
278
|
+
|
|
279
|
+
### Development
|
|
280
|
+
|
|
281
|
+
Test and debug your server with the MCP Inspector:
|
|
282
|
+
```bash
|
|
283
|
+
# Provide the fully qualified path to your server
|
|
284
|
+
fastmcp dev server.py:my_mcp_server
|
|
285
|
+
|
|
286
|
+
# Or just the file if your server is named 'mcp', 'server', or 'app'
|
|
287
|
+
fastmcp dev server.py
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
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:
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
# With additional packages
|
|
294
|
+
fastmcp dev server.py --with pandas --with numpy
|
|
295
|
+
|
|
296
|
+
# Using your project's dependencies and up-to-date code
|
|
297
|
+
fastmcp dev server.py --with-editable .
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Claude Desktop
|
|
301
|
+
|
|
302
|
+
Install your server in Claude Desktop:
|
|
303
|
+
```bash
|
|
304
|
+
# Basic usage (name is taken from your FastMCP instance)
|
|
305
|
+
fastmcp install server.py
|
|
306
|
+
|
|
307
|
+
# With a custom name
|
|
308
|
+
fastmcp install server.py --name "My Server"
|
|
309
|
+
|
|
310
|
+
# With dependencies
|
|
311
|
+
fastmcp install server.py --with pandas --with numpy
|
|
312
|
+
|
|
313
|
+
# Replace an existing server
|
|
314
|
+
fastmcp install server.py --force
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
The server name in Claude will be:
|
|
318
|
+
1. The `--name` parameter if provided
|
|
319
|
+
2. The `name` from your FastMCP instance
|
|
320
|
+
3. The filename if the server can't be imported
|
|
321
|
+
|
|
322
|
+
## Examples
|
|
323
|
+
|
|
324
|
+
### Echo Server
|
|
325
|
+
A simple server demonstrating resources, tools, and prompts:
|
|
326
|
+
|
|
327
|
+
```python
|
|
328
|
+
from fastmcp import FastMCP
|
|
329
|
+
|
|
330
|
+
mcp = FastMCP("Echo")
|
|
331
|
+
|
|
332
|
+
@mcp.resource("echo://{message}")
|
|
333
|
+
def echo_resource(message: str) -> str:
|
|
334
|
+
"""Echo a message as a resource"""
|
|
335
|
+
return f"Resource echo: {message}"
|
|
336
|
+
|
|
337
|
+
@mcp.tool()
|
|
338
|
+
def echo_tool(message: str) -> str:
|
|
339
|
+
"""Echo a message as a tool"""
|
|
340
|
+
return f"Tool echo: {message}"
|
|
341
|
+
|
|
342
|
+
@mcp.prompt()
|
|
343
|
+
def echo_prompt(message: str) -> str:
|
|
344
|
+
"""Create an echo prompt"""
|
|
345
|
+
return f"Please process this message: {message}"
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### SQLite Explorer
|
|
349
|
+
A more complex example showing database integration:
|
|
350
|
+
|
|
351
|
+
```python
|
|
352
|
+
from fastmcp import FastMCP
|
|
353
|
+
import sqlite3
|
|
354
|
+
|
|
355
|
+
mcp = FastMCP("SQLite Explorer")
|
|
356
|
+
|
|
357
|
+
@mcp.resource("schema://main")
|
|
358
|
+
def get_schema() -> str:
|
|
359
|
+
"""Provide the database schema as a resource"""
|
|
360
|
+
conn = sqlite3.connect("database.db")
|
|
361
|
+
schema = conn.execute(
|
|
362
|
+
"SELECT sql FROM sqlite_master WHERE type='table'"
|
|
363
|
+
).fetchall()
|
|
364
|
+
return "\n".join(sql[0] for sql in schema if sql[0])
|
|
365
|
+
|
|
366
|
+
@mcp.tool()
|
|
367
|
+
def query_data(sql: str) -> str:
|
|
368
|
+
"""Execute SQL queries safely"""
|
|
369
|
+
conn = sqlite3.connect("database.db")
|
|
370
|
+
try:
|
|
371
|
+
result = conn.execute(sql).fetchall()
|
|
372
|
+
return "\n".join(str(row) for row in result)
|
|
373
|
+
except Exception as e:
|
|
374
|
+
return f"Error: {str(e)}"
|
|
375
|
+
|
|
376
|
+
@mcp.prompt()
|
|
377
|
+
def analyze_table(table: str) -> str:
|
|
378
|
+
"""Create a prompt template for analyzing tables"""
|
|
379
|
+
return f"""Please analyze this database table:
|
|
380
|
+
Table: {table}
|
|
381
|
+
Schema:
|
|
382
|
+
{get_schema()}
|
|
383
|
+
|
|
384
|
+
What insights can you provide about the structure and relationships?"""
|
|
385
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
fastmcp/__init__.py,sha256=Y5dHGBwyQPgNP5gzOyNIItefvMZ3vJLdom1oV8A1u_k,248
|
|
2
|
+
fastmcp/exceptions.py,sha256=K0rCgXsUVlws39hz98Tb4BBf_BzIql_zXFZgqbkNTiE,348
|
|
3
|
+
fastmcp/server.py,sha256=gZCl4ppLWYjM4L6MXJxr-jh9ngjHjaFSFTLpofmgtmA,21987
|
|
4
|
+
fastmcp/cli/__init__.py,sha256=7hrwtCHX9nMd9qcz7R_JFSoqbL71fC35cBLXBS430mg,88
|
|
5
|
+
fastmcp/cli/claude.py,sha256=hId0cTmAfCrav72Hg5LeO0SPPNyEVtIOcoKVAy8gD3k,3390
|
|
6
|
+
fastmcp/cli/cli.py,sha256=Mru5j0_apXBnLBEd6DMICGPUk52DN3eyjSc1B973M2M,10028
|
|
7
|
+
fastmcp/prompts/__init__.py,sha256=4BsMxoYolpoxg74xkkkzCFL8vvdkLVJ5cIPNs1ND1Jo,99
|
|
8
|
+
fastmcp/prompts/base.py,sha256=WaSsfyFSsUPUbcApkGy3Pm-Ne-Gk-5ZwU3efqRYn1mQ,4996
|
|
9
|
+
fastmcp/prompts/manager.py,sha256=EkexOB_N4QNtC-UlZmIcWcau91ceO2O1K4_kD75pA_A,1485
|
|
10
|
+
fastmcp/prompts/prompt_manager.py,sha256=5uR14gsi7l0YHwbxFH7N5b_ACHHRWyTtBAH3R0-G5rk,1129
|
|
11
|
+
fastmcp/resources/__init__.py,sha256=9QShop6ckX3Khh3BQLZNkB6R2ZhwskAcnh7-sIuX-W8,464
|
|
12
|
+
fastmcp/resources/base.py,sha256=glGLpHp8Eq-LKq7YLJx3LRDiCktLlLaTTmltNcdWzHg,1818
|
|
13
|
+
fastmcp/resources/resource_manager.py,sha256=b0PKpG-pKi7x2Yx-qaeknjv0mqL17zixSIYOz2V5G6o,3271
|
|
14
|
+
fastmcp/resources/templates.py,sha256=EmLlI-ddBBzSTAUiA6-knFnHCE3MPMW2ZoH9WswPKvI,2868
|
|
15
|
+
fastmcp/resources/types.py,sha256=ofE6bfeQQfPSmaWrLGDf3qjCP0kGjKmvupsHDYkSrj0,5658
|
|
16
|
+
fastmcp/tools/__init__.py,sha256=ZboxhyMJDl87Svjov8YwNYwNZi55P9VhmpTjBZLesnk,96
|
|
17
|
+
fastmcp/tools/base.py,sha256=JPdTx8SAl5pKsHyIVxnsLG88f3fbjnopDTOAZ_PoVQE,2585
|
|
18
|
+
fastmcp/tools/tool_manager.py,sha256=PT6XHcQWzhdC6kfdsJaddRn7VLps4nAs5FMG8l1j8Zc,1617
|
|
19
|
+
fastmcp/utilities/__init__.py,sha256=-imJ8S-rXmbXMWeDamldP-dHDqAPg_wwmPVz-LNX14E,31
|
|
20
|
+
fastmcp/utilities/logging.py,sha256=t2w5bwtrkxHxioWSy5vY8esxLQxyxN8tfFZ1FI3Cb6E,704
|
|
21
|
+
fastmcp/utilities/types.py,sha256=jFlZMZsKrJg4NWc1vTBIILLoHpTVwSd-vxO7ycoRuig,1718
|
|
22
|
+
fastmcp-0.3.0.dist-info/METADATA,sha256=Nu76qWE1nxUHiHF4pczCTMUl8C2hKpXfIeo2Sp1UHxo,11452
|
|
23
|
+
fastmcp-0.3.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
24
|
+
fastmcp-0.3.0.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
|
|
25
|
+
fastmcp-0.3.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
26
|
+
fastmcp-0.3.0.dist-info/RECORD,,
|