Jeremiah Lowin commited on
Commit
6e94ee5
·
1 Parent(s): 908b68a
docs/docs.json CHANGED
@@ -37,7 +37,13 @@
37
  },
38
  {
39
  "group": "Servers",
40
- "pages": []
 
 
 
 
 
 
41
  },
42
  {
43
  "group": "Clients",
 
37
  },
38
  {
39
  "group": "Servers",
40
+ "pages": [
41
+ "servers/fastmcp",
42
+ "servers/tools",
43
+ "servers/resources",
44
+ "servers/prompts",
45
+ "servers/context"
46
+ ]
47
  },
48
  {
49
  "group": "Clients",
docs/servers/context.mdx ADDED
@@ -0,0 +1,281 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: MCP Context
3
+ sidebarTitle: Context
4
+ description: Access MCP capabilities like logging, progress, and resources within your tools.
5
+ icon: rectangle-code
6
+ ---
7
+
8
+ When defining FastMCP [Tools](/server/tools), your functions might need to interact with the underlying MCP session or access server capabilities. FastMCP provides the `Context` object for this purpose.
9
+
10
+ ## What Is Context?
11
+
12
+ The `Context` object provides a clean interface to access MCP features within your tool functions, including:
13
+
14
+ - **Logging**: Send debug, info, warning, and error messages back to the client
15
+ - **Progress Reporting**: Update the client on the progress of long-running operations
16
+ - **Resource Access**: Read data from resources registered with the server
17
+ - **LLM Sampling**: Request the client's LLM to generate text based on provided messages
18
+ - **Request Information**: Access metadata about the current request
19
+ - **Server Access**: When needed, access the underlying FastMCP server instance
20
+
21
+ ## Accessing Context
22
+
23
+ To use the context object within your tool function, simply add a parameter to your function signature and type-hint it as `Context`. FastMCP will automatically inject the context instance when your tool is called.
24
+
25
+ ```python
26
+ from fastmcp import FastMCP, Context
27
+
28
+ mcp = FastMCP(name="ContextDemo")
29
+
30
+ @mcp.tool()
31
+ async def process_file(file_uri: str, ctx: Context) -> str:
32
+ """Processes a file, using context for logging and resource access."""
33
+ request_id = ctx.request_id
34
+ await ctx.info(f"[{request_id}] Starting processing for {file_uri}")
35
+
36
+ try:
37
+ # Use context to read a resource
38
+ contents_list = await ctx.read_resource(file_uri)
39
+ if not contents_list:
40
+ await ctx.warning(f"Resource {file_uri} is empty.")
41
+ return "Resource empty"
42
+
43
+ data = contents_list[0].content # Assuming TextResourceContents
44
+ await ctx.debug(f"Read {len(data)} bytes from {file_uri}")
45
+
46
+ # Report progress
47
+ await ctx.report_progress(progress=50, total=100)
48
+
49
+ # Simulate work
50
+ processed_data = data.upper() # Example processing
51
+
52
+ await ctx.report_progress(progress=100, total=100)
53
+ await ctx.info(f"Processing complete for {file_uri}")
54
+
55
+ return f"Processed data length: {len(processed_data)}"
56
+
57
+ except Exception as e:
58
+ # Use context to log errors
59
+ await ctx.error(f"Error processing {file_uri}: {str(e)}")
60
+ raise # Re-raise to send error back to client
61
+ ```
62
+
63
+ **Key Points:**
64
+
65
+ - The parameter name (e.g., `ctx`, `context`) doesn't matter, only the type hint `Context` is important.
66
+ - The context parameter can be placed anywhere in your function's signature.
67
+ - The context is optional - tools that don't need it can omit the parameter.
68
+ - Context is only available within tool functions during a request; attempting to use context methods outside a request will raise errors.
69
+ - Context methods are async, so your tool function usually needs to be async as well.
70
+
71
+ ## Context Capabilities
72
+
73
+ ### Logging
74
+
75
+ Send log messages back to the MCP client. This is useful for debugging and providing visibility into tool execution during a request.
76
+
77
+ ```python
78
+ @mcp.tool()
79
+ async def analyze_data(data: list[float], ctx: Context) -> dict:
80
+ """Analyze numerical data with logging."""
81
+ await ctx.debug("Starting analysis of numerical data")
82
+ await ctx.info(f"Analyzing {len(data)} data points")
83
+
84
+ try:
85
+ result = sum(data) / len(data)
86
+ await ctx.info(f"Analysis complete, average: {result}")
87
+ return {"average": result, "count": len(data)}
88
+ except ZeroDivisionError:
89
+ await ctx.warning("Empty data list provided")
90
+ return {"error": "Empty data list"}
91
+ except Exception as e:
92
+ await ctx.error(f"Analysis failed: {str(e)}")
93
+ raise
94
+ ```
95
+
96
+ **Available Logging Methods:**
97
+
98
+ - **`ctx.debug(message: str)`**: Low-level details useful for debugging
99
+ - **`ctx.info(message: str)`**: General information about tool execution
100
+ - **`ctx.warning(message: str)`**: Potential issues that didn't prevent execution
101
+ - **`ctx.error(message: str)`**: Errors that occurred during execution
102
+ - **`ctx.log(level: Literal["debug", "info", "warning", "error"], message: str, logger_name: str | None = None)`**: Generic log method supporting custom logger names
103
+
104
+ ### Progress Reporting
105
+
106
+ For long-running tools, notify the client about the progress of the operation. This allows clients to display progress indicators and provide a better user experience.
107
+
108
+ ```python
109
+ @mcp.tool()
110
+ async def process_items(items: list[str], ctx: Context) -> dict:
111
+ """Process a list of items with progress updates."""
112
+ total = len(items)
113
+ results = []
114
+
115
+ for i, item in enumerate(items):
116
+ # Report progress as percentage
117
+ await ctx.report_progress(progress=i, total=total)
118
+
119
+ # Process the item (simulated with a sleep)
120
+ await asyncio.sleep(0.1)
121
+ results.append(item.upper())
122
+
123
+ # Report 100% completion
124
+ await ctx.report_progress(progress=total, total=total)
125
+
126
+ return {"processed": len(results), "results": results}
127
+ ```
128
+
129
+ **Method signature:**
130
+
131
+ - **`ctx.report_progress(progress: float, total: float | None = None)`**
132
+ - `progress`: Current progress value (e.g., 24)
133
+ - `total`: Optional total value (e.g., 100). If provided, clients may interpret this as a percentage.
134
+
135
+ Progress reporting requires the client to have sent a `progressToken` in the initial request. If the client doesn't support progress reporting, these calls will have no effect.
136
+
137
+ ### Resource Access
138
+
139
+ Read data from resources registered with your FastMCP server. This allows tools to access files, configuration, or dynamically generated content.
140
+
141
+ ```python
142
+ @mcp.tool()
143
+ async def summarize_document(document_uri: str, ctx: Context) -> str:
144
+ """Summarize a document by its resource URI."""
145
+ # Read the document content
146
+ content_list = await ctx.read_resource(document_uri)
147
+
148
+ if not content_list:
149
+ return "Document is empty"
150
+
151
+ document_text = content_list[0].content
152
+
153
+ # Example: Generate a simple summary (length-based)
154
+ words = document_text.split()
155
+ total_words = len(words)
156
+
157
+ await ctx.info(f"Document has {total_words} words")
158
+
159
+ # Return a simple summary
160
+ if total_words > 100:
161
+ summary = " ".join(words[:100]) + "..."
162
+ return f"Summary ({total_words} words total): {summary}"
163
+ else:
164
+ return f"Full document ({total_words} words): {document_text}"
165
+ ```
166
+
167
+ **Method signature:**
168
+
169
+ - **`ctx.read_resource(uri: str | AnyUrl) -> list[ReadResourceContents]`**
170
+ - `uri`: The resource URI to read
171
+ - Returns a list of resource content parts (usually containing just one item)
172
+
173
+ The returned content is typically accessed via `content_list[0].content` and can be text or binary data depending on the resource.
174
+
175
+ ### LLM Sampling
176
+
177
+ Request the client's LLM to generate text based on provided messages. This is useful when your tool needs to leverage the LLM's capabilities to process data or generate responses.
178
+
179
+ ```python
180
+ @mcp.tool()
181
+ async def analyze_sentiment(text: str, ctx: Context) -> dict:
182
+ """Analyze the sentiment of a text using the client's LLM."""
183
+ # Create a sampling prompt asking for sentiment analysis
184
+ prompt = f"Analyze the sentiment of the following text as positive, negative, or neutral. Just output a single word - 'positive', 'negative', or 'neutral'. Text to analyze: {text}"
185
+
186
+ # Send the sampling request to the client's LLM
187
+ response = await ctx.sample(prompt)
188
+
189
+ # Process the LLM's response
190
+ sentiment = response.text.strip().lower()
191
+
192
+ # Map to standard sentiment values
193
+ if "positive" in sentiment:
194
+ sentiment = "positive"
195
+ elif "negative" in sentiment:
196
+ sentiment = "negative"
197
+ else:
198
+ sentiment = "neutral"
199
+
200
+ return {"text": text, "sentiment": sentiment}
201
+ ```
202
+
203
+ **Method signature:**
204
+
205
+ - **`ctx.sample(messages: str | list[str | SamplingMessage], system_prompt: str | None = None, temperature: float | None = None, max_tokens: int | None = None) -> TextContent | ImageContent`**
206
+ - `messages`: A string or list of strings/message objects to send to the LLM
207
+ - `system_prompt`: Optional system prompt to guide the LLM's behavior
208
+ - `temperature`: Optional sampling temperature (controls randomness)
209
+ - `max_tokens`: Optional maximum number of tokens to generate (defaults to 512)
210
+ - Returns the LLM's response as TextContent or ImageContent
211
+
212
+ When providing a simple string, it's treated as a user message. For more complex scenarios, you can provide a list of messages with different roles.
213
+
214
+ ```python
215
+ @mcp.tool()
216
+ async def generate_example(concept: str, ctx: Context) -> str:
217
+ """Generate a Python code example for a given concept."""
218
+ # Using a system prompt and a user message
219
+ response = await ctx.sample(
220
+ messages=f"Write a simple Python code example demonstrating '{concept}'.",
221
+ system_prompt="You are an expert Python programmer. Provide concise, working code examples without explanations.",
222
+ temperature=0.7,
223
+ max_tokens=300
224
+ )
225
+
226
+ code_example = response.text
227
+ return f"```python\n{code_example}\n```"
228
+ ```
229
+
230
+ See [Client Sampling](/client/sampling) for more details on how clients handle these requests.
231
+
232
+ ### Request Information
233
+
234
+ Access metadata about the current request and client.
235
+
236
+ ```python
237
+ @mcp.tool()
238
+ async def request_info(ctx: Context) -> dict:
239
+ """Return information about the current request."""
240
+ return {
241
+ "request_id": ctx.request_id,
242
+ "client_id": ctx.client_id or "Unknown client"
243
+ }
244
+ ```
245
+
246
+ **Available Properties:**
247
+
248
+ - **`ctx.request_id -> str`**: Get the unique ID for the current MCP request
249
+ - **`ctx.client_id -> str | None`**: Get the ID of the client making the request, if provided during initialization
250
+
251
+ ### Advanced Access
252
+
253
+ For advanced use cases, you can access the underlying MCP session and FastMCP server.
254
+
255
+ ```python
256
+ @mcp.tool()
257
+ async def advanced_tool(ctx: Context) -> str:
258
+ """Demonstrate advanced context access."""
259
+ # Access the FastMCP server instance
260
+ server_name = ctx.fastmcp.name
261
+
262
+ # Low-level session access (rarely needed)
263
+ session = ctx.session
264
+ request_context = ctx.request_context
265
+
266
+ return f"Server: {server_name}"
267
+ ```
268
+
269
+ **Advanced Properties:**
270
+
271
+ - **`ctx.fastmcp -> FastMCP`**: Access the server instance the context belongs to
272
+ - **`ctx.session`**: Access the raw `mcp.server.session.ServerSession` object
273
+ - **`ctx.request_context`**: Access the raw `mcp.shared.context.RequestContext` object
274
+
275
+ <Warning>
276
+ Direct use of `session` or `request_context` requires understanding the low-level MCP Python SDK and may be less stable than using the methods provided directly on the `Context` object.
277
+ </Warning>
278
+
279
+ ## Using Context in Other Components
280
+
281
+ Currently, Context is primarily designed for use within tool functions. Support for Context in other components like resources and prompts is planned for future releases.
docs/servers/fastmcp.mdx ADDED
@@ -0,0 +1,302 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: The FastMCP Server
3
+ sidebarTitle: FastMCP Server
4
+ description: Learn about the core FastMCP server class and how to run it.
5
+ icon: server
6
+ ---
7
+
8
+ The central piece of a FastMCP application is the `FastMCP` server class. This class acts as the main container for your application's tools, resources, and prompts, and manages communication with MCP clients.
9
+
10
+ ## Creating a Server
11
+
12
+ Instantiating a server is straightforward. You typically provide a name for your server, which helps identify it in client applications or logs.
13
+
14
+ ```python
15
+ from fastmcp import FastMCP
16
+
17
+ # Create a basic server instance
18
+ mcp = FastMCP(name="MyAssistantServer")
19
+
20
+ # You can also add instructions for how to interact with the server
21
+ mcp_with_instructions = FastMCP(
22
+ name="HelpfulAssistant",
23
+ instructions="This server provides data analysis tools. Call get_average() to analyze numerical data."
24
+ )
25
+ ```
26
+
27
+ The `FastMCP` constructor accepts several arguments:
28
+
29
+ * `name`: (Optional) A human-readable name for your server. Defaults to "FastMCP".
30
+ * `instructions`: (Optional) Description of how to interact with this server. These instructions help clients understand the server's purpose and available functionality.
31
+ * `lifespan`: (Optional) An async context manager function for server startup and shutdown logic. See [Lifespan Management](/advanced/lifespan).
32
+ * `tags`: (Optional) A set of strings to tag the server itself.
33
+ * `**settings`: Keyword arguments corresponding to `ServerSettings` for configuration. See [Configuration](/advanced/configuration).
34
+
35
+ ## Components
36
+
37
+ FastMCP servers expose several types of components to the client:
38
+
39
+ ### Tools
40
+
41
+ Tools are functions that the client can call to perform actions or access external systems.
42
+
43
+ ```python
44
+ @mcp.tool()
45
+ def multiply(a: float, b: float) -> float:
46
+ """Multiplies two numbers together."""
47
+ return a * b
48
+ ```
49
+
50
+ See [Tools](/server/tools) for detailed documentation.
51
+
52
+ ### Resources
53
+
54
+ Resources expose data sources that the client can read.
55
+
56
+ ```python
57
+ @mcp.resource("data://config")
58
+ def get_config() -> dict:
59
+ """Provides the application configuration."""
60
+ return {"theme": "dark", "version": "1.0"}
61
+ ```
62
+
63
+ See [Resources & Templates](/server/resources) for detailed documentation.
64
+
65
+ ### Resource Templates
66
+
67
+ Resource templates are parameterized resources that allow the client to request specific data.
68
+
69
+ ```python
70
+ @mcp.resource("users://{user_id}/profile")
71
+ def get_user_profile(user_id: int) -> dict:
72
+ """Retrieves a user's profile by ID."""
73
+ # The {user_id} in the URI is extracted and passed to this function
74
+ return {"id": user_id, "name": f"User {user_id}", "status": "active"}
75
+ ```
76
+
77
+ See [Resources & Templates](/server/resources) for detailed documentation.
78
+
79
+ ### Prompts
80
+
81
+ Prompts are reusable message templates for guiding the LLM.
82
+
83
+ ```python
84
+ @mcp.prompt()
85
+ def analyze_data(data_points: list[float]) -> str:
86
+ """Creates a prompt asking for analysis of numerical data."""
87
+ formatted_data = ", ".join(str(point) for point in data_points)
88
+ return f"Please analyze these data points: {formatted_data}"
89
+ ```
90
+
91
+ See [Prompts](/server/prompts) for detailed documentation.
92
+
93
+ ## Running the Server
94
+
95
+ FastMCP servers need a transport mechanism to communicate with clients. In the MCP protocol, servers typically run as separate processes that clients connect to.
96
+
97
+ ### The `__main__` Block Pattern
98
+
99
+ The standard way to make your server executable is to include a `run()` call inside an `if __name__ == "__main__":` block:
100
+
101
+ ```python
102
+ # my_server.py
103
+ from fastmcp import FastMCP
104
+
105
+ mcp = FastMCP(name="MyServer")
106
+
107
+ @mcp.tool()
108
+ def greet(name: str) -> str:
109
+ """Greet a user by name."""
110
+ return f"Hello, {name}!"
111
+
112
+ if __name__ == "__main__":
113
+ # This code only runs when the file is executed directly
114
+ mcp.run()
115
+ ```
116
+
117
+ This pattern is important because:
118
+
119
+ 1. **Client Compatibility**: Standard MCP clients (like Claude Desktop) expect to execute your server file directly with `python my_server.py`
120
+ 2. **Process Isolation**: Each server runs in its own process, allowing clients to manage multiple servers independently
121
+ 3. **Import Safety**: The main block prevents the server from running when the file is imported by other code
122
+
123
+ While this pattern is technically optional when using FastMCP's CLI, it's considered a best practice for maximum compatibility with all MCP clients.
124
+
125
+ ### Transport Options
126
+
127
+ FastMCP supports two transport mechanisms:
128
+
129
+ #### STDIO Transport (Default)
130
+
131
+ The standard input/output (STDIO) transport is the default and most widely compatible option:
132
+
133
+ ```python
134
+ # Run with stdio (default)
135
+ mcp.run() # or explicitly: mcp.run(transport="stdio")
136
+ ```
137
+
138
+ With STDIO:
139
+ - The client starts a new server process for each session
140
+ - Communication happens through standard input/output streams
141
+ - The server process terminates when the client disconnects
142
+ - This is ideal for integrations with tools like Claude Desktop, where each conversation gets its own server instance
143
+
144
+ #### SSE Transport (Server-Sent Events)
145
+
146
+ For long-running servers that serve multiple clients, FastMCP supports SSE:
147
+
148
+ ```python
149
+ # Run with SSE on default host/port (0.0.0.0:8000)
150
+ mcp.run(transport="sse")
151
+ ```
152
+
153
+ With SSE:
154
+ - The server runs as a persistent web server
155
+ - Multiple clients can connect simultaneously
156
+ - The server stays running until explicitly terminated
157
+ - This is ideal for remote access to services
158
+
159
+ You can configure the host, port, and log level when running the server:
160
+
161
+ ```python
162
+ # Configure with parameters
163
+ mcp.run(transport="sse", host="127.0.0.1", port=8888)
164
+
165
+ # Or run asynchronously with the same parameters
166
+ import asyncio
167
+ asyncio.run(mcp.run_sse_async(host="127.0.0.1", port=8888, log_level="debug"))
168
+ ```
169
+
170
+ These parameters override any settings defined when creating the FastMCP instance.
171
+
172
+ ### Using the FastMCP CLI
173
+
174
+ The FastMCP CLI provides a convenient way to run servers:
175
+
176
+ ```bash
177
+ # Run a server (defaults to stdio transport)
178
+ fastmcp run my_server.py:mcp
179
+
180
+ # Explicitly specify a transport
181
+ fastmcp run my_server.py:mcp --transport sse
182
+
183
+ # Configure SSE transport
184
+ fastmcp run my_server.py:mcp --transport sse --host 127.0.0.1 --port 8888
185
+ ```
186
+
187
+ The CLI can dynamically find and run FastMCP server objects in your files, but including the `if __name__ == "__main__":` block ensures compatibility with all clients.
188
+
189
+ <Tip>
190
+ For more options, including how to set up your server's dependencies or use advanced configurations, see the [CLI Reference](/cli/overview).
191
+ </Tip>
192
+
193
+ ## Mounting Subservers
194
+
195
+ FastMCP allows you to compose complex applications by mounting other FastMCP servers as subservers. This is useful for:
196
+
197
+ - Organizing large applications into logical components
198
+ - Reusing existing FastMCP servers as parts of a larger system
199
+ - Creating domain-specific servers that can be used independently or composed
200
+
201
+ ```python
202
+ from fastmcp import FastMCP
203
+
204
+ # Create the main server
205
+ main_mcp = FastMCP(name="MainServer")
206
+
207
+ # Create a domain-specific subserver
208
+ weather_mcp = FastMCP(name="WeatherService")
209
+
210
+ @weather_mcp.tool()
211
+ def get_forecast(city: str) -> dict:
212
+ """Get the weather forecast for a city."""
213
+ return {"city": city, "forecast": "Sunny", "temperature": 72}
214
+
215
+ # Create another domain-specific subserver
216
+ calculator_mcp = FastMCP(name="CalculatorService")
217
+
218
+ @calculator_mcp.tool()
219
+ def add(a: float, b: float) -> float:
220
+ """Add two numbers."""
221
+ return a + b
222
+
223
+ # Mount the subservers with prefixes
224
+ main_mcp.mount("weather", weather_mcp)
225
+ main_mcp.mount("calc", calculator_mcp)
226
+
227
+ # Now main_mcp has access to both subservers' tools:
228
+ # - "weather_get_forecast" (from weather_mcp)
229
+ # - "calc_add" (from calculator_mcp)
230
+
231
+ if __name__ == "__main__":
232
+ main_mcp.run()
233
+ ```
234
+
235
+ ### How Mounting Works
236
+
237
+ When you mount a server with `main_mcp.mount(prefix, subserver)`:
238
+
239
+ 1. All tools from the subserver are imported with prefixed names:
240
+ - `tool_name` becomes `{prefix}_tool_name`
241
+ - Default separator is `_`, but can be customized
242
+
243
+ 2. All resources and resource templates are imported with prefixed URIs:
244
+ - `resource://data` becomes `{prefix}+resource://data`
245
+ - Default separator is `+`, but can be customized
246
+
247
+ 3. All prompts are imported with prefixed names:
248
+ - `prompt_name` becomes `{prefix}_prompt_name`
249
+ - Default separator is `_`, but can be customized
250
+
251
+ 4. The subserver's lifespan is managed automatically when the main server starts and stops
252
+
253
+ ### Customizing Separators
254
+
255
+ You can customize the separators used for naming:
256
+
257
+ ```python
258
+ main_mcp.mount(
259
+ "weather",
260
+ weather_mcp,
261
+ tool_separator="-", # Use "weather-get_forecast" instead of "weather_get_forecast"
262
+ resource_separator=".", # Use "weather.resource://data" instead of "weather+resource://data"
263
+ prompt_separator=":" # Use "weather:prompt_name" instead of "weather_prompt_name"
264
+ )
265
+ ```
266
+
267
+ <Warning>
268
+ Some MCP clients may reject certain separators as invalid. For example, Claude Desktop does not support `/` in tool names.
269
+ </Warning>
270
+
271
+ ## Server Configuration
272
+
273
+ Server behavior, like transport settings (host, port for SSE) and how duplicate components are handled, can be configured via `ServerSettings`. These settings can be passed during `FastMCP` initialization, set via environment variables (prefixed with `FASTMCP_SERVER_`), or loaded from a `.env` file.
274
+
275
+ ```python
276
+ from fastmcp import FastMCP
277
+ from fastmcp.settings import DuplicateBehavior
278
+
279
+ # Configure during initialization
280
+ mcp = FastMCP(
281
+ name="ConfiguredServer",
282
+ port=8080, # Directly maps to ServerSettings
283
+ on_duplicate_tools=DuplicateBehavior.ERROR # Set duplicate handling
284
+ )
285
+
286
+ # Settings are accessible via mcp.settings
287
+ print(mcp.settings.port) # Output: 8080
288
+ print(mcp.settings.on_duplicate_tools) # Output: DuplicateBehavior.ERROR
289
+ ```
290
+
291
+ ### Key Configuration Options
292
+
293
+ - **`host`**: Host address for SSE transport (default: "0.0.0.0")
294
+ - **`port`**: Port number for SSE transport (default: 8000)
295
+ - **`log_level`**: Logging level (default: "INFO")
296
+ - **`on_duplicate_tools`**: How to handle duplicate tool registrations
297
+ - **`on_duplicate_resources`**: How to handle duplicate resource registrations
298
+ - **`on_duplicate_prompts`**: How to handle duplicate prompt registrations
299
+
300
+ All of these can be configured directly as parameters when creating the `FastMCP` instance.
301
+
302
+ See the [Configuration](/advanced/configuration) page for more details.
docs/servers/prompts.mdx ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Prompts
3
+ sidebarTitle: Prompts
4
+ description: Create reusable, parameterized prompt templates for MCP clients.
5
+ icon: message-lines
6
+ ---
7
+
8
+ Prompts are reusable message templates that help LLMs generate structured, purposeful responses. FastMCP simplifies defining these templates, primarily using the `@mcp.prompt` decorator.
9
+
10
+ ## What Are Prompts?
11
+
12
+ Prompts provide parameterized message templates for LLMs. When a client requests a prompt:
13
+
14
+ 1. FastMCP finds the corresponding prompt definition.
15
+ 2. If it has parameters, they are validated against your function signature.
16
+ 3. Your function executes with the validated inputs.
17
+ 4. The generated message(s) are returned to the LLM to guide its response.
18
+
19
+ This allows you to define consistent, reusable templates that LLMs can use across different clients and contexts.
20
+
21
+ ## Defining Prompts
22
+
23
+ ### The `@prompt` Decorator
24
+
25
+ The most common way to define a prompt is by decorating a Python function. The decorator uses the function name as the prompt's identifier.
26
+
27
+ ```python
28
+ from fastmcp import FastMCP
29
+ from fastmcp.prompts.prompt import UserMessage, AssistantMessage, Message
30
+
31
+ mcp = FastMCP(name="PromptServer")
32
+
33
+ # Basic prompt returning a string (converted to UserMessage)
34
+ @mcp.prompt()
35
+ def ask_about_topic(topic: str) -> str:
36
+ """Generates a user message asking for an explanation of a topic."""
37
+ return f"Can you please explain the concept of '{topic}'?"
38
+
39
+ # Prompt returning a specific message type
40
+ @mcp.prompt()
41
+ def generate_code_request(language: str, task_description: str) -> UserMessage:
42
+ """Generates a user message requesting code generation."""
43
+ content = f"Write a {language} function that performs the following task: {task_description}"
44
+ return UserMessage(content=content)
45
+ ```
46
+
47
+ **Key Concepts:**
48
+
49
+ * **Name:** By default, the prompt name is taken from the function name.
50
+ * **Parameters:** The function parameters define the inputs needed to generate the prompt.
51
+ * **Inferred Metadata:** By default:
52
+ * Prompt Name: Taken from the function name (`ask_about_topic`).
53
+ * Prompt Description: Taken from the function's docstring.
54
+
55
+ ### Return Values
56
+
57
+ FastMCP intelligently handles different return types from your prompt function:
58
+
59
+ - **`str`**: Automatically converted to a single `UserMessage`.
60
+ - **`Message`** (e.g., `UserMessage`, `AssistantMessage`): Used directly as provided.
61
+ - **`dict`**: Parsed as a `Message` object if it has the correct structure.
62
+ - **`list[Message]`**: Used as a sequence of messages (a conversation).
63
+
64
+ ```python
65
+ @mcp.prompt()
66
+ def roleplay_scenario(character: str, situation: str) -> list[Message]:
67
+ """Sets up a roleplaying scenario with initial messages."""
68
+ return [
69
+ UserMessage(f"Let's roleplay. You are {character}. The situation is: {situation}"),
70
+ AssistantMessage("Okay, I understand. I am ready. What happens next?")
71
+ ]
72
+
73
+ @mcp.prompt()
74
+ def ask_for_feedback() -> dict:
75
+ """Generates a user message asking for feedback."""
76
+ return {"role": "user", "content": "What did you think of my previous response?"}
77
+ ```
78
+
79
+ ### Type Annotations
80
+
81
+ Type annotations are important for prompts. They:
82
+ 1. Inform FastMCP about the expected types for each parameter.
83
+ 2. Allow validation of parameters received from clients.
84
+ 3. Are used to generate the prompt's schema for the MCP protocol.
85
+
86
+ ```python
87
+ from pydantic import Field
88
+ from typing import Literal, Optional
89
+
90
+ @mcp.prompt()
91
+ def generate_content_request(
92
+ topic: str = Field(description="The main subject to cover"),
93
+ format: Literal["blog", "email", "social"] = "blog",
94
+ tone: str = "professional",
95
+ word_count: Optional[int] = None
96
+ ) -> str:
97
+ """Create a request for generating content in a specific format."""
98
+ prompt = f"Please write a {format} post about {topic} in a {tone} tone."
99
+
100
+ if word_count:
101
+ prompt += f" It should be approximately {word_count} words long."
102
+
103
+ return prompt
104
+ ```
105
+
106
+ ### Required vs. Optional Parameters
107
+
108
+ Parameters in your function signature are considered **required** unless they have a default value.
109
+
110
+ ```python
111
+ @mcp.prompt()
112
+ def data_analysis_prompt(
113
+ data_uri: str, # Required - no default value
114
+ analysis_type: str = "summary", # Optional - has default value
115
+ include_charts: bool = False # Optional - has default value
116
+ ) -> str:
117
+ """Creates a request to analyze data with specific parameters."""
118
+ prompt = f"Please perform a '{analysis_type}' analysis on the data found at {data_uri}."
119
+ if include_charts:
120
+ prompt += " Include relevant charts and visualizations."
121
+ return prompt
122
+ ```
123
+
124
+ In this example, the client *must* provide `data_uri`. If `analysis_type` or `include_charts` are omitted, their default values will be used.
125
+
126
+ ### Prompt Metadata
127
+
128
+ While FastMCP infers the name and description from your function, you can override these and add tags using arguments to the `@mcp.prompt` decorator:
129
+
130
+ ```python
131
+ @mcp.prompt(
132
+ name="analyze_data_request", # Custom prompt name
133
+ description="Creates a request to analyze data with specific parameters", # Custom description
134
+ tags={"analysis", "data"} # Optional categorization tags
135
+ )
136
+ def data_analysis_prompt(
137
+ data_uri: str = Field(description="The URI of the resource containing the data."),
138
+ analysis_type: str = Field(default="summary", description="Type of analysis.")
139
+ ) -> str:
140
+ """This docstring is ignored when description is provided."""
141
+ return f"Please perform a '{analysis_type}' analysis on the data found at {data_uri}."
142
+ ```
143
+
144
+ - **`name`**: Sets the explicit prompt name exposed via MCP.
145
+ - **`description`**: Provides the description exposed via MCP. If set, the function's docstring is ignored for this purpose.
146
+ - **`tags`**: A set of strings used to categorize the prompt. Clients *might* use tags to filter or group available prompts.
147
+
148
+ ### Asynchronous Prompts
149
+
150
+ FastMCP seamlessly supports both standard (`def`) and asynchronous (`async def`) functions as prompts.
151
+
152
+ ```python
153
+ # Synchronous prompt
154
+ @mcp.prompt()
155
+ def simple_question(question: str) -> str:
156
+ """Generates a simple question to ask the LLM."""
157
+ return f"Question: {question}"
158
+
159
+ # Asynchronous prompt
160
+ @mcp.prompt()
161
+ async def data_based_prompt(data_id: str) -> str:
162
+ """Generates a prompt based on data that needs to be fetched."""
163
+ # In a real scenario, you might fetch data from a database or API
164
+ async with aiohttp.ClientSession() as session:
165
+ async with session.get(f"https://api.example.com/data/{data_id}") as response:
166
+ data = await response.json()
167
+ return f"Analyze this data: {data['content']}"
168
+ ```
169
+
170
+ Use `async def` when your prompt function performs I/O operations like network requests, database queries, file I/O, or external service calls.
171
+
172
+ ### The MCP Session
173
+
174
+ Prompts can access the MCP features via the `Context` object, just like tools.
175
+
176
+ ```python
177
+ from fastmcp import Context
178
+
179
+ @mcp.prompt()
180
+ async def generate_report_request(report_type: str, ctx: Context) -> str:
181
+ """Generates a request for a report based on available data."""
182
+ # Log the request
183
+ await ctx.info(f"Generating prompt for report type: {report_type}")
184
+
185
+ # Could potentially use ctx.read_resource to fetch data
186
+ # Or ctx.sample to get additional input from the LLM
187
+
188
+ return f"Please create a {report_type} report based on the available data."
189
+ ```
190
+
191
+ Using the `ctx` parameter (based on its `Context` type hint), you can access:
192
+
193
+ - **Logging:** `ctx.debug()`, `ctx.info()`, etc.
194
+ - **Resource Access:** `ctx.read_resource(uri)`
195
+ - **LLM Sampling:** `ctx.sample(...)`
196
+ - **Request Info:** `ctx.request_id`, `ctx.client_id`
197
+
198
+ Refer to the [Using Context](/server/context) page for more details on these capabilities.
199
+
200
+ ## Server Behavior
201
+
202
+ ### Duplicate Prompts
203
+
204
+ You can configure how the FastMCP server handles attempts to register multiple prompts with the same name. Use the `on_duplicate_prompts` setting during `FastMCP` initialization.
205
+
206
+ ```python
207
+ from fastmcp import FastMCP
208
+ from fastmcp.settings import DuplicateBehavior
209
+
210
+ mcp = FastMCP(
211
+ name="PromptServer",
212
+ on_duplicate_prompts=DuplicateBehavior.ERROR # Raise an error if a prompt name is duplicated
213
+ )
214
+
215
+ @mcp.prompt()
216
+ def greeting(): return "Hello, how can I help you today?"
217
+
218
+ # This registration attempt will raise a ValueError because
219
+ # "greeting" is already registered and the behavior is ERROR.
220
+ # @mcp.prompt()
221
+ # def greeting(): return "Hi there! What can I do for you?"
222
+ ```
223
+
224
+ The `DuplicateBehavior` enum options are:
225
+
226
+ - `WARN` (default): Logs a warning, and the new prompt replaces the old one.
227
+ - `ERROR`: Raises a `ValueError`, preventing the duplicate registration.
228
+ - `REPLACE`: Silently replaces the existing prompt with the new one.
229
+ - `IGNORE`: Keeps the original prompt and ignores the new registration attempt.
docs/servers/resources.mdx ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Resources & Templates
3
+ sidebarTitle: Resources & Templates
4
+ description: Expose data sources and dynamic content generators to your MCP client.
5
+ icon: database
6
+ ---
7
+
8
+ Resources represent data or files that an MCP client can read, and resource templates extend this concept by allowing clients to request dynamically generated resources based on parameters passed in the URI.
9
+
10
+ FastMCP simplifies defining both static and dynamic resources, primarily using the `@mcp.resource` decorator.
11
+
12
+ ## What Are Resources?
13
+
14
+ Resources provide read-only access to data for the LLM or client application. When a client requests a resource URI:
15
+
16
+ 1. FastMCP finds the corresponding resource definition.
17
+ 2. If it's dynamic (defined by a function), the function is executed.
18
+ 3. The content (text, JSON, binary data) is returned to the client.
19
+
20
+ This allows LLMs to access files, database content, configuration, or dynamically generated information relevant to the conversation.
21
+
22
+ ## Defining Resources
23
+
24
+ ### The `@resource` Decorator
25
+
26
+ The most common way to define a resource is by decorating a Python function. The decorator requires the resource's unique URI.
27
+
28
+ ```python
29
+ import json
30
+ from fastmcp import FastMCP
31
+
32
+ mcp = FastMCP(name="DataServer")
33
+
34
+ # Basic dynamic resource returning a string
35
+ @mcp.resource("resource://greeting")
36
+ def get_greeting() -> str:
37
+ """Provides a simple greeting message."""
38
+ return "Hello from FastMCP Resources!"
39
+
40
+ # Resource returning JSON data (dict is auto-serialized)
41
+ @mcp.resource("data://config")
42
+ def get_config() -> dict:
43
+ """Provides application configuration as JSON."""
44
+ return {
45
+ "theme": "dark",
46
+ "version": "1.2.0",
47
+ "features": ["tools", "resources"],
48
+ }
49
+ ```
50
+
51
+ **Key Concepts:**
52
+
53
+ * **URI:** The first argument to `@resource` is the unique URI (e.g., `"resource://greeting"`) clients use to request this data.
54
+ * **Lazy Loading:** The decorated function (`get_greeting`, `get_config`) is only executed when a client specifically requests that resource URI via `resources/read`.
55
+ * **Inferred Metadata:** By default:
56
+ * Resource Name: Taken from the function name (`get_greeting`).
57
+ * Resource Description: Taken from the function's docstring.
58
+
59
+ ### Return Values
60
+
61
+ FastMCP automatically converts your function's return value into the appropriate MCP resource content:
62
+
63
+ - **`str`**: Sent as `TextResourceContents` (with `mime_type="text/plain"` by default).
64
+ - **`dict`, `list`, `pydantic.BaseModel`**: Automatically serialized to a JSON string and sent as `TextResourceContents` (with `mime_type="application/json"` by default).
65
+ - **`bytes`**: Base64 encoded and sent as `BlobResourceContents`. You should specify an appropriate `mime_type` (e.g., `"image/png"`, `"application/octet-stream"`).
66
+ - **`None`**: Results in an empty resource content list being returned.
67
+
68
+ ### Resource Metadata
69
+
70
+ You can customize the resource's properties using arguments in the decorator:
71
+
72
+ ```python
73
+ from fastmcp import FastMCP
74
+
75
+ mcp = FastMCP(name="DataServer")
76
+
77
+ # Example specifying metadata
78
+ @mcp.resource(
79
+ uri="data://app-status", # Explicit URI (required)
80
+ name="ApplicationStatus", # Custom name
81
+ description="Provides the current status of the application.", # Custom description
82
+ mime_type="application/json", # Explicit MIME type
83
+ tags={"monitoring", "status"} # Categorization tags
84
+ )
85
+ def get_application_status() -> dict:
86
+ """Internal function description (ignored if description is provided above)."""
87
+ return {"status": "ok", "uptime": 12345, "version": mcp.settings.version} # Example usage
88
+ ```
89
+
90
+ - **`uri`**: The unique identifier for the resource (required).
91
+ - **`name`**: A human-readable name (defaults to function name).
92
+ - **`description`**: Explanation of the resource (defaults to docstring).
93
+ - **`mime_type`**: Specifies the content type (FastMCP often infers a default like `text/plain` or `application/json`, but explicit is better for non-text types).
94
+ - **`tags`**: A set of strings for categorization, potentially used by clients for filtering.
95
+
96
+
97
+ ### Asynchronous Resources
98
+
99
+ Use `async def` for resource functions that perform I/O operations (e.g., reading from a database or network) to avoid blocking the server.
100
+
101
+ ```python
102
+ import aiofiles
103
+ from fastmcp import FastMCP
104
+
105
+ mcp = FastMCP(name="DataServer")
106
+
107
+ @mcp.resource("file:///app/data/important_log.txt", mime_type="text/plain")
108
+ async def read_important_log() -> str:
109
+ """Reads content from a specific log file asynchronously."""
110
+ try:
111
+ async with aiofiles.open("/app/data/important_log.txt", mode="r") as f:
112
+ content = await f.read()
113
+ return content
114
+ except FileNotFoundError:
115
+ return "Log file not found."
116
+ ```
117
+
118
+ ### Resource Classes
119
+
120
+ While `@mcp.resource` is ideal for dynamic content, you can directly register pre-defined resources (like static files or simple text) using `mcp.add_resource()` and concrete `Resource` subclasses.
121
+
122
+ ```python
123
+ from pathlib import Path
124
+ from fastmcp import FastMCP
125
+ from fastmcp.resources import FileResource, TextResource, DirectoryResource
126
+
127
+ mcp = FastMCP(name="DataServer")
128
+
129
+ # 1. Exposing a static file directly
130
+ readme_path = Path("./README.md").resolve()
131
+ if readme_path.exists():
132
+ # Use a file:// URI scheme
133
+ readme_resource = FileResource(
134
+ uri=f"file://{readme_path.as_posix()}",
135
+ path=readme_path, # Path to the actual file
136
+ name="README File",
137
+ description="The project's README.",
138
+ mime_type="text/markdown",
139
+ tags={"documentation"}
140
+ )
141
+ mcp.add_resource(readme_resource)
142
+
143
+ # 2. Exposing simple, predefined text
144
+ notice_resource = TextResource(
145
+ uri="resource://notice",
146
+ name="Important Notice",
147
+ text="System maintenance scheduled for Sunday.",
148
+ tags={"notification"}
149
+ )
150
+ mcp.add_resource(notice_resource)
151
+
152
+ # 3. Exposing a directory listing
153
+ data_dir_path = Path("./app_data").resolve()
154
+ if data_dir_path.is_dir():
155
+ data_listing_resource = DirectoryResource(
156
+ uri="resource://data-files",
157
+ path=data_dir_path, # Path to the directory
158
+ name="Data Directory Listing",
159
+ description="Lists files available in the data directory.",
160
+ recursive=False # Set to True to list subdirectories
161
+ )
162
+ mcp.add_resource(data_listing_resource) # Returns JSON list of files
163
+ ```
164
+
165
+ **Common Resource Classes:**
166
+
167
+ - `TextResource`: For simple string content.
168
+ - `BinaryResource`: For raw `bytes` content.
169
+ - `FileResource`: Reads content from a local file path. Handles text/binary modes and lazy reading.
170
+ - `HttpResource`: Fetches content from an HTTP(S) URL (requires `httpx`).
171
+ - `DirectoryResource`: Lists files in a local directory (returns JSON).
172
+ - (`FunctionResource`: Internal class used by `@mcp.resource`).
173
+
174
+ Use these when the content is static or sourced directly from a file/URL, bypassing the need for a dedicated Python function.
175
+
176
+ ## Defining Resource Templates
177
+
178
+ Resource Templates allow clients to request resources whose content depends on parameters embedded in the URI. Define a template using the **same `@mcp.resource` decorator**, but include `{parameter_name}` placeholders in the URI string and add corresponding arguments to your function signature.
179
+
180
+ ```python
181
+ from fastmcp import FastMCP
182
+
183
+ mcp = FastMCP(name="DataServer")
184
+
185
+ # Template URI includes {city} placeholder
186
+ @mcp.resource("data://weather/{city}")
187
+ # Function accepts 'city' parameter matching the placeholder
188
+ def get_weather_for_city(city: str) -> dict:
189
+ """Provides weather information for a specific city."""
190
+ print(f"Server: Generating weather for city: {city}...")
191
+ # In reality, call a weather API using the 'city' parameter
192
+ temp = 20 + len(city) % 5 # Dummy logic
193
+ condition = "Sunny" if len(city) % 2 == 0 else "Cloudy"
194
+ return {"city": city.capitalize(), "temperature": temp, "unit": "celsius", "condition": condition}
195
+
196
+ # Template with an integer parameter
197
+ @mcp.resource("users://{user_id}/profile")
198
+ async def get_user_profile(user_id: int) -> dict:
199
+ """Retrieves a user's profile information by ID."""
200
+ print(f"Server: Generating profile for user ID: {user_id}...")
201
+ # In reality, fetch from database using user_id
202
+ # FastMCP uses Pydantic to auto-convert the string URI part to int
203
+ if user_id == 1:
204
+ return {"id": user_id, "name": "Alice", "email": "alice@example.com", "status": "active"}
205
+ elif user_id == 2:
206
+ return {"id": user_id, "name": "Bob", "email": "bob@example.com", "status": "inactive"}
207
+ else:
208
+ # Example of returning an error structure
209
+ return {"error": f"User with ID {user_id} not found"}
210
+ ```
211
+
212
+ **How Templates Work:**
213
+
214
+ 1. **Definition:** When FastMCP sees `{...}` placeholders in the `@resource` URI and matching function parameters, it registers a `ResourceTemplate`.
215
+ 2. **Discovery:** Clients list templates via `resources/listResourceTemplates`.
216
+ 3. **Request & Matching:** A client requests a specific URI, e.g., `data://weather/london`. FastMCP matches this to the `data://weather/{city}` template.
217
+ 4. **Parameter Extraction:** It extracts the parameter value: `city="london"`.
218
+ 5. **Type Conversion & Function Call:** It converts the extracted string `"london"` to the type hinted in the function (`str` in this case) and calls `get_weather_for_city(city="london")`. For `users://1/profile`, it converts `"1"` to `int` before calling `get_user_profile(user_id=1)`.
219
+ 6. **Response:** The function's return value is formatted (e.g., dict to JSON) and sent back as the content of the requested resource URI (`data://weather/london`).
220
+
221
+ Templates provide a powerful way to expose parameterized data access points following REST-like principles.
222
+
223
+ ## Server Behavior
224
+
225
+ ### Duplicate Resources
226
+
227
+ You can configure how the FastMCP server handles attempts to register multiple resources or templates with the same URI. Use the `on_duplicate_resources` setting during `FastMCP` initialization.
228
+
229
+ ```python
230
+ from fastmcp import FastMCP
231
+ from fastmcp.settings import DuplicateBehavior
232
+
233
+ mcp = FastMCP(
234
+ name="ResourceServer",
235
+ on_duplicate_resources=DuplicateBehavior.ERROR # Raise error on duplicates
236
+ )
237
+
238
+ @mcp.resource("data://config")
239
+ def get_config_v1(): return {"version": 1}
240
+
241
+ # This registration attempt will raise a ValueError because
242
+ # "data://config" is already registered and the behavior is ERROR.
243
+ # @mcp.resource("data://config")
244
+ # def get_config_v2(): return {"version": 2}
245
+ ```
246
+
247
+ The `DuplicateBehavior` enum options are:
248
+
249
+ - `WARN` (default): Logs a warning, and the new resource/template replaces the old one.
250
+ - `ERROR`: Raises a `ValueError`, preventing the duplicate registration.
251
+ - `REPLACE`: Silently replaces the existing resource/template with the new one.
252
+ - `IGNORE`: Keeps the original resource/template and ignores the new registration attempt.
docs/servers/resources_backup.mdx ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Resources & Templates
3
+ sidebarTitle: Resources & Templates
4
+ description: Expose data sources and dynamic content generators to your MCP client.
5
+ icon: database
6
+ ---
7
+
8
+ Resources represent data or files that an MCP client can read, and resource templates extend this concept by allowing clients to request dynamically generated resources based on parameters passed in the URI.
9
+
10
+ FastMCP simplifies defining both static and dynamic resources, primarily using the `@mcp.resource` decorator.
11
+
12
+ ## What Are Resources?
13
+
14
+ Resources provide read-only access to data for the LLM or client application. When a client requests a resource URI:
15
+
16
+ 1. FastMCP finds the corresponding resource definition.
17
+ 2. If it's dynamic (defined by a function), the function is executed.
18
+ 3. The content (text, JSON, binary data) is returned to the client.
19
+
20
+ This allows LLMs to access files, database content, configuration, or dynamically generated information relevant to the conversation.
21
+
22
+ ## Defining Resources with `@mcp.resource`
23
+
24
+ The most common way to define a resource is by decorating a Python function. The decorator requires the resource's unique URI.
25
+
26
+ ```python
27
+ import json
28
+ from fastmcp import FastMCP
29
+
30
+ mcp = FastMCP(name="DataServer")
31
+
32
+ # Basic dynamic resource returning a string
33
+ @mcp.resource("resource://greeting")
34
+ def get_greeting() -> str:
35
+ """Provides a simple greeting message."""
36
+ return "Hello from FastMCP Resources!"
37
+
38
+ # Resource returning JSON data (dict is auto-serialized)
39
+ @mcp.resource("data://config")
40
+ def get_config() -> dict:
41
+ """Provides application configuration as JSON."""
42
+ return {
43
+ "theme": "dark",
44
+ "version": "1.2.0",
45
+ "features": ["tools", "resources"],
46
+ }
47
+ ```
48
+
49
+ **Key Concepts:**
50
+
51
+ * **URI:** The first argument to `@resource` is the unique URI (e.g., `"resource://greeting"`) clients use to request this data.
52
+ * **Lazy Loading:** The decorated function (`get_greeting`, `get_config`) is only executed when a client specifically requests that resource URI via `resources/read`.
53
+ * **Inferred Metadata:** By default:
54
+ * Resource Name: Taken from the function name (`get_greeting`).
55
+ * Resource Description: Taken from the function's docstring.
56
+
57
+ ### Return Value Handling
58
+
59
+ FastMCP automatically converts your function's return value into the appropriate MCP resource content:
60
+
61
+ - **`str`**: Sent as `TextResourceContents` (with `mime_type="text/plain"` by default).
62
+ - **`dict`, `list`, `pydantic.BaseModel`**: Automatically serialized to a JSON string and sent as `TextResourceContents` (with `mime_type="application/json"` by default).
63
+ - **`bytes`**: Base64 encoded and sent as `BlobResourceContents`. You should specify an appropriate `mime_type` (e.g., `"image/png"`, `"application/octet-stream"`).
64
+ - **`None`**: Results in an empty resource content list being returned.
65
+
66
+ ### Resource Metadata
67
+
68
+ You can customize the resource's properties using arguments in the decorator:
69
+
70
+ ```python
71
+ from fastmcp import FastMCP
72
+
73
+ mcp = FastMCP(name="DataServer")
74
+
75
+ # Example specifying metadata
76
+ @mcp.resource(
77
+ uri="data://app-status", # Explicit URI (required)
78
+ name="ApplicationStatus", # Custom name
79
+ description="Provides the current status of the application.", # Custom description
80
+ mime_type="application/json", # Explicit MIME type
81
+ tags={"monitoring", "status"} # Categorization tags
82
+ )
83
+ def get_application_status() -> dict:
84
+ """Internal function description (ignored if description is provided above)."""
85
+ return {"status": "ok", "uptime": 12345, "version": mcp.settings.version} # Example usage
86
+ ```
87
+
88
+ - **`uri`**: The unique identifier for the resource (required).
89
+ - **`name`**: A human-readable name (defaults to function name).
90
+ - **`description`**: Explanation of the resource (defaults to docstring).
91
+ - **`mime_type`**: Specifies the content type (FastMCP often infers a default like `text/plain` or `application/json`, but explicit is better for non-text types).
92
+ - **`tags`**: A set of strings for categorization, potentially used by clients for filtering.
93
+
94
+ ### Using Context in Resources
95
+
96
+ Like tools, resource functions can request the `Context` object to access MCP session capabilities.
97
+
98
+ ```python
99
+ from fastmcp import FastMCP, Context
100
+ import datetime
101
+
102
+ mcp = FastMCP(name="DataServer")
103
+
104
+ @mcp.resource("data://server-info", tags={"server", "info"})
105
+ async def get_server_info(ctx: Context) -> dict:
106
+ """Provides information about the server using context."""
107
+ await ctx.info(f"Generating server info resource for request {ctx.request_id}")
108
+ # You could potentially read other resources via ctx.read_resource here
109
+ return {
110
+ "server_name": mcp.name,
111
+ "timestamp": datetime.datetime.now(datetime.UTC).isoformat(),
112
+ "client_id": ctx.client_id or "N/A",
113
+ "log_level": mcp.settings.log_level,
114
+ }
115
+ ```
116
+
117
+ ### Asynchronous Resources
118
+
119
+ Use `async def` for resource functions that perform I/O operations (e.g., reading from a database or network) to avoid blocking the server.
120
+
121
+ ```python
122
+ import aiofiles
123
+ from fastmcp import FastMCP
124
+
125
+ mcp = FastMCP(name="DataServer")
126
+
127
+ @mcp.resource("file:///app/data/important_log.txt", mime_type="text/plain")
128
+ async def read_important_log() -> str:
129
+ """Reads content from a specific log file asynchronously."""
130
+ try:
131
+ async with aiofiles.open("/app/data/important_log.txt", mode="r") as f:
132
+ content = await f.read()
133
+ return content
134
+ except FileNotFoundError:
135
+ return "Log file not found."
136
+ ```
137
+
138
+ ## (Alternative) Defining Static Resources
139
+
140
+ While `@mcp.resource` is ideal for dynamic content, you can directly register pre-defined resources (like static files or simple text) using `mcp.add_resource()` and concrete `Resource` subclasses.
141
+
142
+ ```python
143
+ from pathlib import Path
144
+ from fastmcp import FastMCP
145
+ from fastmcp.resources import FileResource, TextResource, DirectoryResource
146
+
147
+ mcp = FastMCP(name="DataServer")
148
+
149
+ # 1. Exposing a static file directly
150
+ readme_path = Path("./README.md").resolve()
151
+ if readme_path.exists():
152
+ # Use a file:// URI scheme
153
+ readme_resource = FileResource(
154
+ uri=f"file://{readme_path.as_posix()}",
155
+ path=readme_path, # Path to the actual file
156
+ name="README File",
157
+ description="The project's README.",
158
+ mime_type="text/markdown",
159
+ tags={"documentation"}
160
+ )
161
+ mcp.add_resource(readme_resource)
162
+
163
+ # 2. Exposing simple, predefined text
164
+ notice_resource = TextResource(
165
+ uri="resource://notice",
166
+ name="Important Notice",
167
+ text="System maintenance scheduled for Sunday.",
168
+ tags={"notification"}
169
+ )
170
+ mcp.add_resource(notice_resource)
171
+
172
+ # 3. Exposing a directory listing
173
+ data_dir_path = Path("./app_data").resolve()
174
+ if data_dir_path.is_dir():
175
+ data_listing_resource = DirectoryResource(
176
+ uri="resource://data-files",
177
+ path=data_dir_path, # Path to the directory
178
+ name="Data Directory Listing",
179
+ description="Lists files available in the data directory.",
180
+ recursive=False # Set to True to list subdirectories
181
+ )
182
+ mcp.add_resource(data_listing_resource) # Returns JSON list of files
183
+ ```
184
+
185
+ **Common Resource Classes:**
186
+
187
+ - `TextResource`: For simple string content.
188
+ - `BinaryResource`: For raw `bytes` content.
189
+ - `FileResource`: Reads content from a local file path. Handles text/binary modes and lazy reading.
190
+ - `HttpResource`: Fetches content from an HTTP(S) URL (requires `httpx`).
191
+ - `DirectoryResource`: Lists files in a local directory (returns JSON).
192
+ - (`FunctionResource`: Internal class used by `@mcp.resource`).
193
+
194
+ Use these when the content is static or sourced directly from a file/URL, bypassing the need for a dedicated Python function.
195
+
196
+ ## Defining Resource Templates
197
+
198
+ Resource Templates allow clients to request resources whose content depends on parameters embedded in the URI. Define a template using the **same `@mcp.resource` decorator**, but include `{parameter_name}` placeholders in the URI string and add corresponding arguments to your function signature.
199
+
200
+ ```python
201
+ from fastmcp import FastMCP
202
+
203
+ mcp = FastMCP(name="DataServer")
204
+
205
+ # Template URI includes {city} placeholder
206
+ @mcp.resource("data://weather/{city}")
207
+ # Function accepts 'city' parameter matching the placeholder
208
+ def get_weather_for_city(city: str) -> dict:
209
+ """Provides weather information for a specific city."""
210
+ print(f"Server: Generating weather for city: {city}...")
211
+ # In reality, call a weather API using the 'city' parameter
212
+ temp = 20 + len(city) % 5 # Dummy logic
213
+ condition = "Sunny" if len(city) % 2 == 0 else "Cloudy"
214
+ return {"city": city.capitalize(), "temperature": temp, "unit": "celsius", "condition": condition}
215
+
216
+ # Template with an integer parameter
217
+ @mcp.resource("users://{user_id}/profile")
218
+ async def get_user_profile(user_id: int) -> dict:
219
+ """Retrieves a user's profile information by ID."""
220
+ print(f"Server: Generating profile for user ID: {user_id}...")
221
+ # In reality, fetch from database using user_id
222
+ # FastMCP uses Pydantic to auto-convert the string URI part to int
223
+ if user_id == 1:
224
+ return {"id": user_id, "name": "Alice", "email": "alice@example.com", "status": "active"}
225
+ elif user_id == 2:
226
+ return {"id": user_id, "name": "Bob", "email": "bob@example.com", "status": "inactive"}
227
+ else:
228
+ # Example of returning an error structure
229
+ return {"error": f"User with ID {user_id} not found"}
230
+ ```
231
+
232
+ **How Templates Work:**
233
+
234
+ 1. **Definition:** When FastMCP sees `{...}` placeholders in the `@resource` URI and matching function parameters, it registers a `ResourceTemplate`.
235
+ 2. **Discovery:** Clients list templates via `resources/listResourceTemplates`.
236
+ 3. **Request & Matching:** A client requests a specific URI, e.g., `data://weather/london`. FastMCP matches this to the `data://weather/{city}` template.
237
+ 4. **Parameter Extraction:** It extracts the parameter value: `city="london"`.
238
+ 5. **Type Conversion & Function Call:** It converts the extracted string `"london"` to the type hinted in the function (`str` in this case) and calls `get_weather_for_city(city="london")`. For `users://1/profile`, it converts `"1"` to `int` before calling `get_user_profile(user_id=1)`.
239
+ 6. **Response:** The function's return value is formatted (e.g., dict to JSON) and sent back as the content of the requested resource URI (`data://weather/london`).
240
+
241
+ Templates provide a powerful way to expose parameterized data access points following REST-like principles.
242
+
243
+ ## Server Behavior: Handling Duplicate Resources
244
+
245
+ You can configure how the FastMCP server handles attempts to register multiple resources or templates with the same URI. Use the `on_duplicate_resources` setting during `FastMCP` initialization.
246
+
247
+ ```python
248
+ from fastmcp import FastMCP
249
+ from fastmcp.settings import DuplicateBehavior
250
+
251
+ mcp = FastMCP(
252
+ name="ResourceServer",
253
+ on_duplicate_resources=DuplicateBehavior.ERROR # Raise error on duplicates
254
+ )
255
+
256
+ @mcp.resource("data://config")
257
+ def get_config_v1(): return {"version": 1}
258
+
259
+ # This registration attempt will raise a ValueError because
260
+ # "data://config" is already registered and the behavior is ERROR.
261
+ # @mcp.resource("data://config")
262
+ # def get_config_v2(): return {"version": 2}
263
+ ```
264
+
265
+ The `DuplicateBehavior` enum options are:
266
+
267
+ - `WARN` (default): Logs a warning, and the new resource/template replaces the old one.
268
+ - `ERROR`: Raises a `ValueError`, preventing the duplicate registration.
269
+ - `REPLACE`: Silently replaces the existing resource/template with the new one.
270
+ - `IGNORE`: Keeps the original resource/template and ignores the new registration attempt.
docs/servers/tools.mdx ADDED
@@ -0,0 +1,335 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Tools
3
+ sidebarTitle: Tools
4
+ description: Expose functions as executable capabilities for your MCP client.
5
+ icon: wrench
6
+ ---
7
+
8
+ Tools are the core building blocks that allow your LLM to interact with external systems, execute code, and access data that isn't in its training data. In FastMCP, tools are Python functions exposed to LLMs through the MCP protocol.
9
+
10
+ ## What Are Tools?
11
+
12
+ Tools in FastMCP transform regular Python functions into capabilities that LLMs can invoke during conversations. When an LLM decides to use a tool:
13
+
14
+ 1. It sends a request with parameters based on the tool's schema.
15
+ 2. FastMCP validates these parameters against your function's signature.
16
+ 3. Your function executes with the validated inputs.
17
+ 4. The result is returned to the LLM, which can use it in its response.
18
+
19
+ This allows LLMs to perform tasks like querying databases, calling APIs, making calculations, or accessing files—extending their capabilities beyond what's in their training data.
20
+
21
+ ## Defining Tools
22
+
23
+ ### The `@tool` Decorator
24
+
25
+ Creating a tool is as simple as decorating a Python function with `@mcp.tool()`:
26
+
27
+ ```python
28
+ from fastmcp import FastMCP
29
+
30
+ mcp = FastMCP(name="CalculatorServer")
31
+
32
+ @mcp.tool()
33
+ def add(a: int, b: int) -> int:
34
+ """Adds two integer numbers together."""
35
+ return a + b
36
+ ```
37
+
38
+ When this tool is registered, FastMCP automatically:
39
+ - Uses the function name (`add`) as the tool name.
40
+ - Uses the function's docstring (`Adds two integer numbers...`) as the tool description.
41
+ - Generates an input schema based on the function's parameters and type annotations.
42
+ - Handles parameter validation and error reporting.
43
+
44
+
45
+ The way you define your Python function dictates how the tool appears and behaves for the LLM client.
46
+
47
+ ### Type Annotations
48
+
49
+ Type annotations are crucial. They:
50
+ 1. Inform the LLM about the expected type for each parameter.
51
+ 2. Allow FastMCP to validate the data received from the client.
52
+ 3. Are used to generate the tool's input schema for the MCP protocol.
53
+
54
+ FastMCP supports standard Python type annotations, including those from the `typing` module and Pydantic.
55
+
56
+ ```python
57
+ from typing import Literal, Optional, Union
58
+ from pydantic import BaseModel, Field
59
+
60
+ # Example using various type hints
61
+ @mcp.tool()
62
+ def process_data(
63
+ data: list[float], # List of floats
64
+ operation: Literal["sum", "average", "max"], # Fixed choices
65
+ precision: int = 2, # Optional int with default
66
+ description: str | None = None # Optional string (can be None)
67
+ ) -> dict:
68
+ """Process numerical data with the specified operation."""
69
+ result = 0.0
70
+ if operation == "sum":
71
+ result = sum(data)
72
+ elif operation == "average":
73
+ result = sum(data) / len(data) if data else 0.0
74
+ elif operation == "max":
75
+ result = float(max(data)) if data else 0.0
76
+
77
+ return {
78
+ "operation": operation,
79
+ "result": round(result, precision),
80
+ "description": description
81
+ }
82
+ ```
83
+
84
+ **Supported Type Annotation Examples:**
85
+
86
+ | Type Annotation | Example | Description |
87
+ | :---------------------- | :---------------------------- | :---------------------------------- |
88
+ | Basic types | `int`, `float`, `str`, `bool` | Simple scalar values |
89
+ | Container types | `list[str]`, `dict[str, int]` | Collections of items |
90
+ | Optional types | `Optional[float]`, `float\|None`| Parameters that may be null/omitted |
91
+ | Union types | `str \| int`, `Union[str, int]`| Parameters accepting multiple types |
92
+ | Literal types | `Literal["A", "B"]` | Parameters with specific allowed values |
93
+ | Pydantic models | `UserData` | Complex structured data (see below) |
94
+
95
+ <Tip>
96
+ **Automatic JSON Parsing:** FastMCP intelligently handles arguments. If a client sends a string that looks like valid JSON (e.g., `"['a', 'b']"`) for a parameter hinted as a structured type (like `list[str]` or a Pydantic model), FastMCP will automatically attempt to parse the JSON string into the expected Python object before validation. This improves robustness when interacting with various clients.
97
+ </Tip>
98
+
99
+ ### Required vs. Optional Parameters
100
+
101
+ Parameters in your function signature are considered **required** unless they have a default value.
102
+
103
+ ```python
104
+ @mcp.tool()
105
+ def search_products(
106
+ query: str, # Required - no default value
107
+ max_results: int = 10, # Optional - has default value
108
+ sort_by: str = "relevance" # Optional - has default value
109
+ ) -> list[dict]:
110
+ """Search the product catalog."""
111
+ # Implementation...
112
+ print(f"Searching for '{query}', max {max_results}, sorted by {sort_by}")
113
+ return [{"id": 1, "name": "Sample Product"}]
114
+ ```
115
+
116
+ In this example, the LLM *must* provide a `query`. If `max_results` or `sort_by` are omitted, their default values will be used.
117
+
118
+ ### Structured Inputs
119
+
120
+ For tools requiring complex, nested, or well-validated inputs, use Pydantic models. Define a `BaseModel` and use it as a type hint for a parameter.
121
+
122
+ ```python
123
+ from pydantic import BaseModel, Field
124
+ from typing import Optional
125
+ from datetime import date
126
+
127
+ class ReservationRequest(BaseModel):
128
+ guest_name: str = Field(description="Full name of the guest making the reservation.")
129
+ check_in: date
130
+ check_out: date
131
+ room_type: Literal["standard", "deluxe", "suite"] = Field(default="standard", description="Type of room requested.")
132
+ guests: int = Field(gt=0, description="Number of guests (must be positive).")
133
+ special_requests: Optional[str] = Field(default=None, description="Any special requests for the stay.")
134
+
135
+ @mcp.tool()
136
+ def make_reservation(request: ReservationRequest) -> dict:
137
+ """Creates a new hotel reservation based on the provided details."""
138
+ # Pydantic automatically validates the incoming 'request' data
139
+ # against the ReservationRequest model before this function runs.
140
+ print(f"Making reservation for {request.guest_name}...")
141
+ # Implementation...
142
+ return {
143
+ "reservation_id": "R12345",
144
+ "status": "confirmed",
145
+ "guest": request.guest_name,
146
+ "dates": f"{request.check_in} to {request.check_out}"
147
+ }
148
+ ```
149
+
150
+ Using Pydantic models provides:
151
+ - Clear, self-documenting structure for complex inputs.
152
+ - Built-in data validation (e.g., `gt=0`, date parsing).
153
+ - Automatic generation of detailed JSON schemas for the LLM.
154
+ - Easy handling of optional fields and default values.
155
+
156
+ ### Metadata
157
+
158
+ While FastMCP infers the name and description from your function, you can override these and add tags using arguments to the `@mcp.tool` decorator:
159
+
160
+ ```python
161
+ @mcp.tool(
162
+ name="find_products", # Custom tool name for the LLM
163
+ description="Search the product catalog with optional category filtering.", # Custom description
164
+ tags={"catalog", "search"} # Optional tags for organization/filtering
165
+ )
166
+ def search_products_implementation(query: str, category: str | None = None) -> list[dict]:
167
+ """Internal function description (ignored if description is provided above)."""
168
+ # Implementation...
169
+ print(f"Searching for '{query}' in category '{category}'")
170
+ return [{"id": 2, "name": "Another Product"}]
171
+ ```
172
+
173
+ - **`name`**: Sets the explicit tool name exposed via MCP.
174
+ - **`description`**: Provides the description exposed via MCP. If set, the function's docstring is ignored for this purpose.
175
+ - **`tags`**: A set of strings used to categorize the tool. Clients *might* use tags to filter or group available tools.
176
+
177
+
178
+ ### Async Tools
179
+
180
+ FastMCP seamlessly supports both standard (`def`) and asynchronous (`async def`) functions as tools.
181
+
182
+ ```python
183
+ # Synchronous tool (suitable for CPU-bound or quick tasks)
184
+ @mcp.tool()
185
+ def calculate_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
186
+ """Calculate the distance between two coordinates."""
187
+ # Implementation...
188
+ return 42.5
189
+
190
+ # Asynchronous tool (ideal for I/O-bound operations)
191
+ @mcp.tool()
192
+ async def fetch_weather(city: str) -> dict:
193
+ """Retrieve current weather conditions for a city."""
194
+ # Use 'async def' for operations involving network calls, file I/O, etc.
195
+ # This prevents blocking the server while waiting for external operations.
196
+ async with aiohttp.ClientSession() as session:
197
+ async with session.get(f"https://api.example.com/weather/{city}") as response:
198
+ # Check response status before returning
199
+ response.raise_for_status()
200
+ return await response.json()
201
+ ```
202
+
203
+ Use `async def` when your tool needs to perform operations that might wait for external systems (network requests, database queries, file access) to keep your server responsive.
204
+
205
+ ### Return Values
206
+
207
+ FastMCP automatically converts the value returned by your function into the appropriate MCP content format for the client:
208
+
209
+ - **`str`**: Sent as `TextContent`.
210
+ - **`dict`, `list`, Pydantic `BaseModel`**: Serialized to a JSON string and sent as `TextContent`.
211
+ - **`bytes`**: Base64 encoded and sent as `BlobResourceContents` (often within an `EmbeddedResource`).
212
+ - **`fastmcp.utilities.types.Image`**: A helper class to easily return image data. Sent as `ImageContent`.
213
+ - **`None`**: Results in an empty response (no content is sent back to the client).
214
+
215
+ ```python
216
+ from fastmcp.utilities.types import Image
217
+ from PIL import Image as PILImage
218
+ import io
219
+
220
+ @mcp.tool()
221
+ def generate_image(width: int, height: int, color: str) -> Image:
222
+ """Generates a solid color image."""
223
+ # Create image using Pillow
224
+ img = PILImage.new("RGB", (width, height), color=color)
225
+
226
+ # Save to a bytes buffer
227
+ buffer = io.BytesIO()
228
+ img.save(buffer, format="PNG")
229
+ img_bytes = buffer.getvalue()
230
+
231
+ # Return using FastMCP's Image helper
232
+ return Image(data=img_bytes, format="png")
233
+
234
+ @mcp.tool()
235
+ def do_nothing() -> None:
236
+ """This tool performs an action but returns no data."""
237
+ print("Performing a side effect...")
238
+ return None
239
+ ```
240
+
241
+ ### Error Handling
242
+
243
+ If your tool encounters an error, simply raise a standard Python exception (`ValueError`, `TypeError`, `FileNotFoundError`, custom exceptions, etc.).
244
+
245
+ ```python
246
+ @mcp.tool()
247
+ def divide(a: float, b: float) -> float:
248
+ """Divide a by b."""
249
+ if b == 0:
250
+ # Raise a standard exception
251
+ raise ValueError("Division by zero is not allowed.")
252
+ if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
253
+ raise TypeError("Both arguments must be numbers.")
254
+ return a / b
255
+ ```
256
+
257
+ FastMCP automatically catches exceptions raised within your tool function:
258
+ 1. It converts the exception into an MCP error response, typically including the exception type and message.
259
+ 2. This error response is sent back to the client/LLM.
260
+ 3. The LLM can then inform the user or potentially try the tool again with different arguments.
261
+
262
+ Using informative exceptions helps the LLM understand failures and react appropriately.
263
+
264
+ ### Using Context in Tools
265
+
266
+ Tools can access MCP features like logging, reading resources, or reporting progress through the `Context` object. To use it, add a parameter to your tool function with the type hint `Context`.
267
+
268
+ ```python
269
+ from fastmcp import FastMCP, Context
270
+
271
+ mcp = FastMCP(name="ContextDemo")
272
+
273
+ @mcp.tool()
274
+ async def process_data(data_uri: str, ctx: Context) -> dict:
275
+ """Process data from a resource with progress reporting."""
276
+ await ctx.info(f"Processing data from {data_uri}")
277
+
278
+ # Read a resource
279
+ resource = await ctx.read_resource(data_uri)
280
+ data = resource[0].content if resource else ""
281
+
282
+ # Report progress
283
+ await ctx.report_progress(progress=50, total=100)
284
+
285
+ # Example request to the client's LLM for help
286
+ summary = await ctx.sample(f"Summarize this in 10 words: {data[:200]}")
287
+
288
+ await ctx.report_progress(progress=100, total=100)
289
+ return {
290
+ "length": len(data),
291
+ "summary": summary.text
292
+ }
293
+ ```
294
+
295
+ The Context object provides access to:
296
+
297
+ - **Logging**: `ctx.debug()`, `ctx.info()`, `ctx.warning()`, `ctx.error()`
298
+ - **Progress Reporting**: `ctx.report_progress(progress, total)`
299
+ - **Resource Access**: `ctx.read_resource(uri)`
300
+ - **LLM Sampling**: `ctx.sample(...)`
301
+ - **Request Information**: `ctx.request_id`, `ctx.client_id`
302
+
303
+ For full documentation on the Context object and all its capabilities, see the [Context Object](/server/context) page.
304
+
305
+ ## Server Behavior
306
+
307
+ ### Duplicate Tools
308
+
309
+ You can control how the FastMCP server behaves if you try to register multiple tools with the same name. This is configured using the `on_duplicate_tools` argument when creating the `FastMCP` instance.
310
+
311
+ ```python
312
+ from fastmcp import FastMCP
313
+ from fastmcp.settings import DuplicateBehavior
314
+
315
+ mcp = FastMCP(
316
+ name="StrictServer",
317
+ # Configure behavior for duplicate tool names
318
+ on_duplicate_tools=DuplicateBehavior.ERROR
319
+ )
320
+
321
+ @mcp.tool()
322
+ def my_tool(): return "Version 1"
323
+
324
+ # This will now raise a ValueError because 'my_tool' already exists
325
+ # and on_duplicate_tools is set to ERROR.
326
+ # @mcp.tool()
327
+ # def my_tool(): return "Version 2"
328
+ ```
329
+
330
+ The `DuplicateBehavior` enum options are:
331
+
332
+ - `WARN` (default): Logs a warning and the new tool replaces the old one.
333
+ - `ERROR`: Raises a `ValueError`, preventing the duplicate registration.
334
+ - `REPLACE`: Silently replaces the existing tool with the new one.
335
+ - `IGNORE`: Keeps the original tool and ignores the new registration attempt.
src/fastmcp/cli/cli.py CHANGED
@@ -133,7 +133,6 @@ def _import_server(file: Path, server_object: str | None = None):
133
  sys.exit(1)
134
 
135
  module = importlib.util.module_from_spec(spec)
136
- breakpoint()
137
  spec.loader.exec_module(module)
138
 
139
  # If no object specified, try common server names
 
133
  sys.exit(1)
134
 
135
  module = importlib.util.module_from_spec(spec)
 
136
  spec.loader.exec_module(module)
137
 
138
  # If no object specified, try common server names
src/fastmcp/server/server.py CHANGED
@@ -495,15 +495,20 @@ class FastMCP(Generic[LifespanResultT]):
495
  self._mcp_server.create_initialization_options(),
496
  )
