alainivars commited on
Commit
5d83e4d
·
unverified ·
2 Parent(s): 4e8b154ed196c3

Merge branch 'main' into tests_coverage

Browse files
README.md CHANGED
@@ -12,6 +12,17 @@
12
  <a href="https://trendshift.io/repositories/13266" target="_blank"><img src="https://trendshift.io/api/badge/repositories/13266" alt="jlowin%2Ffastmcp | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
13
  </div>
14
 
 
 
 
 
 
 
 
 
 
 
 
15
  The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) is a new, standardized way to provide context and tools to your LLMs, and FastMCP makes building MCP servers and clients simple and intuitive. Create tools, expose resources, define prompts, and connect components with clean, Pythonic code.
16
 
17
  ```python
@@ -29,64 +40,50 @@ if __name__ == "__main__":
29
  mcp.run()
30
  ```
31
 
32
-
33
  Run the server locally:
34
  ```bash
35
  fastmcp run server.py
36
  ```
37
 
38
- FastMCP handles the complex protocol details and server management, letting you focus on building great tools and applications. It's designed to feel natural to Python developers.
 
 
39
 
 
40
 
41
  <!-- omit in toc -->
42
  ## Table of Contents
43
 
44
  - [What is MCP?](#what-is-mcp)
45
  - [Why FastMCP?](#why-fastmcp)
46
- - [Key Features](#key-features)
47
- - [Servers](#servers)
48
- - [Clients](#clients)
49
- - [What's New in v2?](#whats-new-in-v2)
50
- - [Documentation](#documentation)
51
- - [Installation](#installation)
52
- - [Quickstart](#quickstart)
53
  - [Core Concepts](#core-concepts)
54
  - [The `FastMCP` Server](#the-fastmcp-server)
55
  - [Tools](#tools)
56
- - [Resources](#resources)
57
  - [Prompts](#prompts)
58
  - [Context](#context)
59
- - [Images](#images)
60
  - [MCP Clients](#mcp-clients)
61
- - [Client Methods](#client-methods)
62
- - [Transport Options](#transport-options)
63
- - [LLM Sampling](#llm-sampling)
64
- - [Roots Access](#roots-access)
65
  - [Advanced Features](#advanced-features)
66
  - [Proxy Servers](#proxy-servers)
67
  - [Composing MCP Servers](#composing-mcp-servers)
68
  - [OpenAPI \& FastAPI Generation](#openapi--fastapi-generation)
69
- - [Handling `stderr`](#handling-stderr)
70
  - [Running Your Server](#running-your-server)
71
- - [Development Mode (Recommended for Building \& Testing)](#development-mode-recommended-for-building--testing)
72
- - [Claude Desktop Integration (For Regular Use)](#claude-desktop-integration-for-regular-use)
73
- - [Direct Execution (For Advanced Use Cases)](#direct-execution-for-advanced-use-cases)
74
- - [Server Object Names](#server-object-names)
75
- - [Examples](#examples)
76
  - [Contributing](#contributing)
77
- - [Prerequisites](#prerequisites)
78
- - [Setup](#setup)
79
- - [Testing](#testing)
80
- - [Formatting \& Linting](#formatting--linting)
81
- - [Pull Requests](#pull-requests)
82
 
 
83
 
84
  ## What is MCP?
85
 
86
  The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. Think of it like a web API, but specifically designed for LLM interactions. MCP servers can:
87
 
88
- - Expose data through **Resources** (think GET endpoints; load info into context)
89
- - Provide functionality through **Tools** (think POST/PUT endpoints; execute actions)
90
  - Define interaction patterns through **Prompts** (reusable templates)
91
  - And more!
92
 
@@ -96,8 +93,9 @@ FastMCP provides a high-level, Pythonic interface for building and interacting w
96
 
97
  The MCP protocol is powerful but implementing it involves a lot of boilerplate - server setup, protocol handlers, content types, error management. FastMCP handles all the complex protocol details and server management, so you can focus on building great tools. It's designed to be high-level and Pythonic; in most cases, decorating a function is all you need.
98
 
99
- FastMCP aims to be:
100
 
 
101
 
102
  🚀 **Fast:** High-level interface means less code and faster development
103
 
@@ -107,679 +105,278 @@ FastMCP aims to be:
107
 
108
  🔍 **Complete:** FastMCP aims to provide a full implementation of the core MCP specification for both servers and clients
109
 
110
- ## Key Features
111
-
112
- ### Servers
113
- - **Create** servers with minimal boilerplate using intuitive decorators
114
- - **Proxy** existing servers to modify configuration or transport
115
- - **Compose** servers into complex applications
116
- - **Generate** servers from OpenAPI specs or FastAPI objects
117
-
118
- ### Clients
119
- - **Interact** with MCP servers programmatically
120
- - **Connect** to any MCP server using any transport
121
- - **Test** your servers without manual intervention
122
- - **Innovate** with core MCP capabilities like LLM sampling
123
-
124
 
125
- ## What's New in v2?
126
-
127
- FastMCP 1.0 made it so easy to build MCP servers that it's now part of the [official Model Context Protocol Python SDK](https://github.com/modelcontextprotocol/python-sdk)! For basic use cases, you can use the upstream version by importing `mcp.server.fastmcp.FastMCP` (or installing `fastmcp=1.0`).
128
-
129
- Based on how the MCP ecosystem is evolving, FastMCP 2.0 builds on that foundation to introduce a variety of new features (and more experimental ideas). It adds advanced features like proxying and composing MCP servers, as well as automatically generating them from OpenAPI specs or FastAPI objects. FastMCP 2.0 also introduces new client-side functionality like LLM sampling.
130
-
131
-
132
- ## Documentation
133
-
134
- 📚 FastMCP's documentation is available at [gofastmcp.com](https://gofastmcp.com).
135
-
136
- ---
137
-
138
- ### Installation
139
-
140
- We strongly recommend installing FastMCP with [uv](https://docs.astral.sh/uv/), as it is required for deploying servers via the CLI:
141
 
142
  ```bash
143
  uv pip install fastmcp
144
  ```
145
 
146
- Note: on macOS, uv may need to be installed with Homebrew (`brew install uv`) in order to make it available to the Claude Desktop app.
147
-
148
- For development, install with:
149
- ```bash
150
- # Clone the repo first
151
- git clone https://github.com/jlowin/fastmcp.git
152
- cd fastmcp
153
- # Install with dev dependencies
154
- uv sync
155
- ```
156
-
157
- ### Quickstart
158
-
159
- Let's create a simple MCP server that exposes a calculator tool and some data:
160
-
161
- ```python
162
- # server.py
163
- from fastmcp import FastMCP
164
-
165
- # Create an MCP server
166
- mcp = FastMCP("Demo")
167
-
168
- # Add an addition tool
169
- @mcp.tool()
170
- def add(a: int, b: int) -> int:
171
- """Add two numbers"""
172
- return a + b
173
-
174
- # Add a dynamic greeting resource
175
- @mcp.resource("greeting://{name}")
176
- def get_greeting(name: str) -> str:
177
- """Get a personalized greeting"""
178
- return f"Hello, {name}!"
179
- ```
180
-
181
- You can install this server in [Claude Desktop](https://claude.ai/download) and interact with it right away by running:
182
- ```bash
183
- fastmcp install server.py
184
- ```
185
-
186
- ![MCP Inspector](/docs/assets/demo-inspector.png)
187
-
188
 
189
  ## Core Concepts
190
 
191
- These are the building blocks for creating MCP servers, using the familiar decorator-based approach.
192
 
193
  ### The `FastMCP` Server
194
 
195
- The central object representing your MCP application. It handles connections, protocol details, and routing.
196
 
197
  ```python
198
  from fastmcp import FastMCP
199
 
200
- # Create a named server
201
- mcp = FastMCP("My App")
202
-
203
- # Specify dependencies needed when deployed via `fastmcp install`
204
- mcp = FastMCP("My App", dependencies=["pandas", "numpy"])
205
  ```
206
 
207
- ### Tools
208
 
209
- Tools allow LLMs to perform actions by executing your Python functions. They are ideal for tasks that involve computation, external API calls, or side effects.
210
 
211
- Decorate synchronous or asynchronous functions with `@mcp.tool()`. FastMCP automatically generates the necessary MCP schema based on type hints and docstrings. Pydantic models can be used for complex inputs.
212
 
213
  ```python
214
- import httpx
215
- from pydantic import BaseModel
216
-
217
- class UserInfo(BaseModel):
218
- user_id: int
219
- notify: bool = False
220
-
221
- @mcp.tool()
222
- async def send_notification(user: UserInfo, message: str) -> dict:
223
- """Sends a notification to a user if requested."""
224
- if user.notify:
225
- # Simulate sending notification
226
- print(f"Notifying user {user.user_id}: {message}")
227
- return {"status": "sent", "user_id": user.user_id}
228
- return {"status": "skipped", "user_id": user.user_id}
229
-
230
  @mcp.tool()
231
- def get_stock_price(ticker: str) -> float:
232
- """Gets the current price for a stock ticker."""
233
- # Replace with actual API call
234
- prices = {"AAPL": 180.50, "GOOG": 140.20}
235
- return prices.get(ticker.upper(), 0.0)
236
  ```
237
 
238
- ### Resources
239
 
240
- Resources expose data to LLMs. They should primarily provide information without significant computation or side effects (like GET requests).
241
 
242
- Decorate functions with `@mcp.resource("your://uri")`. Use curly braces `{}` in the URI to define dynamic resources (templates) where parts of the URI become function parameters.
243
 
244
  ```python
245
- # Static resource returning simple text
246
- @mcp.resource("config://app-version")
247
- def get_app_version() -> str:
248
- """Returns the application version."""
249
- return "v2.1.0"
250
-
251
- # Dynamic resource template expecting a 'user_id' from the URI
252
- @mcp.resource("db://users/{user_id}/email")
253
- async def get_user_email(user_id: str) -> str:
254
- """Retrieves the email address for a given user ID."""
255
- # Replace with actual database lookup
256
- emails = {"123": "alice@example.com", "456": "bob@example.com"}
257
- return emails.get(user_id, "not_found@example.com")
258
-
259
- # Resource returning JSON data
260
- @mcp.resource("data://product-categories")
261
- def get_categories() -> list[str]:
262
- """Returns a list of available product categories."""
263
- return ["Electronics", "Books", "Home Goods"]
264
  ```
265
 
266
- ### Prompts
267
 
268
- Prompts define reusable templates or interaction patterns for the LLM. They help guide the LLM on how to use your server's capabilities effectively.
269
 
270
- Decorate functions with `@mcp.prompt()`. The function should return the desired prompt content, which can be a simple string, a `Message` object (like `UserMessage` or `AssistantMessage`), or a list of these.
271
 
272
  ```python
273
- from fastmcp.prompts.base import UserMessage, AssistantMessage
274
-
275
- @mcp.prompt()
276
- def ask_review(code_snippet: str) -> str:
277
- """Generates a standard code review request."""
278
- return f"Please review the following code snippet for potential bugs and style issues:\n```python\n{code_snippet}\n```"
279
-
280
  @mcp.prompt()
281
- def debug_session_start(error_message: str) -> list[Message]:
282
- """Initiates a debugging help session."""
283
- return [
284
- UserMessage(f"I encountered an error:\n{error_message}"),
285
- AssistantMessage("Okay, I can help with that. Can you provide the full traceback and tell me what you were trying to do?")
286
- ]
287
  ```
288
 
289
- ### Context
290
-
291
- Gain access to MCP server capabilities *within* your tool or resource functions by adding a parameter type-hinted with `fastmcp.Context`.
292
 
293
- ```python
294
- from fastmcp import Context, FastMCP
295
-
296
- mcp = FastMCP("Context Demo")
297
-
298
- @mcp.resource("system://status")
299
- async def get_system_status(ctx: Context) -> dict:
300
- """Checks system status and logs information."""
301
- await ctx.info("Checking system status...")
302
- # Perform checks
303
- await ctx.report_progress(1, 1) # Report completion
304
- return {"status": "OK", "load": 0.5, "client": ctx.client_id}
305
-
306
- @mcp.tool()
307
- async def process_large_file(file_uri: str, ctx: Context) -> str:
308
- """Processes a large file, reporting progress and reading resources."""
309
- await ctx.info(f"Starting processing for {file_uri}")
310
- # Read the resource using the context
311
- file_content_resource = await ctx.read_resource(file_uri)
312
- file_content = file_content_resource[0].content # Assuming single text content
313
- lines = file_content.splitlines()
314
- total_lines = len(lines)
315
-
316
- for i, line in enumerate(lines):
317
- # Process line...
318
- if (i + 1) % 100 == 0: # Report progress every 100 lines
319
- await ctx.report_progress(i + 1, total_lines)
320
-
321
- await ctx.info(f"Finished processing {file_uri}")
322
- return f"Processed {total_lines} lines."
323
-
324
- ```
325
-
326
- The `Context` object provides:
327
- * Logging: `ctx.debug()`, `ctx.info()`, `ctx.warning()`, `ctx.error()`
328
- * Progress Reporting: `ctx.report_progress(current, total)`
329
- * Resource Access: `await ctx.read_resource(uri)`
330
- * Request Info: `ctx.request_id`, `ctx.client_id`
331
- * Sampling (Advanced): `await ctx.sample(...)` to ask the connected LLM client for completions.
332
-
333
- ### Images
334
 
335
- Easily handle image outputs using the `fastmcp.Image` helper class.
 
 
 
 
 
 
336
 
337
- <Tip>
338
- The below code requires the `pillow` library to be installed.
339
- </Tip>
340
 
341
  ```python
342
- from mcp.server.fastmcp import FastMCP, Image
343
- from io import BytesIO
344
- try:
345
- from PIL import Image as PILImage
346
- except ImportError:
347
- raise ImportError("Please install the `pillow` library to run this example.")
348
 
349
- mcp = FastMCP("My App")
350
 
351
  @mcp.tool()
352
- def create_thumbnail(image_path: str) -> Image:
353
- """Create a thumbnail from an image"""
354
- img = PILImage.open(image_path)
355
- img.thumbnail((100, 100))
356
- buffer = BytesIO()
357
- img.save(buffer, format="PNG")
358
- return Image(data=buffer.getvalue(), format="png")
359
- ```
360
- Return the `Image` helper class from your tool to send an image to the client. The `Image` helper class handles the conversion to/from the base64-encoded format required by the MCP protocol. It works with either a path to an image file, or a bytes object.
361
 
 
 
362
 
363
- ### MCP Clients
 
364
 
365
- The `Client` class lets you interact with any MCP server (not just FastMCP ones) from Python code:
366
-
367
- ```python
368
- from fastmcp import Client
369
-
370
- async with Client("path/to/server") as client:
371
- # Call a tool
372
- result = await client.call_tool("weather", {"location": "San Francisco"})
373
- print(result)
374
-
375
- # Read a resource
376
- res = await client.read_resource("db://users/123/profile")
377
- print(res)
378
  ```
379
 
380
- You can connect to servers using any supported transport protocol (Stdio, SSE, FastMCP, etc.). If you don't specify a transport, the `Client` class automatically attempts to detect an appropriate one from your connection string or server object.
381
-
382
- #### Client Methods
383
-
384
- The `Client` class exposes several methods for interacting with MCP servers.
385
 
386
- ```python
387
- async with Client("path/to/server") as client:
388
- # List available tools
389
- tools = await client.list_tools()
390
-
391
- # List available resources
392
- resources = await client.list_resources()
393
-
394
- # Call a tool with arguments
395
- result = await client.call_tool("generate_report", {"user_id": 123})
396
-
397
- # Read a resource
398
- user_data = await client.read_resource("db://users/123/profile")
399
-
400
- # Get a prompt
401
- greeting = await client.get_prompt("welcome", {"name": "Alice"})
402
-
403
- # Send progress updates
404
- await client.progress("task-123", 50, 100) # 50% complete
405
-
406
- # Basic connectivity testing
407
- await client.ping()
408
- ```
409
-
410
- These methods correspond directly to MCP protocol operations, making it easy to interact with any MCP-compatible server (not just FastMCP ones).
411
 
412
- #### Transport Options
413
 
414
- FastMCP supports various transport protocols for connecting to MCP servers:
415
 
416
  ```python
417
  from fastmcp import Client
418
- from fastmcp.client.transports import (
419
- SSETransport,
420
- PythonStdioTransport,
421
- FastMCPTransport
422
- )
423
-
424
- # Connect to a server over SSE (common for web-based MCP servers)
425
- async with Client(SSETransport("http://localhost:8000/mcp")) as client:
426
- # Use client here...
427
-
428
- # Connect to a Python script using stdio (useful for local tools)
429
- async with Client(PythonStdioTransport("path/to/script.py")) as client:
430
- # Use client here...
431
-
432
- # Connect directly to a FastMCP server object in the same process
433
- from your_app import mcp_server
434
- async with Client(FastMCPTransport(mcp_server)) as client:
435
- # Use client here...
436
- ```
437
 
438
- Common transport options include:
439
- - `SSETransport`: Connect to a server via Server-Sent Events (HTTP)
440
- - `PythonStdioTransport`: Run a Python script and communicate via stdio
441
- - `FastMCPTransport`: Connect directly to a FastMCP server object
442
- - `WSTransport`: Connect via WebSockets
443
-
444
- In addition, if you pass a connection string or `FastMCP` server object to the `Client` constructor, it will try to automatically detect the appropriate transport.
445
-
446
- #### LLM Sampling
447
-
448
- Sampling is an MCP feature that allows a server to request a completion from the client LLM, enabling sophisticated use cases while maintaining security and privacy on the server.
449
-
450
- ```python
451
- import marvin # Or any other LLM client
452
- from fastmcp import Client, Context, FastMCP
453
- from fastmcp.client.sampling import RequestContext, SamplingMessage, SamplingParams
454
-
455
- # -- SERVER SIDE --
456
- # Create a server that requests LLM completions from the client
457
-
458
- mcp = FastMCP("Sampling Example")
459
-
460
- @mcp.tool()
461
- async def generate_poem(topic: str, context: Context) -> str:
462
- """Generate a short poem about the given topic."""
463
- # The server requests a completion from the client LLM
464
- response = await context.sample(
465
- f"Write a short poem about {topic}",
466
- system_prompt="You are a talented poet who writes concise, evocative verses."
467
- )
468
- return response.text
469
-
470
- @mcp.tool()
471
- async def summarize_document(document_uri: str, context: Context) -> str:
472
- """Summarize a document using client-side LLM capabilities."""
473
- # First read the document as a resource
474
- doc_resource = await context.read_resource(document_uri)
475
- doc_content = doc_resource[0].content # Assuming single text content
476
-
477
- # Then ask the client LLM to summarize it
478
- response = await context.sample(
479
- f"Summarize the following document:\n\n{doc_content}",
480
- system_prompt="You are an expert summarizer. Create a concise summary."
481
- )
482
- return response.text
483
-
484
- # -- CLIENT SIDE --
485
- # Create a client that handles the sampling requests
486
-
487
- async def sampling_handler(
488
- messages: list[SamplingMessage],
489
- params: SamplingParams,
490
- ctx: RequestContext,
491
- ) -> str:
492
- """Handle sampling requests from the server using your preferred LLM."""
493
- # Extract the messages and system prompt
494
- prompt = [m.content.text for m in messages if m.content.type == "text"]
495
- system_instruction = params.systemPrompt or "You are a helpful assistant."
496
-
497
- # Use your preferred LLM client to generate completions
498
- return await marvin.say_async(
499
- message=prompt,
500
- instructions=system_instruction,
501
- )
502
-
503
- # Connect them together
504
- async with Client(mcp, sampling_handler=sampling_handler) as client:
505
- result = await client.call_tool("generate_poem", {"topic": "autumn leaves"})
506
- print(result.content[0].text)
507
  ```
508
 
509
- This pattern is powerful because:
510
- 1. The server can delegate text generation to the client LLM
511
- 2. The server remains focused on business logic and data handling
512
- 3. The client maintains control over which LLM is used and how requests are handled
513
- 4. No sensitive data needs to be sent to external APIs
514
-
515
- #### Roots Access
516
-
517
- FastMCP exposes the MCP roots functionality, allowing clients to specify which file system roots they can access. This creates a secure boundary for tools that need to work with files. Note that the server must account for client roots explicitly.
518
 
519
  ```python
520
- from fastmcp import Client, RootsList
521
 
522
- # Specify file roots that the client can access
523
- roots = ["file:///path/to/allowed/directory"]
524
 
525
- async with Client(mcp_server, roots=roots) as client:
526
- # Now tools in the MCP server can access files in the specified roots
527
- await client.call_tool("process_file", {"filename": "data.csv"})
 
528
  ```
529
 
530
- ## Advanced Features
531
 
532
- Building on the core concepts, FastMCP v2 introduces powerful features for more complex scenarios:
533
 
 
534
 
535
  ### Proxy Servers
536
 
537
- Create a FastMCP server that acts as an intermediary, proxying requests to another MCP endpoint (which could be a server or another client connection).
538
-
539
- **Use Cases:**
540
-
541
- * **Transport Conversion:** Expose a server running on Stdio (like many local tools) over SSE or WebSockets, making it accessible to web clients or Claude Desktop.
542
- * **Adding Functionality:** Wrap an existing server to add authentication, request logging, or modified tool behavior.
543
- * **Aggregating Servers:** Combine multiple backend MCP servers behind a single proxy interface (though `mount` might be simpler for this).
544
-
545
- ```python
546
- import asyncio
547
- from fastmcp import FastMCP, Client
548
- from fastmcp.client.transports import PythonStdioTransport
549
-
550
- # Create a client that connects to the original server
551
- proxy_client = Client(
552
- transport=PythonStdioTransport('path/to/original_stdio_server.py'),
553
- )
554
-
555
- # Create a proxy server that connects to the client and exposes its capabilities
556
- proxy = FastMCP.from_client(proxy_client, name="Stdio-to-SSE Proxy")
557
-
558
- if __name__ == "__main__":
559
- proxy.run(transport='sse')
560
- ```
561
-
562
- `FastMCP.from_client` is a class method that connects to the target, discovers its capabilities, and dynamically builds the proxy server instance.
563
-
564
 
 
565
 
566
  ### Composing MCP Servers
567
 
568
- Structure larger MCP applications by creating modular FastMCP servers and "mounting" them onto a parent server. This automatically handles prefixing for tool names and resource URIs, preventing conflicts.
569
-
570
- ```python
571
- from fastmcp import FastMCP
572
-
573
- # --- Weather MCP ---
574
- weather_mcp = FastMCP("Weather Service")
575
-
576
- @weather_mcp.tool()
577
- def get_forecast(city: str):
578
- return f"Sunny in {city}"
579
-
580
- @weather_mcp.resource("data://temp/{city}")
581
- def get_temp(city: str):
582
- return 25.0
583
 
584
- # --- News MCP ---
585
- news_mcp = FastMCP("News Service")
586
-
587
- @news_mcp.tool()
588
- def fetch_headlines():
589
- return ["Big news!", "Other news"]
590
-
591
- @news_mcp.resource("data://latest_story")
592
- def get_story():
593
- return "A story happened."
594
-
595
- # --- Composite MCP ---
596
-
597
- mcp = FastMCP("Composite")
598
-
599
- # Mount sub-apps with prefixes
600
- mcp.mount("weather", weather_mcp) # Tools prefixed "weather/", resources prefixed "weather+"
601
- mcp.mount("news", news_mcp) # Tools prefixed "news/", resources prefixed "news+"
602
-
603
- @mcp.tool()
604
- def ping():
605
- return "Composite OK"
606
-
607
-
608
- if __name__ == "__main__":
609
- mcp.run()
610
- ```
611
-
612
- This promotes code organization and reusability for complex MCP systems.
613
 
614
  ### OpenAPI & FastAPI Generation
615
 
616
- Leverage your existing web APIs by automatically generating FastMCP servers from them.
617
-
618
- By default, the following rules are applied:
619
- - `GET` requests -> MCP resources
620
- - `GET` requests with path parameters -> MCP resource templates
621
- - All other HTTP methods -> MCP tools
622
-
623
- You can override these rules to customize or even ignore certain endpoints.
624
-
625
- **From FastAPI:**
626
-
627
- ```python
628
- from fastapi import FastAPI
629
- from fastmcp import FastMCP
630
-
631
- # Your existing FastAPI application
632
- fastapi_app = FastAPI(title="My Existing API")
633
-
634
- @fastapi_app.get("/status")
635
- def get_status():
636
- return {"status": "running"}
637
-
638
- @fastapi_app.post("/items")
639
- def create_item(name: str, price: float):
640
- return {"id": 1, "name": name, "price": price}
641
-
642
- # Generate an MCP server directly from the FastAPI app
643
- mcp_server = FastMCP.from_fastapi(fastapi_app)
644
 
645
- if __name__ == "__main__":
646
- mcp_server.run()
647
- ```
648
-
649
- **From an OpenAPI Specification:**
650
-
651
- ```python
652
- import httpx
653
- import json
654
- from fastmcp import FastMCP
655
-
656
- # Load the OpenAPI spec (dict)
657
- # with open("my_api_spec.json", "r") as f:
658
- # openapi_spec = json.load(f)
659
- openapi_spec = { ... } # Your spec dict
660
-
661
- # Create an HTTP client to make requests to the actual API endpoint
662
- http_client = httpx.AsyncClient(base_url="https://api.yourservice.com")
663
-
664
- # Generate the MCP server
665
- mcp_server = FastMCP.from_openapi(openapi_spec, client=http_client)
666
-
667
- if __name__ == "__main__":
668
- mcp_server.run()
669
- ```
670
-
671
- ### Handling `stderr`
672
- The MCP spec allows for the server to write anything it wants to `stderr`, and it
673
- doesn't specify the format in any way. FastMCP will forward the server's `stderr`
674
- to the client's `stderr`.
675
 
676
  ## Running Your Server
677
 
678
- Choose the method that best suits your needs:
679
-
680
- ### Development Mode (Recommended for Building & Testing)
681
-
682
- Use `fastmcp dev` for an interactive testing environment with the MCP Inspector.
683
-
684
- ```bash
685
- fastmcp dev your_server_file.py
686
- # With temporary dependencies
687
- fastmcp dev your_server_file.py --with pandas --with numpy
688
- # With local package in editable mode
689
- fastmcp dev your_server_file.py --with-editable .
690
- ```
691
-
692
- ### Claude Desktop Integration (For Regular Use)
693
-
694
- Use `fastmcp install` to set up your server for persistent use within the Claude Desktop app. It handles creating an isolated environment using `uv`.
695
-
696
- ```bash
697
- fastmcp install your_server_file.py
698
- # With a custom name in Claude
699
- fastmcp install your_server_file.py --name "My Analysis Tool"
700
- # With extra packages and environment variables
701
- fastmcp install server.py --with requests -v API_KEY=123 -f .env
702
- ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
703
 
704
- ### Direct Execution (For Advanced Use Cases)
705
-
706
- Run your server script directly for custom deployments or integrations outside of Claude. You manage the environment and dependencies yourself.
707
-
708
- Add to your `your_server_file.py`:
709
- ```python
710
- if __name__ == "__main__":
711
- mcp.run() # Assuming 'mcp' is your FastMCP instance
712
- ```
713
- Run with:
714
- ```bash
715
- python your_server_file.py
716
- # or
717
- uv run python your_server_file.py
718
- ```
719
-
720
- ### Server Object Names
721
-
722
- If your `FastMCP` instance is not named `mcp`, `server`, or `app`, specify it using `file:object` syntax for the `dev` and `install` commands:
723
-
724
- ```bash
725
- fastmcp dev my_module.py:my_mcp_instance
726
- fastmcp install api.py:api_app
727
- ```
728
-
729
- ## Examples
730
-
731
- Explore the `examples/` directory for code samples demonstrating various features:
732
-
733
- * `simple_echo.py`: Basic tool, resource, and prompt.
734
- * `complex_inputs.py`: Using Pydantic models for tool inputs.
735
- * `mount_example.py`: Mounting multiple FastMCP servers.
736
- * `sampling.py`: Using LLM completions within your MCP server.
737
- * `screenshot.py`: Tool returning an Image object.
738
- * `text_me.py`: Tool interacting with an external API.
739
- * `memory.py`: More complex example with database interaction.
740
 
741
  ## Contributing
742
 
743
- Contributions make the open-source community vibrant! We welcome improvements and features.
744
-
745
- <details>
746
-
747
- <summary><h3>Open Developer Guide</h3></summary>
748
 
749
- #### Prerequisites
750
 
751
  * Python 3.10+
752
- * [uv](https://docs.astral.sh/uv/)
753
 
754
- #### Setup
755
 
756
- 1. Clone: `git clone https://github.com/jlowin/fastmcp.git && cd fastmcp`
757
- 2. Install Env & Dependencies: `uv venv && uv sync` (Activate the `.venv` after creation)
 
 
 
 
 
 
 
 
 
 
758
 
759
- #### Testing
760
 
761
- Run the test suite:
 
 
762
  ```bash
763
- uv run --frozen pytest -vv
764
  ```
765
  or if you want an overview of the code coverage
766
  ```bash
767
  uv run pytest --cov=src --cov=examples --cov-report=html
768
  ```
769
 
770
- #### Formatting & Linting
771
 
772
- We use `ruff` via `pre-commit`.
773
- 1. Install hooks: `pre-commit install`
774
- 2. Run checks: `pre-commit run --all-files`
775
 
776
- #### Pull Requests
 
 
 
 
 
 
 
 
 
777
 
778
- 1. Fork the repository.
779
- 2. Create a feature branch.
780
- 3. Make changes, commit, and push to your fork.
781
- 4. Open a pull request against the `main` branch of `jlowin/fastmcp`.
782
 
783
- Please open an issue or discussion for questions or suggestions!
 
 
 
 
 
784
 
785
- </details>
 
12
  <a href="https://trendshift.io/repositories/13266" target="_blank"><img src="https://trendshift.io/api/badge/repositories/13266" alt="jlowin%2Ffastmcp | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
13
  </div>
14
 
15
+ > [!NOTE]
16
+ > #### FastMCP 2.0 & The Official MCP SDK
17
+ >
18
+ > Recognize the `FastMCP` name? You might have used the version integrated into the [official MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk), which was based on **FastMCP 1.0**.
19
+ >
20
+ > **Welcome to FastMCP 2.0!** This is the actively developed successor, and it significantly expands on 1.0 by introducing powerful client capabilities, server proxying & composition, OpenAPI/FastAPI integration, and more advanced features.
21
+ >
22
+ > FastMCP 2.0 is the recommended path for building modern, powerful MCP applications. Ready to upgrade or get started? Follow the [installation instructions](https://gofastmcp.com/getting-started/installation), which include specific steps for upgrading from the official MCP SDK.
23
+
24
+ ---
25
+
26
  The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) is a new, standardized way to provide context and tools to your LLMs, and FastMCP makes building MCP servers and clients simple and intuitive. Create tools, expose resources, define prompts, and connect components with clean, Pythonic code.
27
 
28
  ```python
 
40
  mcp.run()
41
  ```
42
 
 
43
  Run the server locally:
44
  ```bash
45
  fastmcp run server.py
46
  ```
47
 
48
+ ### 📚 Documentation
49
+
50
+ This readme provides only a high-level overview. For detailed guides, API references, and advanced patterns, please refer to the complete FastMCP documentation at **[gofastmcp.com](https://gofastmcp.com)**.
51
 
52
+ ---
53
 
54
  <!-- omit in toc -->
55
  ## Table of Contents
56
 
57
  - [What is MCP?](#what-is-mcp)
58
  - [Why FastMCP?](#why-fastmcp)
59
+ - [Installation](#installation)
 
 
 
 
 
 
60
  - [Core Concepts](#core-concepts)
61
  - [The `FastMCP` Server](#the-fastmcp-server)
62
  - [Tools](#tools)
63
+ - [Resources \& Templates](#resources--templates)
64
  - [Prompts](#prompts)
65
  - [Context](#context)
 
66
  - [MCP Clients](#mcp-clients)
 
 
 
 
67
  - [Advanced Features](#advanced-features)
68
  - [Proxy Servers](#proxy-servers)
69
  - [Composing MCP Servers](#composing-mcp-servers)
70
  - [OpenAPI \& FastAPI Generation](#openapi--fastapi-generation)
 
71
  - [Running Your Server](#running-your-server)
 
 
 
 
 
72
  - [Contributing](#contributing)
73
+ - [Prerequisites](#prerequisites)
74
+ - [Setup](#setup)
75
+ - [Unit Tests](#unit-tests)
76
+ - [Static Checks](#static-checks)
77
+ - [Pull Requests](#pull-requests)
78
 
79
+ ---
80
 
81
  ## What is MCP?
82
 
83
  The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. Think of it like a web API, but specifically designed for LLM interactions. MCP servers can:
84
 
85
+ - Expose data through **Resources** (similar to `GET` requests; load info into context)
86
+ - Provide functionality through **Tools** (similar to `POST`/`PUT` requests; execute actions)
87
  - Define interaction patterns through **Prompts** (reusable templates)
88
  - And more!
89
 
 
93
 
94
  The MCP protocol is powerful but implementing it involves a lot of boilerplate - server setup, protocol handlers, content types, error management. FastMCP handles all the complex protocol details and server management, so you can focus on building great tools. It's designed to be high-level and Pythonic; in most cases, decorating a function is all you need.
95
 
96
+ While the core server concepts of FastMCP 1.0 laid the groundwork and were contributed to the official MCP SDK, **FastMCP 2.0 (this project) is the actively developed successor**, adding significant enhancements and entirely new capabilities like a powerful **client library**, server **proxying**, **composition** patterns, **OpenAPI/FastAPI integration**, and much more.
97
 
98
+ FastMCP aims to be:
99
 
100
  🚀 **Fast:** High-level interface means less code and faster development
101
 
 
105
 
106
  🔍 **Complete:** FastMCP aims to provide a full implementation of the core MCP specification for both servers and clients
107
 
108
+ ## Installation
 
 
 
 
 
 
 
 
 
 
 
 
 
109
 
110
+ We recommend installing FastMCP with [uv](https://docs.astral.sh/uv/):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
  ```bash
113
  uv pip install fastmcp
114
  ```
115
 
116
+ For full installation instructions, including verification, upgrading from the official MCPSDK, and developer setup, see the [**Installation Guide**](https://gofastmcp.com/getting-started/installation).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
  ## Core Concepts
119
 
120
+ These are the building blocks for creating MCP servers and clients with FastMCP.
121
 
122
  ### The `FastMCP` Server
123
 
124
+ The central object representing your MCP application. It holds your tools, resources, and prompts, manages connections, and can be configured with settings like [authentication providers](https://gofastmcp.com/servers/fastmcp#authentication).
125
 
126
  ```python
127
  from fastmcp import FastMCP
128
 
129
+ # Create a server instance
130
+ mcp = FastMCP(name="MyAssistantServer")
 
 
 
131
  ```
132
 
133
+ Learn more in the [**FastMCP Server Documentation**](https://gofastmcp.com/servers/fastmcp).
134
 
135
+ ### Tools
136
 
137
+ Tools allow LLMs to perform actions by executing your Python functions (sync or async). Ideal for computations, API calls, or side effects (like `POST`/`PUT`). FastMCP handles schema generation from type hints and docstrings. Tools can return various types, including text, JSON-serializable objects, and even images using the [`fastmcp.Image`](https://gofastmcp.com/servers/tools#return-values) helper.
138
 
139
  ```python
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  @mcp.tool()
141
+ def multiply(a: float, b: float) -> float:
142
+ """Multiplies two numbers."""
143
+ return a * b
 
 
144
  ```
145
 
146
+ Learn more in the [**Tools Documentation**](https://gofastmcp.com/servers/tools).
147
 
148
+ ### Resources & Templates
149
 
150
+ Resources expose read-only data sources (like `GET` requests). Use `@mcp.resource("your://uri")`. Use `{placeholders}` in the URI to create dynamic templates that accept parameters, allowing clients to request specific data subsets.
151
 
152
  ```python
153
+ # Static resource
154
+ @mcp.resource("config://version")
155
+ def get_version():
156
+ return "2.0.1"
157
+
158
+ # Dynamic resource template
159
+ @mcp.resource("users://{user_id}/profile")
160
+ def get_profile(user_id: int):
161
+ # Fetch profile for user_id...
162
+ return {"name": f"User {user_id}", "status": "active"}
 
 
 
 
 
 
 
 
 
163
  ```
164
 
165
+ Learn more in the [**Resources & Templates Documentation**](https://gofastmcp.com/servers/resources).
166
 
167
+ ### Prompts
168
 
169
+ Prompts define reusable message templates to guide LLM interactions. Decorate functions with `@mcp.prompt()`. Return strings or `Message` objects.
170
 
171
  ```python
 
 
 
 
 
 
 
172
  @mcp.prompt()
173
+ def summarize_request(text: str) -> str:
174
+ """Generate a prompt asking for a summary."""
175
+ return f"Please summarize the following text:\n\n{text}"
 
 
 
176
  ```
177
 
178
+ Learn more in the [**Prompts Documentation**](https://gofastmcp.com/servers/prompts).
 
 
179
 
180
+ ### Context
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
 
182
+ Access MCP session capabilities within your tools, resources, or prompts by adding a `ctx: Context` parameter. Context provides methods for:
183
+ * **Logging:** Log messages to MCP clients with `ctx.info()`, `ctx.error()`, etc.
184
+ * **LLM Sampling:** Use `ctx.sample()` to request completions from the client's LLM.
185
+ * **HTTP Request:** Use `ctx.http_request()` to make HTTP requests to other servers.
186
+ * **Resource Access:** Use `ctx.read_resource()` to access resources on the server
187
+ * **Progress Reporting:** Use `ctx.report_progress()` to report progress to the client.
188
+ * and more...
189
 
190
+ To access the context, add a parameter annotated as `Context` to any mcp-decorated function. FastMCP will automatically inject the correct context object when the function is called.
 
 
191
 
192
  ```python
193
+ from fastmcp import FastMCP, Context
 
 
 
 
 
194
 
195
+ mcp = FastMCP("My MCP Server")
196
 
197
  @mcp.tool()
198
+ async def process_data(uri: str, ctx: Context):
199
+ # Log a message to the client
200
+ await ctx.info(f"Processing {uri}...")
 
 
 
 
 
 
201
 
202
+ # Read a resource from the server
203
+ data = await ctx.read_resource(uri)
204
 
205
+ # Ask client LLM to summarize the data
206
+ summary = await ctx.sample(f"Summarize: {data.content[:500]}")
207
 
208
+ # Return the summary
209
+ return summary.text
 
 
 
 
 
 
 
 
 
 
 
210
  ```
211
 
212
+ Learn more in the [**Context Documentation**](https://gofastmcp.com/servers/context).
 
 
 
 
213
 
214
+ ### MCP Clients
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
 
216
+ Interact with *any* MCP server programmatically using the `fastmcp.Client`. It supports various transports (Stdio, SSE, In-Memory) and often auto-detects the correct one. The client can also handle advanced patterns like server-initiated **LLM sampling requests** if you provide an appropriate handler.
217
 
218
+ Critically, the client allows for efficient **in-memory testing** of your servers by connecting directly to a `FastMCP` server instance via the `FastMCPTransport`, eliminating the need for process management or network calls during tests.
219
 
220
  ```python
221
  from fastmcp import Client
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
 
223
+ async def main():
224
+ # Connect via stdio to a local script
225
+ async with Client("my_server.py") as client:
226
+ tools = await client.list_tools()
227
+ print(f"Available tools: {tools}")
228
+ result = await client.call_tool("add", {"a": 5, "b": 3})
229
+ print(f"Result: {result.text}")
230
+
231
+ # Connect via SSE
232
+ async with Client("http://localhost:8000/sse") as client:
233
+ # ... use the client
234
+ pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  ```
236
 
237
+ To use clients to test servers, use the following pattern:
 
 
 
 
 
 
 
 
238
 
239
  ```python
240
+ from fastmcp import FastMCP, Client
241
 
242
+ mcp = FastMCP("My MCP Server")
 
243
 
244
+ async def main():
245
+ # Connect via in-memory transport
246
+ async with Client(mcp) as client:
247
+ # ... use the client
248
  ```
249
 
250
+ Learn more in the [**Client Documentation**](https://gofastmcp.com/clients/client) and [**Transports Documentation**](https://gofastmcp.com/clients/transports).
251
 
252
+ ## Advanced Features
253
 
254
+ FastMCP introduces powerful ways to structure and deploy your MCP applications.
255
 
256
  ### Proxy Servers
257
 
258
+ Create a FastMCP server that acts as an intermediary for another local or remote MCP server using `FastMCP.from_client()`. This is especially useful for bridging transports (e.g., remote SSE to local Stdio) or adding a layer of logic to a server you don't control.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
 
260
+ Learn more in the [**Proxying Documentation**](https://gofastmcp.com/patterns/proxy).
261
 
262
  ### Composing MCP Servers
263
 
264
+ Build modular applications by mounting multiple `FastMCP` instances onto a parent server using `mcp.mount()` (live link) or `mcp.import_server()` (static copy).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
 
266
+ Learn more in the [**Composition Documentation**](https://gofastmcp.com/patterns/composition).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
 
268
  ### OpenAPI & FastAPI Generation
269
 
270
+ Automatically generate FastMCP servers from existing OpenAPI specifications (`FastMCP.from_openapi()`) or FastAPI applications (`FastMCP.from_fastapi()`), instantly bringing your web APIs to the MCP ecosystem.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
 
272
+ Learn more: [**OpenAPI Integration**](https://gofastmcp.com/patterns/openapi) | [**FastAPI Integration**](https://gofastmcp.com/patterns/fastapi).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
 
274
  ## Running Your Server
275
 
276
+ You can run your FastMCP server in several ways:
277
+
278
+ 1. **Development (`fastmcp dev`)**: Recommended for building and testing. Provides an interactive testing environment with the MCP Inspector.
279
+ ```bash
280
+ fastmcp dev server.py
281
+ # Optionally add temporary dependencies
282
+ fastmcp dev server.py --with pandas numpy
283
+ ```
284
+
285
+ 2. **FastMCP CLI**: Run your server with the FastMCP CLI. This can autodetect and load your server object and run it with any transport configuration you want.
286
+ ```bash
287
+ fastmcp run path/to/server.py:server_object
288
+
289
+ # Run as SSE on port 4200
290
+ fastmcp run path/to/server.py:server_object --transport sse --port 4200
291
+ ```
292
+ FastMCP will auto-detect the server object if it's named `mcp`, `app`, or `server`. In these cases, you can omit the `:server_object` part unless you need to select a specific object.
293
+
294
+ 3. **Direct Execution**: For maximum compatibility with the MCP ecosystem, you can run your server directly as part of a Python script. You will typically do this within an `if __name__ == "__main__":` block in your script:
295
+ ```python
296
+ # Add this to server.py
297
+ if __name__ == "__main__":
298
+ # Default: runs stdio transport
299
+ mcp.run()
300
+
301
+ # Example: Run with SSE transport on a specific port
302
+ mcp.run(transport="sse", host="127.0.0.1", port=9000)
303
+ ```
304
+ Run your script:
305
+ ```bash
306
+ python server.py
307
+ # or using uv to manage the environment
308
+ uv run python server.py
309
+ ```
310
+ 4. **Claude Desktop Integration (`fastmcp install`)**: The easiest way to make your server persistently available in the Claude Desktop app. It handles creating an isolated environment using `uv`.
311
+ ```bash
312
+ fastmcp install server.py --name "My Analysis Tool"
313
+ # Optionally add dependencies and environment variables
314
+ fastmcp install server.py --with requests -v API_KEY=123 -f .env
315
+ ```
316
+
317
+
318
+ See the [**Server Documentation**](https://gofastmcp.com/servers/fastmcp#running-the-server) for more details on transports and configuration.
319
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
 
321
  ## Contributing
322
 
323
+ Contributions are the core of open source! We welcome improvements and features.
 
 
 
 
324
 
325
+ ### Prerequisites
326
 
327
  * Python 3.10+
328
+ * [uv](https://docs.astral.sh/uv/) (Recommended for environment management)
329
 
330
+ ### Setup
331
 
332
+ 1. Clone the repository:
333
+ ```bash
334
+ git clone https://github.com/jlowin/fastmcp.git
335
+ cd fastmcp
336
+ ```
337
+ 2. Create and sync the environment:
338
+ ```bash
339
+ uv sync
340
+ ```
341
+ This installs all dependencies, including dev tools.
342
+
343
+ 3. Activate the virtual environment (e.g., `source .venv/bin/activate` or via your IDE).
344
 
345
+ ### Unit Tests
346
 
347
+ FastMCP has a comprehensive unit test suite. All PRs must introduce or update tests as appropriate and pass the full suite.
348
+
349
+ Run tests using pytest:
350
  ```bash
351
+ pytest
352
  ```
353
  or if you want an overview of the code coverage
354
  ```bash
355
  uv run pytest --cov=src --cov=examples --cov-report=html
356
  ```
357
 
358
+ ### Static Checks
359
 
360
+ FastMCP uses `pre-commit` for code formatting, linting, and type-checking. All PRs must pass these checks (they run automatically in CI).
 
 
361
 
362
+ Install the hooks locally:
363
+ ```bash
364
+ uv run pre-commit install
365
+ ```
366
+ The hooks will now run automatically on `git commit`. You can also run them manually at any time:
367
+ ```bash
368
+ pre-commit run --all-files
369
+ # or via uv
370
+ uv run pre-commit run --all-files
371
+ ```
372
 
373
+ ### Pull Requests
 
 
 
374
 
375
+ 1. Fork the repository on GitHub.
376
+ 2. Create a feature branch from `main`.
377
+ 3. Make your changes, including tests and documentation updates.
378
+ 4. Ensure tests and pre-commit hooks pass.
379
+ 5. Commit your changes and push to your fork.
380
+ 6. Open a pull request against the `main` branch of `jlowin/fastmcp`.
381
 
382
+ Please open an issue or discussion for questions or suggestions before starting significant work!
docs/getting-started/installation.mdx CHANGED
@@ -23,7 +23,7 @@ Alternatively, you can install it directly with `pip` or `uv pip`:
23
  ```
24
  </CodeGroup>
25
 
26
- ## Verify Installation
27
 
28
  To verify that FastMCP is installed correctly, you can run the following command:
29
 
@@ -42,10 +42,26 @@ Python version: 3.12.2
42
  Platform: macOS-15.3.1-arm64-arm-64bit
43
  FastMCP root path: ~/Developer/fastmcp
44
  ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
  ## Installing for Development
47
 
48
- If you plan to contribute to FastMCP, you should begin by cloning the repository and using uv to install all dependencies.
49
 
50
  ```bash
51
  git clone https://github.com/jlowin/fastmcp.git
@@ -53,10 +69,26 @@ cd fastmcp
53
  uv sync
54
  ```
55
 
56
- This will install all dependencies, including ones for development, and create a virtual environment.
57
 
58
- To run the tests, use pytest:
 
 
59
 
60
  ```bash
61
  pytest
62
- ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  ```
24
  </CodeGroup>
25
 
26
+ ### Verify Installation
27
 
28
  To verify that FastMCP is installed correctly, you can run the following command:
29
 
 
42
  Platform: macOS-15.3.1-arm64-arm-64bit
43
  FastMCP root path: ~/Developer/fastmcp
44
  ```
45
+ ## Upgrading from the Official MCP SDK
46
+
47
+ Upgrading from the official MCP SDK's FastMCP 1.0 to FastMCP 2.0 is easy! The core server API is highly compatible, so after you install the `fastmcp` package, just change your import statement from `from mcp.server.fastmcp import FastMCP` to `from fastmcp import FastMCP`.
48
+
49
+
50
+ ```python {1-5}
51
+ # Before
52
+ # from mcp.server.fastmcp import FastMCP
53
+
54
+ # After
55
+ from fastmcp import FastMCP
56
+
57
+ mcp = FastMCP("My MCP Server")
58
+ ```
59
+
60
+ While the 1.0 server API is very stable for common use cases, FastMCP 2.0 introduces many new features (like the Client, proxying, composition) documented throughout this site. Review the documentation for details on new capabilities.
61
 
62
  ## Installing for Development
63
 
64
+ If you plan to contribute to FastMCP, you should begin by cloning the repository and using uv to install all dependencies (development dependencies are installed automatically):
65
 
66
  ```bash
67
  git clone https://github.com/jlowin/fastmcp.git
 
69
  uv sync
70
  ```
71
 
72
+ This will install all dependencies, including ones for development, and create a virtual environment, which you can activate and use as normal.
73
 
74
+ ### Unit Tests
75
+
76
+ FastMCP has a comprehensive unit test suite, and all PR's must introduce and pass appropriate tests. To run the tests, use pytest:
77
 
78
  ```bash
79
  pytest
80
+ ```
81
+
82
+ ### Pre-Commit Hooks
83
+
84
+ FastMCP uses pre-commit to manage code quality, including formatting, linting, and type-safety. All PR's must pass the pre-commit hooks, which are run as a part of the CI process. To install the pre-commit hooks, run:
85
+
86
+ ```bash
87
+ uv run pre-commit install
88
+ ```
89
+
90
+ Alternatively, to run pre-commit manually at any time, use:
91
+
92
+ ```bash
93
+ pre-commit run --all-files
94
+ ```
docs/getting-started/welcome.mdx CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: "Welcome to FastMCP!"
3
  sidebarTitle: "Welcome!"
4
  description: The fast, Pythonic way to build MCP servers and clients.
5
 
@@ -24,6 +24,19 @@ if __name__ == "__main__":
24
  ```
25
 
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  ## What is MCP?
28
  The Model Context Protocol lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. It is often described as "the USB-C port for AI", providing a uniform way to connect LLMs to resources they can use. It may be easier to think of it as an API, but specifically designed for LLM interactions. MCP servers can:
29
 
@@ -34,17 +47,13 @@ The Model Context Protocol lets you build servers that expose data and functiona
34
 
35
  There is a low-level Python SDK available for implementing the protocol directly, but FastMCP aims to make that easier by providing a high-level, Pythonic interface.
36
 
37
- <Tip>
38
- FastMCP 1.0 was so successful that it is now included as part of the official [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)!
39
- </Tip>
40
-
41
-
42
-
43
 
44
  ## Why FastMCP?
45
 
46
  The MCP protocol is powerful but implementing it involves a lot of boilerplate - server setup, protocol handlers, content types, error management. FastMCP handles all the complex protocol details and server management, so you can focus on building great tools. It's designed to be high-level and Pythonic; in most cases, decorating a function is all you need.
47
 
 
 
48
  FastMCP aims to be:
49
 
50
  🚀 **Fast**: High-level interface means less code and faster development
@@ -55,5 +64,4 @@ FastMCP aims to be:
55
 
56
  🔍 **Complete**: FastMCP aims to provide a full implementation of the core MCP specification
57
 
58
- **FastMCP v1** focused on abstracting the most common boilerplate of exposing MCP server functionality, and is now included in the official MCP Python SDK. **FastMCP v2** expands on that foundation to introduce novel functionality mainly focused on simplifying server interactions, including flexible clients, proxying and composition, and deployment.
59
 
 
1
  ---
2
+ title: "Welcome to FastMCP 2.0!"
3
  sidebarTitle: "Welcome!"
4
  description: The fast, Pythonic way to build MCP servers and clients.
5
 
 
24
  ```
25
 
26
 
27
+ ## FastMCP 2.0 and the Official MCP SDK
28
+
29
+ <Tip>
30
+ Recognize the `FastMCP` name? You might have used the version integrated into the [official MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk), which was based on **FastMCP 1.0**.
31
+
32
+
33
+ **Welcome to FastMCP 2.0!** This is the [actively developed successor](https://github.com/jlowin/fastmcp), and it significantly expands on 1.0 by introducing powerful client capabilities, server proxying & composition, OpenAPI/FastAPI integration, and more advanced features.
34
+
35
+ FastMCP 2.0 is the recommended path for building modern, powerful MCP applications. Ready to upgrade or get started? Follow the [installation instructions](/getting-started/installation), which include specific steps for upgrading.
36
+ </Tip>
37
+
38
+
39
+
40
  ## What is MCP?
41
  The Model Context Protocol lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. It is often described as "the USB-C port for AI", providing a uniform way to connect LLMs to resources they can use. It may be easier to think of it as an API, but specifically designed for LLM interactions. MCP servers can:
42
 
 
47
 
48
  There is a low-level Python SDK available for implementing the protocol directly, but FastMCP aims to make that easier by providing a high-level, Pythonic interface.
49
 
 
 
 
 
 
 
50
 
51
  ## Why FastMCP?
52
 
53
  The MCP protocol is powerful but implementing it involves a lot of boilerplate - server setup, protocol handlers, content types, error management. FastMCP handles all the complex protocol details and server management, so you can focus on building great tools. It's designed to be high-level and Pythonic; in most cases, decorating a function is all you need.
54
 
55
+ While the core server concepts of FastMCP 1.0 laid the groundwork and were contributed to the official MCP SDK, FastMCP 2.0 (this project) is the actively developed successor, adding significant enhancements and entirely new capabilities like a powerful client library, server proxying, composition patterns, and much more.
56
+
57
  FastMCP aims to be:
58
 
59
  🚀 **Fast**: High-level interface means less code and faster development
 
64
 
65
  🔍 **Complete**: FastMCP aims to provide a full implementation of the core MCP specification
66
 
 
67
 
docs/patterns/composition.mdx CHANGED
@@ -30,8 +30,6 @@ The choice of importing or mounting depends on your use case and requirements. I
30
  | **Method** | `FastMCP.import_server()` | `FastMCP.mount()` |
31
  | **Composition Type** | One-time copy (static) | Live link (dynamic) |
32
  | **Updates** | Changes to subserver NOT reflected | Changes to subserver immediately reflected |
33
- | **Lifespan** | Not managed | Automatically managed |
34
- | **Synchronicity** | Async (must be awaited) | Sync |
35
  | **Best For** | Bundling finalized components | Modular runtime composition |
36
 
37
  ### Proxy Servers
@@ -184,12 +182,12 @@ if __name__ == "__main__":
184
 
185
  ### How Mounting Works
186
 
187
- When you call `main_mcp.mount(prefix, server)`:
188
 
189
- 1. **Live Link**: A live connection is established between `main_mcp` and the `subserver`.
190
- 2. **Dynamic Updates**: Changes made to the `subserver` (e.g., adding new tools) **will be reflected** immediately when accessing components through `main_mcp`.
191
- 3. **Lifespan Management**: The `subserver`'s `lifespan` context **is automatically managed** and executed within the `main_mcp`'s lifespan.
192
- 4. **Delegation**: Requests for components matching the prefix are delegated to the subserver at runtime.
193
 
194
  The same prefixing rules apply as with `import_server` for naming tools, resources, templates, and prompts.
195
 
@@ -207,6 +205,51 @@ main_mcp.mount(
207
  )
208
  ```
209
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
 
211
  ## Example: Modular Application
212
 
 
30
  | **Method** | `FastMCP.import_server()` | `FastMCP.mount()` |
31
  | **Composition Type** | One-time copy (static) | Live link (dynamic) |
32
  | **Updates** | Changes to subserver NOT reflected | Changes to subserver immediately reflected |
 
 
33
  | **Best For** | Bundling finalized components | Modular runtime composition |
34
 
35
  ### Proxy Servers
 
182
 
183
  ### How Mounting Works
184
 
185
+ Mounting creates a relationship between two servers where one server (the parent) delegates certain operations to another (the mounted server) based on prefixes. When mounting is configured:
186
 
187
+ 1. **Live Link**: The parent server establishes a connection to the mounted server.
188
+ 2. **Dynamic Updates**: Changes made to the mounted server (e.g., adding new tools) are immediately reflected when accessed through the parent server.
189
+ 3. **Prefixed Access**: The parent server uses prefixes to route requests to the mounted server.
190
+ 4. **Delegation**: Requests for components matching the prefix are delegated to the mounted server at runtime.
191
 
192
  The same prefixing rules apply as with `import_server` for naming tools, resources, templates, and prompts.
193
 
 
205
  )
206
  ```
207
 
208
+ ### Direct vs. Proxy Mounting
209
+
210
+ <VersionBadge version="2.2.7" />
211
+
212
+ FastMCP supports two modes for mounting servers:
213
+
214
+ 1. **Direct Mounting** (default): The parent server directly accesses the mounted server's objects in memory for optimal performance and observability. In this mode:
215
+ - No client lifecycle events occur on the mounted server
216
+ - The mounted server's lifespan context is not executed
217
+ - Communication is handled through direct method calls
218
+
219
+ 2. **Proxy Mounting**: The parent server treats the mounted server as a separate entity and communicates with it through a client interface. In this mode:
220
+ - Full client lifecycle events occur on the mounted server
221
+ - The mounted server's lifespan is executed when a client connects
222
+ - Communication happens via an in-memory Client transport
223
+ - This preserves all client-facing behaviors but is slightly less efficient
224
+
225
+ You can control which mode to use with the `as_proxy` parameter:
226
+
227
+ ```python
228
+ # Direct mounting (default when no custom lifespan)
229
+ main_mcp.mount("api", api_server)
230
+
231
+ # Proxy mounting (preserves full client lifecycle)
232
+ main_mcp.mount("api", api_server, as_proxy=True)
233
+ ```
234
+
235
+ FastMCP automatically uses proxy mounting when the mounted server has a custom lifespan, but you can override this behavior by explicitly setting `as_proxy=False` or `as_proxy=True`.
236
+
237
+ #### Interaction with Proxy Servers
238
+
239
+ When using `FastMCP.from_client()` to create a proxy server, mounting that server will always use proxy mounting since the proxy server is already designed to be accessed via a client interface.
240
+
241
+ ```python
242
+ from fastmcp import FastMCP, Client
243
+
244
+ # Create a proxy for a remote server
245
+ remote_proxy = FastMCP.from_client(Client("http://example.com/mcp"))
246
+
247
+ # Mount the proxy - this will preserve full client lifecycle
248
+ main_server.mount("remote", remote_proxy)
249
+ ```
250
+
251
+ This is particularly useful for incorporating remote servers into your local application architecture.
252
+
253
 
254
  ## Example: Modular Application
255
 
docs/servers/fastmcp.mdx CHANGED
@@ -245,6 +245,7 @@ sub = FastMCP(name="Sub")
245
  def hello():
246
  return "hi"
247
 
 
248
  main.mount("sub", sub)
249
  ```
250
 
@@ -294,6 +295,40 @@ print(mcp.settings.on_duplicate_tools) # Output: "error"
294
 
295
  All of these can be configured directly as parameters when creating the `FastMCP` instance.
296
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
  ## Authentication
298
 
299
  <VersionBadge version="2.2.7" />
 
245
  def hello():
246
  return "hi"
247
 
248
+ # Mount directly
249
  main.mount("sub", sub)
250
  ```
251
 
 
295
 
296
  All of these can be configured directly as parameters when creating the `FastMCP` instance.
297
 
298
+ ### Custom Tool Serialization
299
+
300
+ <VersionBadge version="2.2.7" />
301
+
302
+ By default, FastMCP serializes tool return values to JSON when they need to be converted to text. You can customize this behavior by providing a `tool_serializer` function when creating your server:
303
+
304
+ ```python
305
+ import yaml
306
+ from fastmcp import FastMCP
307
+
308
+ # Define a custom serializer that formats dictionaries as YAML
309
+ def yaml_serializer(data):
310
+ return yaml.dump(data, sort_keys=False)
311
+
312
+ # Create a server with the custom serializer
313
+ mcp = FastMCP(name="MyServer", tool_serializer=yaml_serializer)
314
+
315
+ @mcp.tool()
316
+ def get_config():
317
+ """Returns configuration in YAML format."""
318
+ return {"api_key": "abc123", "debug": True, "rate_limit": 100}
319
+ ```
320
+
321
+ The serializer function takes any data object and returns a string representation. This is applied to **all non-string return values** from your tools. Tools that already return strings bypass the serializer.
322
+
323
+ This customization is useful when you want to:
324
+ - Format data in a specific way (like YAML or custom formats)
325
+ - Control specific serialization options (like indentation or sorting)
326
+ - Add metadata or transform data before sending it to clients
327
+
328
+ <Tip>
329
+ If the serializer function raises an exception, the tool will fall back to the default JSON serialization to avoid breaking the server.
330
+ </Tip>
331
+
332
  ## Authentication
333
 
334
  <VersionBadge version="2.2.7" />
examples/serializer.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ from typing import Any
3
+
4
+ import yaml
5
+
6
+ from fastmcp import FastMCP
7
+
8
+
9
+ # Define a simple custom serializer
10
+ def custom_dict_serializer(data: Any) -> str:
11
+ return yaml.dump(data, width=100, sort_keys=False)
12
+
13
+
14
+ server = FastMCP(name="CustomSerializerExample", tool_serializer=custom_dict_serializer)
15
+
16
+
17
+ @server.tool()
18
+ def get_example_data() -> dict:
19
+ """Returns some example data."""
20
+ return {"name": "Test", "value": 123, "status": True}
21
+
22
+
23
+ async def example_usage():
24
+ result = await server._mcp_call_tool("get_example_data", {})
25
+ print("Tool Result:")
26
+ print(result)
27
+ print("This is an example of using a custom serializer with FastMCP.")
28
+
29
+
30
+ if __name__ == "__main__":
31
+ asyncio.run(example_usage())
32
+ server.run()
src/fastmcp/server/openapi.py CHANGED
@@ -5,6 +5,7 @@ from __future__ import annotations
5
  import enum
6
  import json
7
  import re
 
8
  from dataclasses import dataclass
9
  from re import Pattern
10
  from typing import TYPE_CHECKING, Any, Literal
@@ -127,6 +128,7 @@ class OpenAPITool(Tool):
127
  tags: set[str] = set(),
128
  timeout: float | None = None,
129
  annotations: ToolAnnotations | None = None,
 
130
  ):
131
  super().__init__(
132
  name=name,
@@ -138,6 +140,7 @@ class OpenAPITool(Tool):
138
  context_kwarg="context", # Default context keyword argument
139
  tags=tags,
140
  annotations=annotations,
 
141
  )
142
  self._client = client
143
  self._route = route
 
5
  import enum
6
  import json
7
  import re
8
+ from collections.abc import Callable
9
  from dataclasses import dataclass
10
  from re import Pattern
11
  from typing import TYPE_CHECKING, Any, Literal
 
128
  tags: set[str] = set(),
129
  timeout: float | None = None,
130
  annotations: ToolAnnotations | None = None,
131
+ serializer: Callable[[Any], str] | None = None,
132
  ):
133
  super().__init__(
134
  name=name,
 
140
  context_kwarg="context", # Default context keyword argument
141
  tags=tags,
142
  annotations=annotations,
143
+ serializer=serializer,
144
  )
145
  self._client = client
146
  self._route = route
src/fastmcp/server/server.py CHANGED
@@ -205,6 +205,7 @@ class FastMCP(Generic[LifespanResultT]):
205
  | None
206
  ) = None,
207
  tags: set[str] | None = None,
 
208
  **settings: Any,
209
  ):
210
  self.tags: set[str] = tags or set()
@@ -218,7 +219,10 @@ class FastMCP(Generic[LifespanResultT]):
218
  self._mounted_servers: dict[str, MountedServer] = {}
219
 
220
  if lifespan is None:
 
221
  lifespan = default_lifespan
 
 
222
 
223
  self._mcp_server = MCPServer[LifespanResultT](
224
  name=name or "FastMCP",
@@ -226,7 +230,8 @@ class FastMCP(Generic[LifespanResultT]):
226
  lifespan=_lifespan_wrapper(self, lifespan),
227
  )
228
  self._tool_manager = ToolManager(
229
- duplicate_behavior=self.settings.on_duplicate_tools
 
230
  )
231
  self._resource_manager = ResourceManager(
232
  duplicate_behavior=self.settings.on_duplicate_resources
@@ -944,10 +949,62 @@ class FastMCP(Generic[LifespanResultT]):
944
  tool_separator: str | None = None,
945
  resource_separator: str | None = None,
946
  prompt_separator: str | None = None,
 
947
  ) -> None:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
948
  """
949
- Mount another FastMCP server on a given prefix.
950
- """
 
 
 
 
 
 
 
 
 
 
951
  mounted_server = MountedServer(
952
  server=server,
953
  prefix=prefix,
 
205
  | None
206
  ) = None,
207
  tags: set[str] | None = None,
208
+ tool_serializer: Callable[[Any], str] | None = None,
209
  **settings: Any,
210
  ):
211
  self.tags: set[str] = tags or set()
 
219
  self._mounted_servers: dict[str, MountedServer] = {}
220
 
221
  if lifespan is None:
222
+ self._has_lifespan = False
223
  lifespan = default_lifespan
224
+ else:
225
+ self._has_lifespan = True
226
 
227
  self._mcp_server = MCPServer[LifespanResultT](
228
  name=name or "FastMCP",
 
230
  lifespan=_lifespan_wrapper(self, lifespan),
231
  )
232
  self._tool_manager = ToolManager(
233
+ duplicate_behavior=self.settings.on_duplicate_tools,
234
+ serializer=tool_serializer,
235
  )
236
  self._resource_manager = ResourceManager(
237
  duplicate_behavior=self.settings.on_duplicate_resources
 
949
  tool_separator: str | None = None,
950
  resource_separator: str | None = None,
951
  prompt_separator: str | None = None,
952
+ as_proxy: bool | None = None,
953
  ) -> None:
954
+ """Mount another FastMCP server on this server with the given prefix.
955
+
956
+ Unlike importing (with import_server), mounting establishes a dynamic connection
957
+ between servers. When a client interacts with a mounted server's objects through
958
+ the parent server, requests are forwarded to the mounted server in real-time.
959
+ This means changes to the mounted server are immediately reflected when accessed
960
+ through the parent.
961
+
962
+ When a server is mounted:
963
+ - Tools from the mounted server are accessible with prefixed names using the tool_separator.
964
+ Example: If server has a tool named "get_weather", it will be available as "prefix_get_weather".
965
+ - Resources are accessible with prefixed URIs using the resource_separator.
966
+ Example: If server has a resource with URI "weather://forecast", it will be available as
967
+ "prefix+weather://forecast".
968
+ - Templates are accessible with prefixed URI templates using the resource_separator.
969
+ Example: If server has a template with URI "weather://location/{id}", it will be available
970
+ as "prefix+weather://location/{id}".
971
+ - Prompts are accessible with prefixed names using the prompt_separator.
972
+ Example: If server has a prompt named "weather_prompt", it will be available as
973
+ "prefix_weather_prompt".
974
+
975
+ There are two modes for mounting servers:
976
+ 1. Direct mounting (default when server has no custom lifespan): The parent server
977
+ directly accesses the mounted server's objects in-memory for better performance.
978
+ In this mode, no client lifecycle events occur on the mounted server, including
979
+ lifespan execution.
980
+
981
+ 2. Proxy mounting (default when server has a custom lifespan): The parent server
982
+ treats the mounted server as a separate entity and communicates with it via a
983
+ Client transport. This preserves all client-facing behaviors, including lifespan
984
+ execution, but with slightly higher overhead.
985
+
986
+ Args:
987
+ prefix: Prefix to use for the mounted server's objects.
988
+ server: The FastMCP server to mount.
989
+ tool_separator: Separator character for tool names (defaults to "_").
990
+ resource_separator: Separator character for resource URIs (defaults to "+").
991
+ prompt_separator: Separator character for prompt names (defaults to "_").
992
+ as_proxy: Whether to treat the mounted server as a proxy. If None (default),
993
+ automatically determined based on whether the server has a custom lifespan
994
+ (True if it has a custom lifespan, False otherwise).
995
  """
996
+ from fastmcp import Client
997
+ from fastmcp.client.transports import FastMCPTransport
998
+ from fastmcp.server.proxy import FastMCPProxy
999
+
1000
+ # if as_proxy is not specified and the server has a custom lifespan,
1001
+ # we should treat it as a proxy
1002
+ if as_proxy is None:
1003
+ as_proxy = server._has_lifespan
1004
+
1005
+ if as_proxy and not isinstance(server, FastMCPProxy):
1006
+ server = FastMCPProxy(Client(transport=FastMCPTransport(server)))
1007
+
1008
  mounted_server = MountedServer(
1009
  server=server,
1010
  prefix=prefix,
src/fastmcp/tools/tool.py CHANGED
@@ -11,6 +11,7 @@ from pydantic import BaseModel, BeforeValidator, Field
11
 
12
  from fastmcp.exceptions import ToolError
13
  from fastmcp.utilities.func_metadata import FuncMetadata, func_metadata
 
14
  from fastmcp.utilities.types import (
15
  Image,
16
  _convert_set_defaults,
@@ -23,6 +24,12 @@ if TYPE_CHECKING:
23
 
24
  from fastmcp.server import Context
25
 
 
 
 
 
 
 
26
 
27
  class Tool(BaseModel):
28
  """Internal tool registration info."""
@@ -45,6 +52,9 @@ class Tool(BaseModel):
45
  annotations: ToolAnnotations | None = Field(
46
  None, description="Additional annotations about the tool"
47
  )
 
 
 
48
 
49
  @classmethod
50
  def from_function(
@@ -55,6 +65,7 @@ class Tool(BaseModel):
55
  context_kwarg: str | None = None,
56
  tags: set[str] | None = None,
57
  annotations: ToolAnnotations | None = None,
 
58
  ) -> Tool:
59
  """Create a Tool from a function."""
60
  from fastmcp import Context
@@ -100,6 +111,7 @@ class Tool(BaseModel):
100
  context_kwarg=context_kwarg,
101
  tags=tags or set(),
102
  annotations=annotations,
 
103
  )
104
 
105
  async def run(
@@ -120,7 +132,7 @@ class Tool(BaseModel):
120
  arguments_to_validate=arguments,
121
  arguments_to_pass_directly=pass_args,
122
  )
123
- return _convert_to_content(result)
124
  except Exception as e:
125
  raise ToolError(f"Error executing tool {self.name}: {e}") from e
126
 
@@ -141,6 +153,7 @@ class Tool(BaseModel):
141
 
142
  def _convert_to_content(
143
  result: Any,
 
144
  _process_as_single_item: bool = False,
145
  ) -> list[TextContent | ImageContent | EmbeddedResource]:
146
  """Convert a result to a sequence of content objects."""
@@ -176,6 +189,17 @@ def _convert_to_content(
176
  return other_content + mcp_types
177
 
178
  if not isinstance(result, str):
179
- result = pydantic_core.to_json(result, fallback=str, indent=2).decode()
 
 
 
 
 
 
 
 
 
 
 
180
 
181
  return [TextContent(type="text", text=result)]
 
11
 
12
  from fastmcp.exceptions import ToolError
13
  from fastmcp.utilities.func_metadata import FuncMetadata, func_metadata
14
+ from fastmcp.utilities.logging import get_logger
15
  from fastmcp.utilities.types import (
16
  Image,
17
  _convert_set_defaults,
 
24
 
25
  from fastmcp.server import Context
26
 
27
+ logger = get_logger(__name__)
28
+
29
+
30
+ def default_serializer(data: Any) -> str:
31
+ return pydantic_core.to_json(data, fallback=str, indent=2).decode()
32
+
33
 
34
  class Tool(BaseModel):
35
  """Internal tool registration info."""
 
52
  annotations: ToolAnnotations | None = Field(
53
  None, description="Additional annotations about the tool"
54
  )
55
+ serializer: Callable[[Any], str] | None = Field(
56
+ None, description="Optional custom serializer for tool results"
57
+ )
58
 
59
  @classmethod
60
  def from_function(
 
65
  context_kwarg: str | None = None,
66
  tags: set[str] | None = None,
67
  annotations: ToolAnnotations | None = None,
68
+ serializer: Callable[[Any], str] | None = None,
69
  ) -> Tool:
70
  """Create a Tool from a function."""
71
  from fastmcp import Context
 
111
  context_kwarg=context_kwarg,
112
  tags=tags or set(),
113
  annotations=annotations,
114
+ serializer=serializer,
115
  )
116
 
117
  async def run(
 
132
  arguments_to_validate=arguments,
133
  arguments_to_pass_directly=pass_args,
134
  )
135
+ return _convert_to_content(result, serializer=self.serializer)
136
  except Exception as e:
137
  raise ToolError(f"Error executing tool {self.name}: {e}") from e
138
 
 
153
 
154
  def _convert_to_content(
155
  result: Any,
156
+ serializer: Callable[[Any], str] | None = None,
157
  _process_as_single_item: bool = False,
158
  ) -> list[TextContent | ImageContent | EmbeddedResource]:
159
  """Convert a result to a sequence of content objects."""
 
189
  return other_content + mcp_types
190
 
191
  if not isinstance(result, str):
192
+ if serializer is None:
193
+ result = default_serializer(result)
194
+ else:
195
+ try:
196
+ result = serializer(result)
197
+ except Exception as e:
198
+ logger.warning(
199
+ "Error serializing tool result: %s",
200
+ e,
201
+ exc_info=True,
202
+ )
203
+ result = default_serializer(result)
204
 
205
  return [TextContent(type="text", text=result)]
src/fastmcp/tools/tool_manager.py CHANGED
@@ -22,8 +22,13 @@ logger = get_logger(__name__)
22
  class ToolManager:
23
  """Manages FastMCP tools."""
24
 
25
- def __init__(self, duplicate_behavior: DuplicateBehavior | None = None):
 
 
 
 
26
  self._tools: dict[str, Tool] = {}
 
27
 
28
  # Default to "warn" if None is provided
29
  if duplicate_behavior is None:
@@ -70,6 +75,7 @@ class ToolManager:
70
  description=description,
71
  tags=tags,
72
  annotations=annotations,
 
73
  )
74
  return self.add_tool(tool)
75
 
 
22
  class ToolManager:
23
  """Manages FastMCP tools."""
24
 
25
+ def __init__(
26
+ self,
27
+ duplicate_behavior: DuplicateBehavior | None = None,
28
+ serializer: Callable[[Any], str] | None = None,
29
+ ):
30
  self._tools: dict[str, Tool] = {}
31
+ self._serializer = serializer
32
 
33
  # Default to "warn" if None is provided
34
  if duplicate_behavior is None:
 
75
  description=description,
76
  tags=tags,
77
  annotations=annotations,
78
+ serializer=self._serializer,
79
  )
80
  return self.add_tool(tool)
81
 
tests/server/test_mount.py CHANGED
@@ -1,4 +1,5 @@
1
  import json
 
2
 
3
  import pytest
4
  from mcp.server.lowlevel.helper_types import ReadResourceContents
@@ -8,6 +9,7 @@ from fastmcp import FastMCP
8
  from fastmcp.client import Client
9
  from fastmcp.client.transports import FastMCPTransport
10
  from fastmcp.exceptions import NotFoundError
 
11
 
12
 
13
  class TestBasicMount:
@@ -427,3 +429,110 @@ class TestProxyServer:
427
  result = await main_app._mcp_get_prompt("proxy_welcome", {"name": "World"})
428
  assert result.messages is not None
429
  # The message should contain our welcome text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import json
2
+ from contextlib import asynccontextmanager
3
 
4
  import pytest
5
  from mcp.server.lowlevel.helper_types import ReadResourceContents
 
9
  from fastmcp.client import Client
10
  from fastmcp.client.transports import FastMCPTransport
11
  from fastmcp.exceptions import NotFoundError
12
+ from fastmcp.server.proxy import FastMCPProxy
13
 
14
 
15
  class TestBasicMount:
 
429
  result = await main_app._mcp_get_prompt("proxy_welcome", {"name": "World"})
430
  assert result.messages is not None
431
  # The message should contain our welcome text
432
+
433
+
434
+ class TestAsProxyKwarg:
435
+ """Test the as_proxy kwarg."""
436
+
437
+ async def test_as_proxy_defaults_false(self):
438
+ mcp = FastMCP("Main")
439
+ sub = FastMCP("Sub")
440
+
441
+ mcp.mount("sub", sub)
442
+
443
+ assert mcp._mounted_servers["sub"].server is sub
444
+
445
+ async def test_as_proxy_false(self):
446
+ mcp = FastMCP("Main")
447
+ sub = FastMCP("Sub")
448
+
449
+ mcp.mount("sub", sub, as_proxy=False)
450
+
451
+ assert mcp._mounted_servers["sub"].server is sub
452
+
453
+ async def test_as_proxy_true(self):
454
+ mcp = FastMCP("Main")
455
+ sub = FastMCP("Sub")
456
+
457
+ mcp.mount("sub", sub, as_proxy=True)
458
+
459
+ assert mcp._mounted_servers["sub"].server is not sub
460
+ assert isinstance(mcp._mounted_servers["sub"].server, FastMCPProxy)
461
+
462
+ async def test_as_proxy_defaults_true_if_lifespan(self):
463
+ @asynccontextmanager
464
+ async def lifespan(mcp: FastMCP):
465
+ yield
466
+
467
+ mcp = FastMCP("Main")
468
+ sub = FastMCP("Sub", lifespan=lifespan)
469
+
470
+ mcp.mount("sub", sub)
471
+
472
+ assert mcp._mounted_servers["sub"].server is not sub
473
+ assert isinstance(mcp._mounted_servers["sub"].server, FastMCPProxy)
474
+
475
+ async def test_as_proxy_ignored_for_proxy_mounts_default(self):
476
+ mcp = FastMCP("Main")
477
+ sub = FastMCP("Sub")
478
+ sub_proxy = FastMCP.from_client(Client(transport=FastMCPTransport(sub)))
479
+
480
+ mcp.mount("sub", sub_proxy)
481
+
482
+ assert mcp._mounted_servers["sub"].server is sub_proxy
483
+
484
+ async def test_as_proxy_ignored_for_proxy_mounts_false(self):
485
+ mcp = FastMCP("Main")
486
+ sub = FastMCP("Sub")
487
+ sub_proxy = FastMCP.from_client(Client(transport=FastMCPTransport(sub)))
488
+
489
+ mcp.mount("sub", sub_proxy, as_proxy=False)
490
+
491
+ assert mcp._mounted_servers["sub"].server is sub_proxy
492
+
493
+ async def test_as_proxy_ignored_for_proxy_mounts_true(self):
494
+ mcp = FastMCP("Main")
495
+ sub = FastMCP("Sub")
496
+ sub_proxy = FastMCP.from_client(Client(transport=FastMCPTransport(sub)))
497
+
498
+ mcp.mount("sub", sub_proxy, as_proxy=True)
499
+
500
+ assert mcp._mounted_servers["sub"].server is sub_proxy
501
+
502
+ async def test_as_proxy_mounts_still_have_live_link(self):
503
+ mcp = FastMCP("Main")
504
+ sub = FastMCP("Sub")
505
+
506
+ mcp.mount("sub", sub, as_proxy=True)
507
+
508
+ assert len(await mcp.get_tools()) == 0
509
+
510
+ @sub.tool()
511
+ def hello():
512
+ return "hi"
513
+
514
+ assert len(await mcp.get_tools()) == 1
515
+
516
+ async def test_sub_lifespan_is_executed(self):
517
+ lifespan_check = []
518
+
519
+ @asynccontextmanager
520
+ async def lifespan(mcp: FastMCP):
521
+ lifespan_check.append("start")
522
+ yield
523
+
524
+ mcp = FastMCP("Main")
525
+ sub = FastMCP("Sub", lifespan=lifespan)
526
+
527
+ @sub.tool()
528
+ def hello():
529
+ return "hi"
530
+
531
+ mcp.mount("sub", sub, as_proxy=True)
532
+
533
+ assert lifespan_check == []
534
+
535
+ async with Client(mcp) as client:
536
+ await client.call_tool("sub_hello", {})
537
+
538
+ assert lifespan_check == ["start"]
tests/tools/test_tool_manager.py CHANGED
@@ -1,7 +1,9 @@
1
  import json
2
  import logging
3
- from typing import Annotated
 
4
 
 
5
  import pytest
6
  from mcp.server.session import ServerSessionT
7
  from mcp.shared.context import LifespanContextT
@@ -392,6 +394,51 @@ class TestCallTools:
392
  assert isinstance(result[0], TextContent)
393
  assert result[0].text == '[\n "rex",\n "gertrude"\n]'
394
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
395
 
396
  class TestToolSchema:
397
  async def test_context_arg_excluded_from_schema(self):
 
1
  import json
2
  import logging
3
+ import uuid
4
+ from typing import Annotated, Any
5
 
6
+ import pydantic_core
7
  import pytest
8
  from mcp.server.session import ServerSessionT
9
  from mcp.shared.context import LifespanContextT
 
394
  assert isinstance(result[0], TextContent)
395
  assert result[0].text == '[\n "rex",\n "gertrude"\n]'
396
 
397
+ async def test_call_tool_with_custom_serializer(self):
398
+ """Test that a custom serializer provided to FastMCP is used by tools."""
399
+
400
+ def custom_serializer(data: Any) -> str:
401
+ if isinstance(data, dict):
402
+ return f"CUSTOM:{json.dumps(data)}"
403
+ return json.dumps(data)
404
+
405
+ # Instantiate FastMCP with the custom serializer
406
+ mcp = FastMCP(tool_serializer=custom_serializer)
407
+ manager = mcp._tool_manager
408
+
409
+ def get_data() -> dict:
410
+ return {"key": "value", "number": 123}
411
+
412
+ manager.add_tool_from_fn(get_data)
413
+
414
+ result = await manager.call_tool("get_data", {})
415
+ assert isinstance(result, list)
416
+ assert len(result) == 1
417
+ assert isinstance(result[0], TextContent)
418
+ assert result[0].text == 'CUSTOM:{"key": "value", "number": 123}'
419
+
420
+ async def test_custom_serializer_fallback_on_error(self):
421
+ """Test that a broken custom serializer gracefully falls back."""
422
+
423
+ uuid_result = uuid.uuid4()
424
+
425
+ def custom_serializer(data: Any) -> str:
426
+ return json.dumps(data)
427
+
428
+ mcp = FastMCP(tool_serializer=custom_serializer)
429
+ manager = mcp._tool_manager
430
+
431
+ def get_data() -> uuid.UUID:
432
+ return uuid_result
433
+
434
+ manager.add_tool_from_fn(get_data)
435
+
436
+ result = await manager.call_tool("get_data", {})
437
+ assert isinstance(result, list)
438
+ assert len(result) == 1
439
+ assert isinstance(result[0], TextContent)
440
+ assert result[0].text == pydantic_core.to_json(uuid_result).decode()
441
+
442
 
443
  class TestToolSchema:
444
  async def test_context_arg_excluded_from_schema(self):