497
 
498
- async def run_sse_async(self) -> None:
 
 
 
 
 
499
  """Run the server using SSE transport."""
500
  starlette_app = self.sse_app()
501
 
502
  config = uvicorn.Config(
503
  starlette_app,
504
- host=self.settings.host,
505
- port=self.settings.port,
506
- log_level=self.settings.log_level.lower(),
507
  )
508
  server = uvicorn.Server(config)
509
  await server.serve()
 
495
  self._mcp_server.create_initialization_options(),
496
  )
497
 
498
+ async def run_sse_async(
499
+ self,
500
+ host: str | None = None,
501
+ port: int | None = None,
502
+ log_level: str | None = None,
503
+ ) -> None:
504
  """Run the server using SSE transport."""
505
  starlette_app = self.sse_app()
506
 
507
  config = uvicorn.Config(
508
  starlette_app,
509
+ host=host or self.settings.host,
510
+ port=port or self.settings.port,
511
+ log_level=log_level or self.settings.log_level.lower(),
512
  )
513
  server = uvicorn.Server(config)
514
  await server.serve()
uv.lock CHANGED
@@ -1,5 +1,4 @@
1
  version = 1
2
- revision = 1
3
  requires-python = ">=3.10"
4
 
5
  [[package]]
@@ -128,7 +127,7 @@ name = "click"
128
  version = "8.1.8"
129
  source = { registry = "https://pypi.org/simple" }
130
  dependencies = [
131
- { name = "colorama", marker = "sys_platform == 'win32'" },
132
  ]
133
  sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 }
134
  wheels = [
@@ -231,7 +230,7 @@ name = "fancycompleter"
231
  version = "0.9.1"
232
  source = { registry = "https://pypi.org/simple" }
233
  dependencies = [
234
- { name = "pyreadline", marker = "sys_platform == 'win32'" },
235
  { name = "pyrepl" },
236
  ]
237
  sdist = { url = "https://files.pythonhosted.org/packages/a9/95/649d135442d8ecf8af5c7e235550c628056423c96c4bc6787348bdae9248/fancycompleter-0.9.1.tar.gz", hash = "sha256:09e0feb8ae242abdfd7ef2ba55069a46f011814a80fe5476be48f51b00247272", size = 10866 }
@@ -255,6 +254,7 @@ wheels = [
255
 
256
  [[package]]
257
  name = "fastmcp"
 
258
  source = { editable = "." }
259
  dependencies = [
260
  { name = "dotenv" },
@@ -351,15 +351,15 @@ wheels = [
351
 
352
  [[package]]
353
  name = "httpcore"
354
- version = "1.0.7"
355
  source = { registry = "https://pypi.org/simple" }
356
  dependencies = [
357
  { name = "certifi" },
358
  { name = "h11" },
359
  ]
360
- sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 }
361
  wheels = [
362
- { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 },
363
  ]
364
 
365
  [[package]]
@@ -415,7 +415,7 @@ wheels = [
415
 
416
  [[package]]
417
  name = "ipython"
418
- version = "8.34.0"
419
  source = { registry = "https://pypi.org/simple" }
420
  dependencies = [
421
  { name = "colorama", marker = "sys_platform == 'win32'" },
@@ -430,9 +430,9 @@ dependencies = [
430
  { name = "traitlets" },
431
  { name = "typing-extensions", marker = "python_full_version < '3.12'" },
432
  ]
433
- sdist = { url = "https://files.pythonhosted.org/packages/13/18/1a60aa62e9d272fcd7e658a89e1c148da10e1a5d38edcbcd834b52ca7492/ipython-8.34.0.tar.gz", hash = "sha256:c31d658e754673ecc6514583e7dda8069e47136eb62458816b7d1e6625948b5a", size = 5508477 }
434
  wheels = [
435
- { url = "https://files.pythonhosted.org/packages/04/78/45615356bb973904856808183ae2a5fba1f360e9d682314d79766f4b88f2/ipython-8.34.0-py3-none-any.whl", hash = "sha256:0419883fa46e0baa182c5d50ebb8d6b49df1889fdb70750ad6d8cfe678eda6e3", size = 826731 },
436
  ]
437
 
438
  [[package]]
@@ -639,7 +639,7 @@ wheels = [
639
 
640
  [[package]]
641
  name = "pydantic"
642
- version = "2.11.2"
643
  source = { registry = "https://pypi.org/simple" }
644
  dependencies = [
645
  { name = "annotated-types" },
@@ -647,9 +647,9 @@ dependencies = [
647
  { name = "typing-extensions" },
648
  { name = "typing-inspection" },
649
  ]
650
- sdist = { url = "https://files.pythonhosted.org/packages/b0/41/832125a41fe098b58d1fdd04ae819b4dc6b34d6b09ed78304fd93d4bc051/pydantic-2.11.2.tar.gz", hash = "sha256:2138628e050bd7a1e70b91d4bf4a91167f4ad76fdb83209b107c8d84b854917e", size = 784742 }
651
  wheels = [
652
- { url = "https://files.pythonhosted.org/packages/bf/c2/0f3baea344d0b15e35cb3e04ad5b953fa05106b76efbf4c782a3f47f22f5/pydantic-2.11.2-py3-none-any.whl", hash = "sha256:7f17d25846bcdf89b670a86cdfe7b29a9f1c9ca23dee154221c9aa81845cfca7", size = 443295 },
653
  ]
654
 
655
  [[package]]
@@ -781,15 +781,15 @@ sdist = { url = "https://files.pythonhosted.org/packages/05/1b/ea40363be00560804
781
 
782
  [[package]]
783
  name = "pyright"
784
- version = "1.1.398"
785
  source = { registry = "https://pypi.org/simple" }
786
  dependencies = [
787
  { name = "nodeenv" },
788
  { name = "typing-extensions" },
789
  ]
790
- sdist = { url = "https://files.pythonhosted.org/packages/24/d6/48740f1d029e9fc4194880d1ad03dcf0ba3a8f802e0e166b8f63350b3584/pyright-1.1.398.tar.gz", hash = "sha256:357a13edd9be8082dc73be51190913e475fa41a6efb6ec0d4b7aab3bc11638d8", size = 3892675 }
791
  wheels = [
792
- { url = "https://files.pythonhosted.org/packages/58/e0/5283593f61b3c525d6d7e94cfb6b3ded20b3df66e953acaf7bb4f23b3f6e/pyright-1.1.398-py3-none-any.whl", hash = "sha256:0a70bfd007d9ea7de1cf9740e1ad1a40a122592cfe22a3f6791b06162ad08753", size = 5780235 },
793
  ]
794
 
795
  [[package]]
@@ -999,27 +999,27 @@ wheels = [
999
 
1000
  [[package]]
1001
  name = "ruff"
1002
- version = "0.11.4"
1003
- source = { registry = "https://pypi.org/simple" }
1004
- sdist = { url = "https://files.pythonhosted.org/packages/e8/5b/3ae20f89777115944e89c2d8c2e795dcc5b9e04052f76d5347e35e0da66e/ruff-0.11.4.tar.gz", hash = "sha256:f45bd2fb1a56a5a85fae3b95add03fb185a0b30cf47f5edc92aa0355ca1d7407", size = 3933063 }
1005
- wheels = [
1006
- { url = "https://files.pythonhosted.org/packages/9c/db/baee59ac88f57527fcbaad3a7b309994e42329c6bc4d4d2b681a3d7b5426/ruff-0.11.4-py3-none-linux_armv6l.whl", hash = "sha256:d9f4a761ecbde448a2d3e12fb398647c7f0bf526dbc354a643ec505965824ed2", size = 10106493 },
1007
- { url = "https://files.pythonhosted.org/packages/c1/d6/9a0962cbb347f4ff98b33d699bf1193ff04ca93bed4b4222fd881b502154/ruff-0.11.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8c1747d903447d45ca3d40c794d1a56458c51e5cc1bc77b7b64bd2cf0b1626cc", size = 10876382 },
1008
- { url = "https://files.pythonhosted.org/packages/3a/8f/62bab0c7d7e1ae3707b69b157701b41c1ccab8f83e8501734d12ea8a839f/ruff-0.11.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:51a6494209cacca79e121e9b244dc30d3414dac8cc5afb93f852173a2ecfc906", size = 10237050 },
1009
- { url = "https://files.pythonhosted.org/packages/09/96/e296965ae9705af19c265d4d441958ed65c0c58fc4ec340c27cc9d2a1f5b/ruff-0.11.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f171605f65f4fc49c87f41b456e882cd0c89e4ac9d58e149a2b07930e1d466f", size = 10424984 },
1010
- { url = "https://files.pythonhosted.org/packages/e5/56/644595eb57d855afed6e54b852e2df8cd5ca94c78043b2f29bdfb29882d5/ruff-0.11.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ebf99ea9af918878e6ce42098981fc8c1db3850fef2f1ada69fb1dcdb0f8e79e", size = 9957438 },
1011
- { url = "https://files.pythonhosted.org/packages/86/83/9d3f3bed0118aef3e871ded9e5687fb8c5776bde233427fd9ce0a45db2d4/ruff-0.11.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edad2eac42279df12e176564a23fc6f4aaeeb09abba840627780b1bb11a9d223", size = 11547282 },
1012
- { url = "https://files.pythonhosted.org/packages/40/e6/0c6e4f5ae72fac5ccb44d72c0111f294a5c2c8cc5024afcb38e6bda5f4b3/ruff-0.11.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f103a848be9ff379fc19b5d656c1f911d0a0b4e3e0424f9532ececf319a4296e", size = 12182020 },
1013
- { url = "https://files.pythonhosted.org/packages/b5/92/4aed0e460aeb1df5ea0c2fbe8d04f9725cccdb25d8da09a0d3f5b8764bf8/ruff-0.11.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:193e6fac6eb60cc97b9f728e953c21cc38a20077ed64f912e9d62b97487f3f2d", size = 11679154 },
1014
- { url = "https://files.pythonhosted.org/packages/1b/d3/7316aa2609f2c592038e2543483eafbc62a0e1a6a6965178e284808c095c/ruff-0.11.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7af4e5f69b7c138be8dcffa5b4a061bf6ba6a3301f632a6bce25d45daff9bc99", size = 13905985 },
1015
- { url = "https://files.pythonhosted.org/packages/63/80/734d3d17546e47ff99871f44ea7540ad2bbd7a480ed197fe8a1c8a261075/ruff-0.11.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:126b1bf13154aa18ae2d6c3c5efe144ec14b97c60844cfa6eb960c2a05188222", size = 11348343 },
1016
- { url = "https://files.pythonhosted.org/packages/04/7b/70fc7f09a0161dce9613a4671d198f609e653d6f4ff9eee14d64c4c240fb/ruff-0.11.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8806daaf9dfa881a0ed603f8a0e364e4f11b6ed461b56cae2b1c0cab0645304", size = 10308487 },
1017
- { url = "https://files.pythonhosted.org/packages/1a/22/1cdd62dabd678d75842bf4944fd889cf794dc9e58c18cc547f9eb28f95ed/ruff-0.11.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5d94bb1cc2fc94a769b0eb975344f1b1f3d294da1da9ddbb5a77665feb3a3019", size = 9929091 },
1018
- { url = "https://files.pythonhosted.org/packages/9f/20/40e0563506332313148e783bbc1e4276d657962cc370657b2fff20e6e058/ruff-0.11.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:995071203d0fe2183fc7a268766fd7603afb9996785f086b0d76edee8755c896", size = 10924659 },
1019
- { url = "https://files.pythonhosted.org/packages/b5/41/eef9b7aac8819d9e942f617f9db296f13d2c4576806d604aba8db5a753f1/ruff-0.11.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7a37ca937e307ea18156e775a6ac6e02f34b99e8c23fe63c1996185a4efe0751", size = 11428160 },
1020
- { url = "https://files.pythonhosted.org/packages/ff/61/c488943414fb2b8754c02f3879de003e26efdd20f38167ded3fb3fc1cda3/ruff-0.11.4-py3-none-win32.whl", hash = "sha256:0e9365a7dff9b93af933dab8aebce53b72d8f815e131796268709890b4a83270", size = 10311496 },
1021
- { url = "https://files.pythonhosted.org/packages/b6/2b/2a1c8deb5f5dfa3871eb7daa41492c4d2b2824a74d2b38e788617612a66d/ruff-0.11.4-py3-none-win_amd64.whl", hash = "sha256:5a9fa1c69c7815e39fcfb3646bbfd7f528fa8e2d4bebdcf4c2bd0fa037a255fb", size = 11399146 },
1022
- { url = "https://files.pythonhosted.org/packages/4f/03/3aec4846226d54a37822e4c7ea39489e4abd6f88388fba74e3d4abe77300/ruff-0.11.4-py3-none-win_arm64.whl", hash = "sha256:d435db6b9b93d02934cf61ef332e66af82da6d8c69aefdea5994c89997c7a0fc", size = 10450306 },
1023
  ]
1024
 
1025
  [[package]]
@@ -1189,11 +1189,11 @@ wheels = [
1189
 
1190
  [[package]]
1191
  name = "typing-extensions"
1192
- version = "4.13.1"
1193
  source = { registry = "https://pypi.org/simple" }
1194
- sdist = { url = "https://files.pythonhosted.org/packages/76/ad/cd3e3465232ec2416ae9b983f27b9e94dc8171d56ac99b345319a9475967/typing_extensions-4.13.1.tar.gz", hash = "sha256:98795af00fb9640edec5b8e31fc647597b4691f099ad75f469a2616be1a76dff", size = 106633 }
1195
  wheels = [
1196
- { url = "https://files.pythonhosted.org/packages/df/c5/e7a0b0f5ed69f94c8ab7379c599e6036886bffcde609969a5325f47f1332/typing_extensions-4.13.1-py3-none-any.whl", hash = "sha256:4b6cf02909eb5495cfbc3f6e8fd49217e6cc7944e145cdda8caa3734777f9e69", size = 45739 },
1197
  ]
1198
 
1199
  [[package]]
@@ -1210,11 +1210,11 @@ wheels = [
1210
 
1211
  [[package]]
1212
  name = "urllib3"
1213
- version = "2.3.0"
1214
  source = { registry = "https://pypi.org/simple" }
1215
- sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 }
1216
  wheels = [
1217
- { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 },
1218
  ]
1219
 
1220
  [[package]]
 
1
  version = 1
 
2
  requires-python = ">=3.10"
3
 
4
  [[package]]
 
127
  version = "8.1.8"
128
  source = { registry = "https://pypi.org/simple" }
129
  dependencies = [
130
+ { name = "colorama", marker = "platform_system == 'Windows'" },
131
  ]
132
  sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 }
133
  wheels = [
 
230
  version = "0.9.1"
231
  source = { registry = "https://pypi.org/simple" }
232
  dependencies = [
233
+ { name = "pyreadline", marker = "platform_system == 'Windows'" },
234
  { name = "pyrepl" },
235
  ]
236
  sdist = { url = "https://files.pythonhosted.org/packages/a9/95/649d135442d8ecf8af5c7e235550c628056423c96c4bc6787348bdae9248/fancycompleter-0.9.1.tar.gz", hash = "sha256:09e0feb8ae242abdfd7ef2ba55069a46f011814a80fe5476be48f51b00247272", size = 10866 }
 
254
 
255
  [[package]]
256
  name = "fastmcp"
257
+ version = "2.1.1.dev3+4f58d82"
258
  source = { editable = "." }
259
  dependencies = [
260
  { name = "dotenv" },
 
351
 
352
  [[package]]
353
  name = "httpcore"
354
+ version = "1.0.8"
355
  source = { registry = "https://pypi.org/simple" }
356
  dependencies = [
357
  { name = "certifi" },
358
  { name = "h11" },
359
  ]
360
+ sdist = { url = "https://files.pythonhosted.org/packages/9f/45/ad3e1b4d448f22c0cff4f5692f5ed0666658578e358b8d58a19846048059/httpcore-1.0.8.tar.gz", hash = "sha256:86e94505ed24ea06514883fd44d2bc02d90e77e7979c8eb71b90f41d364a1bad", size = 85385 }
361
  wheels = [
362
+ { url = "https://files.pythonhosted.org/packages/18/8d/f052b1e336bb2c1fc7ed1aaed898aa570c0b61a09707b108979d9fc6e308/httpcore-1.0.8-py3-none-any.whl", hash = "sha256:5254cf149bcb5f75e9d1b2b9f729ea4a4b883d1ad7379fc632b727cec23674be", size = 78732 },
363
  ]
364
 
365
  [[package]]
 
415
 
416
  [[package]]
417
  name = "ipython"
418
+ version = "8.35.0"
419
  source = { registry = "https://pypi.org/simple" }
420
  dependencies = [
421
  { name = "colorama", marker = "sys_platform == 'win32'" },
 
430
  { name = "traitlets" },
431
  { name = "typing-extensions", marker = "python_full_version < '3.12'" },
432
  ]
433
+ sdist = { url = "https://files.pythonhosted.org/packages/0c/77/7d1501e8b539b179936e0d5969b578ed23887be0ab8c63e0120b825bda3e/ipython-8.35.0.tar.gz", hash = "sha256:d200b7d93c3f5883fc36ab9ce28a18249c7706e51347681f80a0aef9895f2520", size = 5605027 }
434
  wheels = [
435
+ { url = "https://files.pythonhosted.org/packages/91/bf/17ffca8c8b011d0bac90adb5d4e720cb3ae1fe5ccfdfc14ca31f827ee320/ipython-8.35.0-py3-none-any.whl", hash = "sha256:e6b7470468ba6f1f0a7b116bb688a3ece2f13e2f94138e508201fad677a788ba", size = 830880 },
436
  ]
437
 
438
  [[package]]
 
639
 
640
  [[package]]
641
  name = "pydantic"
642
+ version = "2.11.3"
643
  source = { registry = "https://pypi.org/simple" }
644
  dependencies = [
645
  { name = "annotated-types" },
 
647
  { name = "typing-extensions" },
648
  { name = "typing-inspection" },
649
  ]
650
+ sdist = { url = "https://files.pythonhosted.org/packages/10/2e/ca897f093ee6c5f3b0bee123ee4465c50e75431c3d5b6a3b44a47134e891/pydantic-2.11.3.tar.gz", hash = "sha256:7471657138c16adad9322fe3070c0116dd6c3ad8d649300e3cbdfe91f4db4ec3", size = 785513 }
651
  wheels = [
652
+ { url = "https://files.pythonhosted.org/packages/b0/1d/407b29780a289868ed696d1616f4aad49d6388e5a77f567dcd2629dcd7b8/pydantic-2.11.3-py3-none-any.whl", hash = "sha256:a082753436a07f9ba1289c6ffa01cd93db3548776088aa917cc43b63f68fa60f", size = 443591 },
653
  ]
654
 
655
  [[package]]
 
781
 
782
  [[package]]
783
  name = "pyright"
784
+ version = "1.1.399"
785
  source = { registry = "https://pypi.org/simple" }
786
  dependencies = [
787
  { name = "nodeenv" },
788
  { name = "typing-extensions" },
789
  ]
790
+ sdist = { url = "https://files.pythonhosted.org/packages/db/9d/d91d5f6d26b2db95476fefc772e2b9a16d54c6bd0ea6bb5c1b6d635ab8b4/pyright-1.1.399.tar.gz", hash = "sha256:439035d707a36c3d1b443aec980bc37053fbda88158eded24b8eedcf1c7b7a1b", size = 3856954 }
791
  wheels = [
792
+ { url = "https://files.pythonhosted.org/packages/2f/b5/380380c9e7a534cb1783c70c3e8ac6d1193c599650a55838d0557586796e/pyright-1.1.399-py3-none-any.whl", hash = "sha256:55f9a875ddf23c9698f24208c764465ffdfd38be6265f7faf9a176e1dc549f3b", size = 5592584 },
793
  ]
794
 
795
  [[package]]
 
999
 
1000
  [[package]]
1001
  name = "ruff"
1002
+ version = "0.11.5"
1003
+ source = { registry = "https://pypi.org/simple" }
1004
+ sdist = { url = "https://files.pythonhosted.org/packages/45/71/5759b2a6b2279bb77fe15b1435b89473631c2cd6374d45ccdb6b785810be/ruff-0.11.5.tar.gz", hash = "sha256:cae2e2439cb88853e421901ec040a758960b576126dab520fa08e9de431d1bef", size = 3976488 }
1005
+ wheels = [
1006
+ { url = "https://files.pythonhosted.org/packages/23/db/6efda6381778eec7f35875b5cbefd194904832a1153d68d36d6b269d81a8/ruff-0.11.5-py3-none-linux_armv6l.whl", hash = "sha256:2561294e108eb648e50f210671cc56aee590fb6167b594144401532138c66c7b", size = 10103150 },
1007
+ { url = "https://files.pythonhosted.org/packages/44/f2/06cd9006077a8db61956768bc200a8e52515bf33a8f9b671ee527bb10d77/ruff-0.11.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac12884b9e005c12d0bd121f56ccf8033e1614f736f766c118ad60780882a077", size = 10898637 },
1008
+ { url = "https://files.pythonhosted.org/packages/18/f5/af390a013c56022fe6f72b95c86eb7b2585c89cc25d63882d3bfe411ecf1/ruff-0.11.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4bfd80a6ec559a5eeb96c33f832418bf0fb96752de0539905cf7b0cc1d31d779", size = 10236012 },
1009
+ { url = "https://files.pythonhosted.org/packages/b8/ca/b9bf954cfed165e1a0c24b86305d5c8ea75def256707f2448439ac5e0d8b/ruff-0.11.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0947c0a1afa75dcb5db4b34b070ec2bccee869d40e6cc8ab25aca11a7d527794", size = 10415338 },
1010
+ { url = "https://files.pythonhosted.org/packages/d9/4d/2522dde4e790f1b59885283f8786ab0046958dfd39959c81acc75d347467/ruff-0.11.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad871ff74b5ec9caa66cb725b85d4ef89b53f8170f47c3406e32ef040400b038", size = 9965277 },
1011
+ { url = "https://files.pythonhosted.org/packages/e5/7a/749f56f150eef71ce2f626a2f6988446c620af2f9ba2a7804295ca450397/ruff-0.11.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6cf918390cfe46d240732d4d72fa6e18e528ca1f60e318a10835cf2fa3dc19f", size = 11541614 },
1012
+ { url = "https://files.pythonhosted.org/packages/89/b2/7d9b8435222485b6aac627d9c29793ba89be40b5de11584ca604b829e960/ruff-0.11.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:56145ee1478582f61c08f21076dc59153310d606ad663acc00ea3ab5b2125f82", size = 12198873 },
1013
+ { url = "https://files.pythonhosted.org/packages/00/e0/a1a69ef5ffb5c5f9c31554b27e030a9c468fc6f57055886d27d316dfbabd/ruff-0.11.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5f66f8f1e8c9fc594cbd66fbc5f246a8d91f916cb9667e80208663ec3728304", size = 11670190 },
1014
+ { url = "https://files.pythonhosted.org/packages/05/61/c1c16df6e92975072c07f8b20dad35cd858e8462b8865bc856fe5d6ccb63/ruff-0.11.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80b4df4d335a80315ab9afc81ed1cff62be112bd165e162b5eed8ac55bfc8470", size = 13902301 },
1015
+ { url = "https://files.pythonhosted.org/packages/79/89/0af10c8af4363304fd8cb833bd407a2850c760b71edf742c18d5a87bb3ad/ruff-0.11.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3068befab73620b8a0cc2431bd46b3cd619bc17d6f7695a3e1bb166b652c382a", size = 11350132 },
1016
+ { url = "https://files.pythonhosted.org/packages/b9/e1/ecb4c687cbf15164dd00e38cf62cbab238cad05dd8b6b0fc68b0c2785e15/ruff-0.11.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5da2e710a9641828e09aa98b92c9ebbc60518fdf3921241326ca3e8f8e55b8b", size = 10312937 },
1017
+ { url = "https://files.pythonhosted.org/packages/cf/4f/0e53fe5e500b65934500949361e3cd290c5ba60f0324ed59d15f46479c06/ruff-0.11.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ef39f19cb8ec98cbc762344921e216f3857a06c47412030374fffd413fb8fd3a", size = 9936683 },
1018
+ { url = "https://files.pythonhosted.org/packages/04/a8/8183c4da6d35794ae7f76f96261ef5960853cd3f899c2671961f97a27d8e/ruff-0.11.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b2a7cedf47244f431fd11aa5a7e2806dda2e0c365873bda7834e8f7d785ae159", size = 10950217 },
1019
+ { url = "https://files.pythonhosted.org/packages/26/88/9b85a5a8af21e46a0639b107fcf9bfc31da4f1d263f2fc7fbe7199b47f0a/ruff-0.11.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:81be52e7519f3d1a0beadcf8e974715b2dfc808ae8ec729ecfc79bddf8dbb783", size = 11404521 },
1020
+ { url = "https://files.pythonhosted.org/packages/fc/52/047f35d3b20fd1ae9ccfe28791ef0f3ca0ef0b3e6c1a58badd97d450131b/ruff-0.11.5-py3-none-win32.whl", hash = "sha256:e268da7b40f56e3eca571508a7e567e794f9bfcc0f412c4b607931d3af9c4afe", size = 10320697 },
1021
+ { url = "https://files.pythonhosted.org/packages/b9/fe/00c78010e3332a6e92762424cf4c1919065707e962232797d0b57fd8267e/ruff-0.11.5-py3-none-win_amd64.whl", hash = "sha256:6c6dc38af3cfe2863213ea25b6dc616d679205732dc0fb673356c2d69608f800", size = 11378665 },
1022
+ { url = "https://files.pythonhosted.org/packages/43/7c/c83fe5cbb70ff017612ff36654edfebec4b1ef79b558b8e5fd933bab836b/ruff-0.11.5-py3-none-win_arm64.whl", hash = "sha256:67e241b4314f4eacf14a601d586026a962f4002a475aa702c69980a38087aa4e", size = 10460287 },
1023
  ]
1024
 
1025
  [[package]]
 
1189
 
1190
  [[package]]
1191
  name = "typing-extensions"
1192
+ version = "4.13.2"
1193
  source = { registry = "https://pypi.org/simple" }
1194
+ sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 }
1195
  wheels = [
1196
+ { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 },
1197
  ]
1198
 
1199
  [[package]]
 
1210
 
1211
  [[package]]
1212
  name = "urllib3"
1213
+ version = "2.4.0"
1214
  source = { registry = "https://pypi.org/simple" }
1215
+ sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672 }
1216
  wheels = [
1217
+ { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680 },
1218
  ]
1219
 
1220
  [[package]]