Jeremiah Lowin commited on
Commit
cca52df
·
2 Parent(s): 69b7117e3c1e69

Merge branch 'main' into include-exclude

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .github/dependabot.yml +20 -0
  2. .github/release.yml +10 -0
  3. .github/workflows/publish.yml +1 -1
  4. .github/workflows/run-static.yml +1 -1
  5. .github/workflows/run-tests.yml +1 -1
  6. README.md +2 -0
  7. docs/assets/images/tutorial-rest-api-result.png +3 -0
  8. docs/assets/updates/release-2-7.png +3 -0
  9. docs/changelog.mdx +1062 -0
  10. docs/clients/auth/oauth.mdx +3 -3
  11. docs/docs.json +77 -62
  12. docs/getting-started/installation.mdx +1 -1
  13. docs/getting-started/welcome.mdx +10 -4
  14. docs/integrations/anthropic.mdx +8 -11
  15. docs/integrations/claude-desktop.mdx +1 -1
  16. docs/integrations/contrib.mdx +1 -1
  17. docs/integrations/gemini.mdx +2 -2
  18. docs/integrations/openai.mdx +3 -4
  19. docs/patterns/tool-transformation.mdx +454 -0
  20. docs/servers/composition.mdx +1 -1
  21. docs/servers/openapi.mdx +18 -37
  22. docs/servers/prompts.mdx +27 -0
  23. docs/servers/resources.mdx +27 -0
  24. docs/servers/tools.mdx +47 -16
  25. docs/tutorials/create-mcp-server.mdx +198 -0
  26. docs/tutorials/mcp.mdx +120 -0
  27. docs/tutorials/rest-api.mdx +203 -0
  28. docs/updates.mdx +126 -4
  29. pyproject.toml +2 -0
  30. src/fastmcp/__init__.py +4 -1
  31. src/fastmcp/cli/cli.py +3 -2
  32. src/fastmcp/client/auth/oauth.py +1 -1
  33. src/fastmcp/client/client.py +3 -1
  34. src/fastmcp/client/transports.py +43 -37
  35. src/fastmcp/exceptions.py +4 -0
  36. src/fastmcp/prompts/prompt.py +8 -18
  37. src/fastmcp/prompts/prompt_manager.py +3 -2
  38. src/fastmcp/resources/resource.py +21 -26
  39. src/fastmcp/resources/resource_manager.py +3 -2
  40. src/fastmcp/resources/template.py +8 -16
  41. src/fastmcp/server/auth/providers/bearer_env.py +8 -11
  42. src/fastmcp/server/dependencies.py +1 -0
  43. src/fastmcp/server/http.py +2 -1
  44. src/fastmcp/server/openapi.py +3 -11
  45. src/fastmcp/server/proxy.py +27 -14
  46. src/fastmcp/server/server.py +273 -142
  47. src/fastmcp/settings.py +73 -37
  48. src/fastmcp/tools/__init__.py +2 -1
  49. src/fastmcp/tools/tool.py +116 -73
  50. src/fastmcp/tools/tool_manager.py +3 -2
.github/dependabot.yml ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "uv"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "daily"
7
+ labels:
8
+ - "dependencies"
9
+ - package-ecosystem: "pip"
10
+ directory: "/"
11
+ schedule:
12
+ interval: "daily"
13
+ labels:
14
+ - "dependencies"
15
+ - package-ecosystem: "github-actions"
16
+ directory: "/"
17
+ schedule:
18
+ interval: "weekly"
19
+ labels:
20
+ - "dependencies"
.github/release.yml CHANGED
@@ -7,6 +7,12 @@ changelog:
7
  - title: New Features 🎉
8
  labels:
9
  - feature
 
 
 
 
 
 
10
  - enhancement
11
  exclude:
12
  labels:
@@ -27,6 +33,10 @@ changelog:
27
  labels:
28
  - documentation
29
 
 
 
 
 
30
  - title: Other Changes 🦾
31
  labels:
32
  - "*"
 
7
  - title: New Features 🎉
8
  labels:
9
  - feature
10
+ exclude:
11
+ labels:
12
+ - breaking change
13
+
14
+ - title: Enhancements 🔧
15
+ labels:
16
  - enhancement
17
  exclude:
18
  labels:
 
33
  labels:
34
  - documentation
35
 
36
+ - title: Dependencies 📦
37
+ labels:
38
+ - dependencies
39
+
40
  - title: Other Changes 🦾
41
  labels:
42
  - "*"
.github/workflows/publish.yml CHANGED
@@ -17,7 +17,7 @@ jobs:
17
  fetch-depth: 0
18
 
19
  - name: "Install uv"
20
- uses: astral-sh/setup-uv@v3
21
 
22
  - name: Build
23
  run: uv build
 
17
  fetch-depth: 0
18
 
19
  - name: "Install uv"
20
+ uses: astral-sh/setup-uv@v6
21
 
22
  - name: Build
23
  run: uv build
.github/workflows/run-static.yml CHANGED
@@ -32,7 +32,7 @@ jobs:
32
  steps:
33
  - uses: actions/checkout@v4
34
  - name: Install uv
35
- uses: astral-sh/setup-uv@v5
36
  with:
37
  enable-cache: true
38
  cache-dependency-glob: "uv.lock"
 
32
  steps:
33
  - uses: actions/checkout@v4
34
  - name: Install uv
35
+ uses: astral-sh/setup-uv@v6
36
  with:
37
  enable-cache: true
38
  cache-dependency-glob: "uv.lock"
.github/workflows/run-tests.yml CHANGED
@@ -37,7 +37,7 @@ jobs:
37
  - uses: actions/checkout@v4
38
 
39
  - name: Install uv
40
- uses: astral-sh/setup-uv@v5
41
  with:
42
  enable-cache: true
43
  cache-dependency-glob: "uv.lock"
 
37
  - uses: actions/checkout@v4
38
 
39
  - name: Install uv
40
+ uses: astral-sh/setup-uv@v6
41
  with:
42
  enable-cache: true
43
  cache-dependency-glob: "uv.lock"
README.md CHANGED
@@ -4,6 +4,8 @@
4
  # FastMCP v2 🚀
5
  <strong>The fast, Pythonic way to build MCP servers and clients.</strong>
6
 
 
 
7
  [![Docs](https://img.shields.io/badge/docs-gofastmcp.com-blue)](https://gofastmcp.com)
8
  [![PyPI - Version](https://img.shields.io/pypi/v/fastmcp.svg)](https://pypi.org/project/fastmcp)
9
  [![Tests](https://github.com/jlowin/fastmcp/actions/workflows/run-tests.yml/badge.svg)](https://github.com/jlowin/fastmcp/actions/workflows/run-tests.yml)
 
4
  # FastMCP v2 🚀
5
  <strong>The fast, Pythonic way to build MCP servers and clients.</strong>
6
 
7
+ *FastMCP is made with 💙 by [Prefect](https://www.prefect.io/)*
8
+
9
  [![Docs](https://img.shields.io/badge/docs-gofastmcp.com-blue)](https://gofastmcp.com)
10
  [![PyPI - Version](https://img.shields.io/pypi/v/fastmcp.svg)](https://pypi.org/project/fastmcp)
11
  [![Tests](https://github.com/jlowin/fastmcp/actions/workflows/run-tests.yml/badge.svg)](https://github.com/jlowin/fastmcp/actions/workflows/run-tests.yml)
docs/assets/images/tutorial-rest-api-result.png ADDED

Git LFS Details

  • SHA256: 6b4380750851e27f9f246bcb01bf936b98da1fbf660520cb42b6d0b91fff03b1
  • Pointer size: 131 Bytes
  • Size of remote file: 661 kB
docs/assets/updates/release-2-7.png ADDED

Git LFS Details

  • SHA256: 0e49b3d8194dfc8cc0a20ef62c34e665594efa31449b2a3c3c3080a13b10c1d6
  • Pointer size: 131 Bytes
  • Size of remote file: 422 kB
docs/changelog.mdx ADDED
@@ -0,0 +1,1062 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ mode: center
3
+ ---
4
+
5
+ <Update label="v2.7.1" description="2024-06-08">
6
+
7
+ ## [v2.7.1: The Bearer Necessities](https://github.com/jlowin/fastmcp/releases/tag/v2.7.1)
8
+
9
+ This release primarily contains a fix for parsing string tokens that are provided to FastMCP clients.
10
+
11
+ ### New Features 🎉
12
+
13
+ * Respect cache setting, set default to 1 second by [@jlowin](https://github.com/jlowin) in [#747](https://github.com/jlowin/fastmcp/pull/747)
14
+
15
+ ### Fixes 🐞
16
+
17
+ * Ensure event store is properly typed by [@jlowin](https://github.com/jlowin) in [#753](https://github.com/jlowin/fastmcp/pull/753)
18
+ * Fix passing token string to client auth & add auth to MCPConfig clients by [@jlowin](https://github.com/jlowin) in [#754](https://github.com/jlowin/fastmcp/pull/754)
19
+
20
+ ### Docs 📚
21
+
22
+ * Docs : fix client to mcp\_client in Gemini example by [@yrangana](https://github.com/yrangana) in [#734](https://github.com/jlowin/fastmcp/pull/734)
23
+ * update add tool docstring by [@strawgate](https://github.com/strawgate) in [#739](https://github.com/jlowin/fastmcp/pull/739)
24
+ * Fix contrib link by [@richardkmichael](https://github.com/richardkmichael) in [#749](https://github.com/jlowin/fastmcp/pull/749)
25
+
26
+ ### Other Changes 🦾
27
+
28
+ * Switch Pydantic defaults to kwargs by [@strawgate](https://github.com/strawgate) in [#731](https://github.com/jlowin/fastmcp/pull/731)
29
+ * Fix Typo in CLI module by [@wfclark5](https://github.com/wfclark5) in [#737](https://github.com/jlowin/fastmcp/pull/737)
30
+ * chore: fix prompt docstring by [@danb27](https://github.com/danb27) in [#752](https://github.com/jlowin/fastmcp/pull/752)
31
+ * Add accept to excluded headers by [@jlowin](https://github.com/jlowin) in [#751](https://github.com/jlowin/fastmcp/pull/751)
32
+
33
+ ### New Contributors
34
+
35
+ * [@wfclark5](https://github.com/wfclark5) made their first contribution in [#737](https://github.com/jlowin/fastmcp/pull/737)
36
+ * [@richardkmichael](https://github.com/richardkmichael) made their first contribution in [#749](https://github.com/jlowin/fastmcp/pull/749)
37
+ * [@danb27](https://github.com/danb27) made their first contribution in [#752](https://github.com/jlowin/fastmcp/pull/752)
38
+
39
+ **Full Changelog**: [v2.7.0...v2.7.1](https://github.com/jlowin/fastmcp/compare/v2.7.0...v2.7.1)
40
+ </Update>
41
+
42
+ <Update label="v2.7.0" description="2024-06-05">
43
+
44
+ ## [v2.7.0: Pare Programming](https://github.com/jlowin/fastmcp/releases/tag/v2.7.0)
45
+
46
+ This is primarily a housekeeping release to remove or deprecate cruft that's accumulated since v1. Primarily, this release refactors FastMCP's internals in preparation for features planned in the next few major releases. However please note that as a result, this release has some minor breaking changes (which is why it's 2.7, not 2.6.2, in accordance with repo guidelines) though not to the core user-facing APIs.
47
+
48
+ ### Breaking Changes 🛫
49
+
50
+ * decorators return the objects they create, not the decorated function
51
+ * websockets is an optional dependency
52
+ * methods on the server for automatically converting functions into tools/resources/prompts have been deprecated in favor of using the decorators directly
53
+
54
+ ### New Features 🎉
55
+
56
+ * allow passing flags to servers by [@zzstoatzz](https://github.com/zzstoatzz) in [#690](https://github.com/jlowin/fastmcp/pull/690)
57
+ * replace $ref pointing to `#/components/schemas/` with `#/$defs/` by [@phateffect](https://github.com/phateffect) in [#697](https://github.com/jlowin/fastmcp/pull/697)
58
+ * Split Tool into Tool and FunctionTool by [@jlowin](https://github.com/jlowin) in [#700](https://github.com/jlowin/fastmcp/pull/700)
59
+ * Use strict basemodel for Prompt; relax from\_function deprecation by [@jlowin](https://github.com/jlowin) in [#701](https://github.com/jlowin/fastmcp/pull/701)
60
+ * Formalize resource/functionresource replationship by [@jlowin](https://github.com/jlowin) in [#702](https://github.com/jlowin/fastmcp/pull/702)
61
+ * Formalize template/functiontemplate split by [@jlowin](https://github.com/jlowin) in [#703](https://github.com/jlowin/fastmcp/pull/703)
62
+ * Support flexible @tool decorator call patterns by [@jlowin](https://github.com/jlowin) in [#706](https://github.com/jlowin/fastmcp/pull/706)
63
+ * Ensure deprecation warnings have stacklevel=2 by [@jlowin](https://github.com/jlowin) in [#710](https://github.com/jlowin/fastmcp/pull/710)
64
+ * Allow naked prompt decorator by [@jlowin](https://github.com/jlowin) in [#711](https://github.com/jlowin/fastmcp/pull/711)
65
+
66
+ ### Fixes 🐞
67
+
68
+ * Updates / Fixes for Tool Content Conversion by [@strawgate](https://github.com/strawgate) in [#642](https://github.com/jlowin/fastmcp/pull/642)
69
+ * Fix pr labeler permissions by [@jlowin](https://github.com/jlowin) in [#708](https://github.com/jlowin/fastmcp/pull/708)
70
+ * remove -n auto by [@jlowin](https://github.com/jlowin) in [#709](https://github.com/jlowin/fastmcp/pull/709)
71
+ * Fix links in README.md by [@alainivars](https://github.com/alainivars) in [#723](https://github.com/jlowin/fastmcp/pull/723)
72
+
73
+ Happily, this release DOES permit the use of "naked" decorators to align with Pythonic practice:
74
+
75
+ ```python
76
+ @mcp.tool
77
+ def my_tool():
78
+ ...
79
+ ```
80
+
81
+ **Full Changelog**: [v2.6.2...v2.7.0](https://github.com/jlowin/fastmcp/compare/v2.6.2...v2.7.0)
82
+ </Update>
83
+
84
+ <Update label="v2.6.1" description="2024-06-03">
85
+
86
+ ## [v2.6.1: Blast Auth (second ignition)](https://github.com/jlowin/fastmcp/releases/tag/v2.6.1)
87
+
88
+ This is a patch release to restore py.typed in #686.
89
+
90
+ ### Docs 📚
91
+
92
+ * Update readme by [@jlowin](https://github.com/jlowin) in [#679](https://github.com/jlowin/fastmcp/pull/679)
93
+ * Add gemini tutorial by [@jlowin](https://github.com/jlowin) in [#680](https://github.com/jlowin/fastmcp/pull/680)
94
+ * Fix : fix path error to CLI Documentation by [@yrangana](https://github.com/yrangana) in [#684](https://github.com/jlowin/fastmcp/pull/684)
95
+ * Update auth docs by [@jlowin](https://github.com/jlowin) in [#687](https://github.com/jlowin/fastmcp/pull/687)
96
+
97
+ ### Other Changes 🦾
98
+
99
+ * Remove deprecation notice by [@jlowin](https://github.com/jlowin) in [#677](https://github.com/jlowin/fastmcp/pull/677)
100
+ * Delete server.py by [@jlowin](https://github.com/jlowin) in [#681](https://github.com/jlowin/fastmcp/pull/681)
101
+ * Restore py.typed by [@jlowin](https://github.com/jlowin) in [#686](https://github.com/jlowin/fastmcp/pull/686)
102
+
103
+ ### New Contributors
104
+
105
+ * [@yrangana](https://github.com/yrangana) made their first contribution in [#684](https://github.com/jlowin/fastmcp/pull/684)
106
+
107
+ **Full Changelog**: [v2.6.0...v2.6.1](https://github.com/jlowin/fastmcp/compare/v2.6.0...v2.6.1)
108
+ </Update>
109
+
110
+ <Update label="v2.6.0" description="2024-06-02">
111
+
112
+ ## [v2.6.0: Blast Auth](https://github.com/jlowin/fastmcp/releases/tag/v2.6.0)
113
+
114
+ ### New Features 🎉
115
+
116
+ * Introduce MCP client oauth flow by [@jlowin](https://github.com/jlowin) in [#478](https://github.com/jlowin/fastmcp/pull/478)
117
+ * Support providing tools at init by [@jlowin](https://github.com/jlowin) in [#647](https://github.com/jlowin/fastmcp/pull/647)
118
+ * Simplify code for running servers in processes during tests by [@jlowin](https://github.com/jlowin) in [#649](https://github.com/jlowin/fastmcp/pull/649)
119
+ * Add basic bearer auth for server and client by [@jlowin](https://github.com/jlowin) in [#650](https://github.com/jlowin/fastmcp/pull/650)
120
+ * Support configuring bearer auth from env vars by [@jlowin](https://github.com/jlowin) in [#652](https://github.com/jlowin/fastmcp/pull/652)
121
+ * feat(tool): add support for excluding arguments from tool definition by [@deepak-stratforge](https://github.com/deepak-stratforge) in [#626](https://github.com/jlowin/fastmcp/pull/626)
122
+ * Add docs for server + client auth by [@jlowin](https://github.com/jlowin) in [#655](https://github.com/jlowin/fastmcp/pull/655)
123
+
124
+ ### Fixes 🐞
125
+
126
+ * fix: Support concurrency in FastMcpProxy (and Client) by [@Sillocan](https://github.com/Sillocan) in [#635](https://github.com/jlowin/fastmcp/pull/635)
127
+ * Ensure Client.close() cleans up client context appropriately by [@jlowin](https://github.com/jlowin) in [#643](https://github.com/jlowin/fastmcp/pull/643)
128
+ * Update client.mdx: ClientError namespace by [@mjkaye](https://github.com/mjkaye) in [#657](https://github.com/jlowin/fastmcp/pull/657)
129
+
130
+ ### Docs 📚
131
+
132
+ * Make FastMCPTransport support simulated Streamable HTTP Transport (didn't work) by [@jlowin](https://github.com/jlowin) in [#645](https://github.com/jlowin/fastmcp/pull/645)
133
+ * Document exclude\_args by [@jlowin](https://github.com/jlowin) in [#653](https://github.com/jlowin/fastmcp/pull/653)
134
+ * Update welcome by [@jlowin](https://github.com/jlowin) in [#673](https://github.com/jlowin/fastmcp/pull/673)
135
+ * Add Anthropic + Claude desktop integration guides by [@jlowin](https://github.com/jlowin) in [#674](https://github.com/jlowin/fastmcp/pull/674)
136
+ * Minor docs design updates by [@jlowin](https://github.com/jlowin) in [#676](https://github.com/jlowin/fastmcp/pull/676)
137
+
138
+ ### Other Changes 🦾
139
+
140
+ * Update test typing by [@jlowin](https://github.com/jlowin) in [#646](https://github.com/jlowin/fastmcp/pull/646)
141
+ * Add OpenAI integration docs by [@jlowin](https://github.com/jlowin) in [#660](https://github.com/jlowin/fastmcp/pull/660)
142
+
143
+ ### New Contributors
144
+
145
+ * [@Sillocan](https://github.com/Sillocan) made their first contribution in [#635](https://github.com/jlowin/fastmcp/pull/635)
146
+ * [@deepak-stratforge](https://github.com/deepak-stratforge) made their first contribution in [#626](https://github.com/jlowin/fastmcp/pull/626)
147
+ * [@mjkaye](https://github.com/mjkaye) made their first contribution in [#657](https://github.com/jlowin/fastmcp/pull/657)
148
+
149
+ **Full Changelog**: [v2.5.2...v2.6.0](https://github.com/jlowin/fastmcp/compare/v2.5.2...v2.6.0)
150
+ </Update>
151
+
152
+ <Update label="v2.5.2" description="2024-05-29">
153
+
154
+ ## [v2.5.2: Stayin' Alive](https://github.com/jlowin/fastmcp/releases/tag/v2.5.2)
155
+
156
+ ### New Features 🎉
157
+
158
+ * Add graceful error handling for unreachable mounted servers by [@davenpi](https://github.com/davenpi) in [#605](https://github.com/jlowin/fastmcp/pull/605)
159
+ * Improve type inference from client transport by [@jlowin](https://github.com/jlowin) in [#623](https://github.com/jlowin/fastmcp/pull/623)
160
+ * Add keep\_alive param to reuse subprocess by [@jlowin](https://github.com/jlowin) in [#624](https://github.com/jlowin/fastmcp/pull/624)
161
+
162
+ ### Fixes 🐞
163
+
164
+ * Fix handling tools without descriptions by [@jlowin](https://github.com/jlowin) in [#610](https://github.com/jlowin/fastmcp/pull/610)
165
+ * Don't print env vars to console when format is wrong by [@jlowin](https://github.com/jlowin) in [#615](https://github.com/jlowin/fastmcp/pull/615)
166
+ * Ensure behavior-affecting headers are excluded when forwarding proxies/openapi by [@jlowin](https://github.com/jlowin) in [#620](https://github.com/jlowin/fastmcp/pull/620)
167
+
168
+ ### Docs 📚
169
+
170
+ * Add notes about uv and claude desktop by [@jlowin](https://github.com/jlowin) in [#597](https://github.com/jlowin/fastmcp/pull/597)
171
+
172
+ ### Other Changes 🦾
173
+
174
+ * add init\_timeout for mcp client by [@jfouret](https://github.com/jfouret) in [#607](https://github.com/jlowin/fastmcp/pull/607)
175
+ * Add init\_timeout for mcp client (incl settings) by [@jlowin](https://github.com/jlowin) in [#609](https://github.com/jlowin/fastmcp/pull/609)
176
+ * Support for uppercase letters at the log level by [@ksawaray](https://github.com/ksawaray) in [#625](https://github.com/jlowin/fastmcp/pull/625)
177
+
178
+ ### New Contributors
179
+
180
+ * [@jfouret](https://github.com/jfouret) made their first contribution in [#607](https://github.com/jlowin/fastmcp/pull/607)
181
+ * [@ksawaray](https://github.com/ksawaray) made their first contribution in [#625](https://github.com/jlowin/fastmcp/pull/625)
182
+
183
+ **Full Changelog**: [v2.5.1...v2.5.2](https://github.com/jlowin/fastmcp/compare/v2.5.1...v2.5.2)
184
+ </Update>
185
+
186
+ <Update label="v2.5.1" description="2024-05-24">
187
+
188
+ ## [v2.5.1: Route Awakening (Part 2)](https://github.com/jlowin/fastmcp/releases/tag/v2.5.1)
189
+
190
+ ### Fixes 🐞
191
+
192
+ * Ensure content-length is always stripped from client headers by [@jlowin](https://github.com/jlowin) in [#589](https://github.com/jlowin/fastmcp/pull/589)
193
+
194
+ ### Docs 📚
195
+
196
+ * Fix redundant section of docs by [@jlowin](https://github.com/jlowin) in [#583](https://github.com/jlowin/fastmcp/pull/583)
197
+
198
+ **Full Changelog**: [v2.5.0...v2.5.1](https://github.com/jlowin/fastmcp/compare/v2.5.0...v2.5.1)
199
+ </Update>
200
+
201
+ <Update label="v2.5.0" description="2024-05-24">
202
+
203
+ ## [v2.5.0: Route Awakening](https://github.com/jlowin/fastmcp/releases/tag/v2.5.0)
204
+
205
+ This release introduces completely new tools for generating and customizing MCP servers from OpenAPI specs and FastAPI apps, including popular requests like mechanisms for determining what routes map to what MCP components; renaming routes; and customizing the generated MCP components.
206
+
207
+ ### New Features 🎉
208
+
209
+ * Add FastMCP 1.0 server support for in-memory Client / Testing by [@jlowin](https://github.com/jlowin) in [#539](https://github.com/jlowin/fastmcp/pull/539)
210
+ * Minor addition: add transport to stdio server in mcpconfig, with default by [@jlowin](https://github.com/jlowin) in [#555](https://github.com/jlowin/fastmcp/pull/555)
211
+ * Raise an error if a Client is created with no servers in config by [@jlowin](https://github.com/jlowin) in [#554](https://github.com/jlowin/fastmcp/pull/554)
212
+ * Expose model preferences in `Context.sample` for flexible model selection. by [@davenpi](https://github.com/davenpi) in [#542](https://github.com/jlowin/fastmcp/pull/542)
213
+ * Ensure custom routes are respected by [@jlowin](https://github.com/jlowin) in [#558](https://github.com/jlowin/fastmcp/pull/558)
214
+ * Add client method to send cancellation notifications by [@davenpi](https://github.com/davenpi) in [#563](https://github.com/jlowin/fastmcp/pull/563)
215
+ * Enhance route map logic for include/exclude OpenAPI routes by [@jlowin](https://github.com/jlowin) in [#564](https://github.com/jlowin/fastmcp/pull/564)
216
+ * Add tag-based route maps by [@jlowin](https://github.com/jlowin) in [#565](https://github.com/jlowin/fastmcp/pull/565)
217
+ * Add advanced control of openAPI route creation by [@jlowin](https://github.com/jlowin) in [#566](https://github.com/jlowin/fastmcp/pull/566)
218
+ * Make error masking configurable by [@jlowin](https://github.com/jlowin) in [#550](https://github.com/jlowin/fastmcp/pull/550)
219
+ * Ensure client headers are passed through to remote servers by [@jlowin](https://github.com/jlowin) in [#575](https://github.com/jlowin/fastmcp/pull/575)
220
+ * Use lowercase name for headers when comparing by [@jlowin](https://github.com/jlowin) in [#576](https://github.com/jlowin/fastmcp/pull/576)
221
+ * Permit more flexible name generation for OpenAPI servers by [@jlowin](https://github.com/jlowin) in [#578](https://github.com/jlowin/fastmcp/pull/578)
222
+ * Ensure that tools/templates/prompts are compatible with callable objects by [@jlowin](https://github.com/jlowin) in [#579](https://github.com/jlowin/fastmcp/pull/579)
223
+
224
+ ### Docs 📚
225
+
226
+ * Add version badge for prefix formats by [@jlowin](https://github.com/jlowin) in [#537](https://github.com/jlowin/fastmcp/pull/537)
227
+ * Add versioning note to docs by [@jlowin](https://github.com/jlowin) in [#551](https://github.com/jlowin/fastmcp/pull/551)
228
+ * Bump 2.3.6 references to 2.4.0 by [@jlowin](https://github.com/jlowin) in [#567](https://github.com/jlowin/fastmcp/pull/567)
229
+
230
+ **Full Changelog**: [v2.4.0...v2.5.0](https://github.com/jlowin/fastmcp/compare/v2.4.0...v2.5.0)
231
+ </Update>
232
+
233
+ <Update label="v2.4.0" description="2024-05-21">
234
+
235
+ ## [v2.4.0: Config and Conquer](https://github.com/jlowin/fastmcp/releases/tag/v2.4.0)
236
+
237
+ **Note**: this release includes a backwards-incompatible change to how resources are prefixed when mounted in composed servers. However, it is only backwards-incompatible if users were running tests or manually loading resources by prefixed key; LLMs should not have any issue discovering the new route. See [Resource Prefix Formats](https://gofastmcp.com/servers/composition#resource-prefix-formats) for more.
238
+
239
+ ### New Features 🎉
240
+
241
+ * Allow \* Methods and all routes as tools shortcuts by [@jlowin](https://github.com/jlowin) in [#520](https://github.com/jlowin/fastmcp/pull/520)
242
+ * Improved support for config dicts by [@jlowin](https://github.com/jlowin) in [#522](https://github.com/jlowin/fastmcp/pull/522)
243
+ * Support creating clients from MCP config dicts, including multi-server clients by [@jlowin](https://github.com/jlowin) in [#527](https://github.com/jlowin/fastmcp/pull/527)
244
+ * Make resource prefix format configurable by [@jlowin](https://github.com/jlowin) in [#534](https://github.com/jlowin/fastmcp/pull/534)
245
+
246
+ ### Fixes 🐞
247
+
248
+ * Avoid hanging on initializing server session by [@jlowin](https://github.com/jlowin) in [#523](https://github.com/jlowin/fastmcp/pull/523)
249
+
250
+ ### Breaking Changes 🛫
251
+
252
+ * Remove customizable separators; improve resource separator by [@jlowin](https://github.com/jlowin) in [#526](https://github.com/jlowin/fastmcp/pull/526)
253
+
254
+ ### Docs 📚
255
+
256
+ * Improve client documentation by [@jlowin](https://github.com/jlowin) in [#517](https://github.com/jlowin/fastmcp/pull/517)
257
+
258
+ ### Other Changes 🦾
259
+
260
+ * Ensure openapi path params are handled properly by [@jlowin](https://github.com/jlowin) in [#519](https://github.com/jlowin/fastmcp/pull/519)
261
+ * better error when missing lifespan by [@zzstoatzz](https://github.com/zzstoatzz) in [#521](https://github.com/jlowin/fastmcp/pull/521)
262
+
263
+ **Full Changelog**: [v2.3.5...v2.4.0](https://github.com/jlowin/fastmcp/compare/v2.3.5...v2.4.0)
264
+ </Update>
265
+
266
+ <Update label="v2.3.5" description="2024-05-20">
267
+
268
+ ## [v2.3.5: Making Progress](https://github.com/jlowin/fastmcp/releases/tag/v2.3.5)
269
+
270
+ ### New Features 🎉
271
+
272
+ * support messages in progress notifications by [@rickygenhealth](https://github.com/rickygenhealth) in [#471](https://github.com/jlowin/fastmcp/pull/471)
273
+ * feat: Add middleware option in server.run by [@Maxi91f](https://github.com/Maxi91f) in [#475](https://github.com/jlowin/fastmcp/pull/475)
274
+ * Add lifespan property to app by [@jlowin](https://github.com/jlowin) in [#483](https://github.com/jlowin/fastmcp/pull/483)
275
+ * Update `fastmcp run` to work with remote servers by [@jlowin](https://github.com/jlowin) in [#491](https://github.com/jlowin/fastmcp/pull/491)
276
+ * Add FastMCP.as\_proxy() by [@jlowin](https://github.com/jlowin) in [#490](https://github.com/jlowin/fastmcp/pull/490)
277
+ * Infer sse transport from urls containing /sse by [@jlowin](https://github.com/jlowin) in [#512](https://github.com/jlowin/fastmcp/pull/512)
278
+ * Add progress handler to client by [@jlowin](https://github.com/jlowin) in [#513](https://github.com/jlowin/fastmcp/pull/513)
279
+ * Store the initialize result on the client by [@jlowin](https://github.com/jlowin) in [#509](https://github.com/jlowin/fastmcp/pull/509)
280
+
281
+ ### Fixes 🐞
282
+
283
+ * Remove patch and use upstream SSEServerTransport by [@jlowin](https://github.com/jlowin) in [#425](https://github.com/jlowin/fastmcp/pull/425)
284
+
285
+ ### Docs 📚
286
+
287
+ * Update transport docs by [@jlowin](https://github.com/jlowin) in [#458](https://github.com/jlowin/fastmcp/pull/458)
288
+ * update proxy docs + example by [@zzstoatzz](https://github.com/zzstoatzz) in [#460](https://github.com/jlowin/fastmcp/pull/460)
289
+ * doc(asgi): Change custom route example to PlainTextResponse by [@mcw0933](https://github.com/mcw0933) in [#477](https://github.com/jlowin/fastmcp/pull/477)
290
+ * Store FastMCP instance on app.state.fastmcp\_server by [@jlowin](https://github.com/jlowin) in [#489](https://github.com/jlowin/fastmcp/pull/489)
291
+ * Improve AGENTS.md overview by [@jlowin](https://github.com/jlowin) in [#492](https://github.com/jlowin/fastmcp/pull/492)
292
+ * Update release numbers for anticipated version by [@jlowin](https://github.com/jlowin) in [#516](https://github.com/jlowin/fastmcp/pull/516)
293
+
294
+ ### Other Changes 🦾
295
+
296
+ * run tests on all PRs by [@jlowin](https://github.com/jlowin) in [#468](https://github.com/jlowin/fastmcp/pull/468)
297
+ * add null check by [@zzstoatzz](https://github.com/zzstoatzz) in [#473](https://github.com/jlowin/fastmcp/pull/473)
298
+ * strict typing for `server.py` by [@zzstoatzz](https://github.com/zzstoatzz) in [#476](https://github.com/jlowin/fastmcp/pull/476)
299
+ * Doc(quickstart): Fix import statements by [@mai-nakagawa](https://github.com/mai-nakagawa) in [#479](https://github.com/jlowin/fastmcp/pull/479)
300
+ * Add labeler by [@jlowin](https://github.com/jlowin) in [#484](https://github.com/jlowin/fastmcp/pull/484)
301
+ * Fix flaky timeout test by increasing timeout (#474) by [@davenpi](https://github.com/davenpi) in [#486](https://github.com/jlowin/fastmcp/pull/486)
302
+ * Skipping `test_permission_error` if runner is root. by [@ZiadAmerr](https://github.com/ZiadAmerr) in [#502](https://github.com/jlowin/fastmcp/pull/502)
303
+ * allow passing full uvicorn config by [@zzstoatzz](https://github.com/zzstoatzz) in [#504](https://github.com/jlowin/fastmcp/pull/504)
304
+ * Skip timeout tests on windows by [@jlowin](https://github.com/jlowin) in [#514](https://github.com/jlowin/fastmcp/pull/514)
305
+
306
+ ### New Contributors
307
+
308
+ * [@rickygenhealth](https://github.com/rickygenhealth) made their first contribution in [#471](https://github.com/jlowin/fastmcp/pull/471)
309
+ * [@Maxi91f](https://github.com/Maxi91f) made their first contribution in [#475](https://github.com/jlowin/fastmcp/pull/475)
310
+ * [@mcw0933](https://github.com/mcw0933) made their first contribution in [#477](https://github.com/jlowin/fastmcp/pull/477)
311
+ * [@mai-nakagawa](https://github.com/mai-nakagawa) made their first contribution in [#479](https://github.com/jlowin/fastmcp/pull/479)
312
+ * [@ZiadAmerr](https://github.com/ZiadAmerr) made their first contribution in [#502](https://github.com/jlowin/fastmcp/pull/502)
313
+
314
+ **Full Changelog**: [v2.3.4...v2.3.5](https://github.com/jlowin/fastmcp/compare/v2.3.4...v2.3.5)
315
+ </Update>
316
+
317
+ <Update label="v2.3.4" description="2024-05-15">
318
+
319
+ ## [v2.3.4: Error Today, Gone Tomorrow](https://github.com/jlowin/fastmcp/releases/tag/v2.3.4)
320
+
321
+ ### New Features 🎉
322
+
323
+ * logging stack trace for easier debugging by [@jbkoh](https://github.com/jbkoh) in [#413](https://github.com/jlowin/fastmcp/pull/413)
324
+ * add missing StreamableHttpTransport in client exports by [@yihuang](https://github.com/yihuang) in [#408](https://github.com/jlowin/fastmcp/pull/408)
325
+ * Improve error handling for tools and resources by [@jlowin](https://github.com/jlowin) in [#434](https://github.com/jlowin/fastmcp/pull/434)
326
+ * feat: add support for removing tools from server by [@davenpi](https://github.com/davenpi) in [#437](https://github.com/jlowin/fastmcp/pull/437)
327
+ * Prune titles from JSONSchemas by [@jlowin](https://github.com/jlowin) in [#449](https://github.com/jlowin/fastmcp/pull/449)
328
+ * Declare toolsChanged capability for stdio server. by [@davenpi](https://github.com/davenpi) in [#450](https://github.com/jlowin/fastmcp/pull/450)
329
+ * Improve handling of exceptiongroups when raised in clients by [@jlowin](https://github.com/jlowin) in [#452](https://github.com/jlowin/fastmcp/pull/452)
330
+ * Add timeout support to client by [@jlowin](https://github.com/jlowin) in [#455](https://github.com/jlowin/fastmcp/pull/455)
331
+
332
+ ### Fixes 🐞
333
+
334
+ * Pin to mcp 1.8.1 to resolve callback deadlocks with SHTTP by [@jlowin](https://github.com/jlowin) in [#427](https://github.com/jlowin/fastmcp/pull/427)
335
+ * Add reprs for OpenAPI objects by [@jlowin](https://github.com/jlowin) in [#447](https://github.com/jlowin/fastmcp/pull/447)
336
+ * Ensure openapi defs for structured objects are loaded properly by [@jlowin](https://github.com/jlowin) in [#448](https://github.com/jlowin/fastmcp/pull/448)
337
+ * Ensure tests run against correct python version by [@jlowin](https://github.com/jlowin) in [#454](https://github.com/jlowin/fastmcp/pull/454)
338
+ * Ensure result is only returned if a new key was found by [@jlowin](https://github.com/jlowin) in [#456](https://github.com/jlowin/fastmcp/pull/456)
339
+
340
+ ### Docs 📚
341
+
342
+ * Add documentation for tool removal by [@jlowin](https://github.com/jlowin) in [#440](https://github.com/jlowin/fastmcp/pull/440)
343
+
344
+ ### Other Changes 🦾
345
+
346
+ * Deprecate passing settings to the FastMCP instance by [@jlowin](https://github.com/jlowin) in [#424](https://github.com/jlowin/fastmcp/pull/424)
347
+ * Add path prefix to test by [@jlowin](https://github.com/jlowin) in [#432](https://github.com/jlowin/fastmcp/pull/432)
348
+
349
+ ### New Contributors
350
+
351
+ * [@jbkoh](https://github.com/jbkoh) made their first contribution in [#413](https://github.com/jlowin/fastmcp/pull/413)
352
+ * [@davenpi](https://github.com/davenpi) made their first contribution in [#437](https://github.com/jlowin/fastmcp/pull/437)
353
+
354
+ **Full Changelog**: [v2.3.3...v2.3.4](https://github.com/jlowin/fastmcp/compare/v2.3.3...v2.3.4)
355
+ </Update>
356
+
357
+ <Update label="v2.3.3" description="2024-05-10">
358
+
359
+ ## [v2.3.3: SSE you later](https://github.com/jlowin/fastmcp/releases/tag/v2.3.3)
360
+
361
+ This is a hotfix for a bug introduced in 2.3.2 that broke SSE servers
362
+
363
+ ### Fixes 🐞
364
+
365
+ * Fix bug that sets message path and sse path to same value by [@jlowin](https://github.com/jlowin) in [#405](https://github.com/jlowin/fastmcp/pull/405)
366
+
367
+ ### Docs 📚
368
+
369
+ * Update composition docs by [@jlowin](https://github.com/jlowin) in [#403](https://github.com/jlowin/fastmcp/pull/403)
370
+
371
+ ### Other Changes 🦾
372
+
373
+ * Add test for no prefix when importing by [@jlowin](https://github.com/jlowin) in [#404](https://github.com/jlowin/fastmcp/pull/404)
374
+
375
+ **Full Changelog**: [v2.3.2...v2.3.3](https://github.com/jlowin/fastmcp/compare/v2.3.2...v2.3.3)
376
+ </Update>
377
+
378
+ <Update label="v2.3.2" description="2024-05-10">
379
+
380
+ ## [v2.3.2: Stuck in the Middleware With You](https://github.com/jlowin/fastmcp/releases/tag/v2.3.2)
381
+
382
+ ### New Features 🎉
383
+
384
+ * Allow users to pass middleware to starlette app constructors by [@jlowin](https://github.com/jlowin) in [#398](https://github.com/jlowin/fastmcp/pull/398)
385
+ * Deprecate transport-specific methods on FastMCP server by [@jlowin](https://github.com/jlowin) in [#401](https://github.com/jlowin/fastmcp/pull/401)
386
+
387
+ ### Docs 📚
388
+
389
+ * Update CLI docs by [@jlowin](https://github.com/jlowin) in [#402](https://github.com/jlowin/fastmcp/pull/402)
390
+
391
+ ### Other Changes 🦾
392
+
393
+ * Adding 23 tests for CLI by [@didier-durand](https://github.com/didier-durand) in [#394](https://github.com/jlowin/fastmcp/pull/394)
394
+
395
+ **Full Changelog**: [v2.3.1...v2.3.2](https://github.com/jlowin/fastmcp/compare/v2.3.1...v2.3.2)
396
+ </Update>
397
+
398
+ <Update label="v2.3.1" description="2024-05-09">
399
+
400
+ ## [v2.3.1: For Good-nests Sake](https://github.com/jlowin/fastmcp/releases/tag/v2.3.1)
401
+
402
+ This release primarily patches a long-standing bug with nested ASGI SSE servers.
403
+
404
+ ### Fixes 🐞
405
+
406
+ * Fix tool result serialization when the tool returns a list by [@strawgate](https://github.com/strawgate) in [#379](https://github.com/jlowin/fastmcp/pull/379)
407
+ * Ensure FastMCP handles nested SSE and SHTTP apps properly in ASGI frameworks by [@jlowin](https://github.com/jlowin) in [#390](https://github.com/jlowin/fastmcp/pull/390)
408
+
409
+ ### Docs 📚
410
+
411
+ * Update transport docs by [@jlowin](https://github.com/jlowin) in [#377](https://github.com/jlowin/fastmcp/pull/377)
412
+ * Add llms.txt to docs by [@jlowin](https://github.com/jlowin) in [#384](https://github.com/jlowin/fastmcp/pull/384)
413
+ * Fixing various text typos by [@didier-durand](https://github.com/didier-durand) in [#385](https://github.com/jlowin/fastmcp/pull/385)
414
+
415
+ ### Other Changes 🦾
416
+
417
+ * Adding a few tests to Image type by [@didier-durand](https://github.com/didier-durand) in [#387](https://github.com/jlowin/fastmcp/pull/387)
418
+ * Adding tests for TimedCache by [@didier-durand](https://github.com/didier-durand) in [#388](https://github.com/jlowin/fastmcp/pull/388)
419
+
420
+ ### New Contributors
421
+
422
+ * [@didier-durand](https://github.com/didier-durand) made their first contribution in [#385](https://github.com/jlowin/fastmcp/pull/385)
423
+
424
+ **Full Changelog**: [v2.3.0...v2.3.1](https://github.com/jlowin/fastmcp/compare/v2.3.0...v2.3.1)
425
+ </Update>
426
+
427
+ <Update label="v2.3.0" description="2024-05-08">
428
+
429
+ ## [v2.3.0: Stream Me Up, Scotty](https://github.com/jlowin/fastmcp/releases/tag/v2.3.0)
430
+
431
+ ### New Features 🎉
432
+
433
+ * Add streaming support for HTTP transport by [@jlowin](https://github.com/jlowin) in [#365](https://github.com/jlowin/fastmcp/pull/365)
434
+ * Support streaming HTTP transport in clients by [@jlowin](https://github.com/jlowin) in [#366](https://github.com/jlowin/fastmcp/pull/366)
435
+ * Add streaming support to CLI by [@jlowin](https://github.com/jlowin) in [#367](https://github.com/jlowin/fastmcp/pull/367)
436
+
437
+ ### Fixes 🐞
438
+
439
+ * Fix streaming transport initialization by [@jlowin](https://github.com/jlowin) in [#368](https://github.com/jlowin/fastmcp/pull/368)
440
+
441
+ ### Docs 📚
442
+
443
+ * Update transport documentation for streaming support by [@jlowin](https://github.com/jlowin) in [#369](https://github.com/jlowin/fastmcp/pull/369)
444
+
445
+ **Full Changelog**: [v2.2.10...v2.3.0](https://github.com/jlowin/fastmcp/compare/v2.2.10...v2.3.0)
446
+ </Update>
447
+
448
+ <Update label="v2.2.10" description="2024-05-06">
449
+
450
+ ## [v2.2.10: That's JSON Bourne](https://github.com/jlowin/fastmcp/releases/tag/v2.2.10)
451
+
452
+ ### Fixes 🐞
453
+
454
+ * Disable automatic JSON parsing of tool args by [@jlowin](https://github.com/jlowin) in [#341](https://github.com/jlowin/fastmcp/pull/341)
455
+ * Fix prompt test by [@jlowin](https://github.com/jlowin) in [#342](https://github.com/jlowin/fastmcp/pull/342)
456
+
457
+ ### Other Changes 🦾
458
+
459
+ * Update docs.json by [@jlowin](https://github.com/jlowin) in [#338](https://github.com/jlowin/fastmcp/pull/338)
460
+ * Add test coverage + tests on 4 examples by [@alainivars](https://github.com/alainivars) in [#306](https://github.com/jlowin/fastmcp/pull/306)
461
+
462
+ ### New Contributors
463
+
464
+ * [@alainivars](https://github.com/alainivars) made their first contribution in [#306](https://github.com/jlowin/fastmcp/pull/306)
465
+
466
+ **Full Changelog**: [v2.2.9...v2.2.10](https://github.com/jlowin/fastmcp/compare/v2.2.9...v2.2.10)
467
+ </Update>
468
+
469
+ <Update label="v2.2.9" description="2024-05-06">
470
+
471
+ ## [v2.2.9: Str-ing the Pot (Hotfix)](https://github.com/jlowin/fastmcp/releases/tag/v2.2.9)
472
+
473
+ This release is a hotfix for the issue detailed in #330
474
+
475
+ ### Fixes 🐞
476
+
477
+ * Prevent invalid resource URIs by [@jlowin](https://github.com/jlowin) in [#336](https://github.com/jlowin/fastmcp/pull/336)
478
+ * Coerce numbers to str by [@jlowin](https://github.com/jlowin) in [#337](https://github.com/jlowin/fastmcp/pull/337)
479
+
480
+ ### Docs 📚
481
+
482
+ * Add client badge by [@jlowin](https://github.com/jlowin) in [#327](https://github.com/jlowin/fastmcp/pull/327)
483
+ * Update bug.yml by [@jlowin](https://github.com/jlowin) in [#328](https://github.com/jlowin/fastmcp/pull/328)
484
+
485
+ ### Other Changes 🦾
486
+
487
+ * Update quickstart.mdx example to include import by [@discdiver](https://github.com/discdiver) in [#329](https://github.com/jlowin/fastmcp/pull/329)
488
+
489
+ ### New Contributors
490
+
491
+ * [@discdiver](https://github.com/discdiver) made their first contribution in [#329](https://github.com/jlowin/fastmcp/pull/329)
492
+
493
+ **Full Changelog**: [v2.2.8...v2.2.9](https://github.com/jlowin/fastmcp/compare/v2.2.8...v2.2.9)
494
+ </Update>
495
+
496
+ <Update label="v2.2.8" description="2024-05-05">
497
+
498
+ ## [v2.2.8: Parse and Recreation](https://github.com/jlowin/fastmcp/releases/tag/v2.2.8)
499
+
500
+ ### New Features 🎉
501
+
502
+ * Replace custom parsing with TypeAdapter by [@jlowin](https://github.com/jlowin) in [#314](https://github.com/jlowin/fastmcp/pull/314)
503
+ * Handle \*args/\*\*kwargs appropriately for various components by [@jlowin](https://github.com/jlowin) in [#317](https://github.com/jlowin/fastmcp/pull/317)
504
+ * Add timeout-graceful-shutdown as a default config for SSE app by [@jlowin](https://github.com/jlowin) in [#323](https://github.com/jlowin/fastmcp/pull/323)
505
+ * Ensure prompts return descriptions by [@jlowin](https://github.com/jlowin) in [#325](https://github.com/jlowin/fastmcp/pull/325)
506
+
507
+ ### Fixes 🐞
508
+
509
+ * Ensure that tool serialization has a graceful fallback by [@jlowin](https://github.com/jlowin) in [#310](https://github.com/jlowin/fastmcp/pull/310)
510
+
511
+ ### Docs 📚
512
+
513
+ * Update docs for clarity by [@jlowin](https://github.com/jlowin) in [#312](https://github.com/jlowin/fastmcp/pull/312)
514
+
515
+ ### Other Changes 🦾
516
+
517
+ * Remove is\_async attribute by [@jlowin](https://github.com/jlowin) in [#315](https://github.com/jlowin/fastmcp/pull/315)
518
+ * Dry out retrieving context kwarg by [@jlowin](https://github.com/jlowin) in [#316](https://github.com/jlowin/fastmcp/pull/316)
519
+
520
+ **Full Changelog**: [v2.2.7...v2.2.8](https://github.com/jlowin/fastmcp/compare/v2.2.7...v2.2.8)
521
+ </Update>
522
+
523
+ <Update label="v2.2.7" description="2024-05-03">
524
+
525
+ ## [v2.2.7: You Auth to Know Better](https://github.com/jlowin/fastmcp/releases/tag/v2.2.7)
526
+
527
+ ### New Features 🎉
528
+
529
+ * use pydantic\_core.to\_json by [@jlowin](https://github.com/jlowin) in [#290](https://github.com/jlowin/fastmcp/pull/290)
530
+ * Ensure openapi descriptions are included in tool details by [@jlowin](https://github.com/jlowin) in [#293](https://github.com/jlowin/fastmcp/pull/293)
531
+ * Bump mcp to 1.7.1 by [@jlowin](https://github.com/jlowin) in [#298](https://github.com/jlowin/fastmcp/pull/298)
532
+ * Add support for tool annotations by [@jlowin](https://github.com/jlowin) in [#299](https://github.com/jlowin/fastmcp/pull/299)
533
+ * Add auth support by [@jlowin](https://github.com/jlowin) in [#300](https://github.com/jlowin/fastmcp/pull/300)
534
+ * Add low-level methods to client by [@jlowin](https://github.com/jlowin) in [#301](https://github.com/jlowin/fastmcp/pull/301)
535
+ * Add method for retrieving current starlette request to FastMCP context by [@jlowin](https://github.com/jlowin) in [#302](https://github.com/jlowin/fastmcp/pull/302)
536
+ * get\_starlette\_request → get\_http\_request by [@jlowin](https://github.com/jlowin) in [#303](https://github.com/jlowin/fastmcp/pull/303)
537
+ * Support custom Serializer for Tools by [@strawgate](https://github.com/strawgate) in [#308](https://github.com/jlowin/fastmcp/pull/308)
538
+ * Support proxy mount by [@jlowin](https://github.com/jlowin) in [#309](https://github.com/jlowin/fastmcp/pull/309)
539
+
540
+ ### Other Changes 🦾
541
+
542
+ * Improve context injection type checks by [@jlowin](https://github.com/jlowin) in [#291](https://github.com/jlowin/fastmcp/pull/291)
543
+ * add readme to smarthome example by [@zzstoatzz](https://github.com/zzstoatzz) in [#294](https://github.com/jlowin/fastmcp/pull/294)
544
+
545
+ **Full Changelog**: [v2.2.6...v2.2.7](https://github.com/jlowin/fastmcp/compare/v2.2.6...v2.2.7)
546
+ </Update>
547
+
548
+ <Update label="v2.2.6" description="2024-04-30">
549
+
550
+ ## [v2.2.6: The REST is History](https://github.com/jlowin/fastmcp/releases/tag/v2.2.6)
551
+
552
+ ### New Features 🎉
553
+
554
+ * Added feature : Load MCP server using config by [@sandipan1](https://github.com/sandipan1) in [#260](https://github.com/jlowin/fastmcp/pull/260)
555
+ * small typing fixes by [@zzstoatzz](https://github.com/zzstoatzz) in [#237](https://github.com/jlowin/fastmcp/pull/237)
556
+ * Expose configurable timeout for OpenAPI by [@jlowin](https://github.com/jlowin) in [#279](https://github.com/jlowin/fastmcp/pull/279)
557
+ * Lower websockets pin for compatibility by [@jlowin](https://github.com/jlowin) in [#286](https://github.com/jlowin/fastmcp/pull/286)
558
+ * Improve OpenAPI param handling by [@jlowin](https://github.com/jlowin) in [#287](https://github.com/jlowin/fastmcp/pull/287)
559
+
560
+ ### Fixes 🐞
561
+
562
+ * Ensure openapi tool responses are properly converted by [@jlowin](https://github.com/jlowin) in [#283](https://github.com/jlowin/fastmcp/pull/283)
563
+ * Fix OpenAPI examples by [@jlowin](https://github.com/jlowin) in [#285](https://github.com/jlowin/fastmcp/pull/285)
564
+ * Fix client docs for advanced features, add tests for logging by [@jlowin](https://github.com/jlowin) in [#284](https://github.com/jlowin/fastmcp/pull/284)
565
+
566
+ ### Other Changes 🦾
567
+
568
+ * add testing doc by [@jlowin](https://github.com/jlowin) in [#264](https://github.com/jlowin/fastmcp/pull/264)
569
+ * #267 Fix openapi template resource to support multiple path parameters by [@jeger-at](https://github.com/jeger-at) in [#278](https://github.com/jlowin/fastmcp/pull/278)
570
+
571
+ ### New Contributors
572
+
573
+ * [@sandipan1](https://github.com/sandipan1) made their first contribution in [#260](https://github.com/jlowin/fastmcp/pull/260)
574
+ * [@jeger-at](https://github.com/jeger-at) made their first contribution in [#278](https://github.com/jlowin/fastmcp/pull/278)
575
+
576
+ **Full Changelog**: [v2.2.5...v2.2.6](https://github.com/jlowin/fastmcp/compare/v2.2.5...v2.2.6)
577
+ </Update>
578
+
579
+ <Update label="v2.2.5" description="2024-04-26">
580
+
581
+ ## [v2.2.5: Context Switching](https://github.com/jlowin/fastmcp/releases/tag/v2.2.5)
582
+
583
+ ### New Features 🎉
584
+
585
+ * Add tests for tool return types; improve serialization behavior by [@jlowin](https://github.com/jlowin) in [#262](https://github.com/jlowin/fastmcp/pull/262)
586
+ * Support context injection in resources, templates, and prompts (like tools) by [@jlowin](https://github.com/jlowin) in [#263](https://github.com/jlowin/fastmcp/pull/263)
587
+
588
+ ### Docs 📚
589
+
590
+ * Update wildcards to 2.2.4 by [@jlowin](https://github.com/jlowin) in [#257](https://github.com/jlowin/fastmcp/pull/257)
591
+ * Update note in templates docs by [@jlowin](https://github.com/jlowin) in [#258](https://github.com/jlowin/fastmcp/pull/258)
592
+ * Significant documentation and test expansion for tool input types by [@jlowin](https://github.com/jlowin) in [#261](https://github.com/jlowin/fastmcp/pull/261)
593
+
594
+ **Full Changelog**: [v2.2.4...v2.2.5](https://github.com/jlowin/fastmcp/compare/v2.2.4...v2.2.5)
595
+ </Update>
596
+
597
+ <Update label="v2.2.4" description="2024-04-25">
598
+
599
+ ## [v2.2.4: The Wild Side, Actually](https://github.com/jlowin/fastmcp/releases/tag/v2.2.4)
600
+
601
+ The wildcard URI templates exposed in v2.2.3 were blocked by a server-level check which is removed in this release.
602
+
603
+ ### New Features 🎉
604
+
605
+ * Allow customization of inspector proxy port, ui port, and version by [@jlowin](https://github.com/jlowin) in [#253](https://github.com/jlowin/fastmcp/pull/253)
606
+
607
+ ### Fixes 🐞
608
+
609
+ * fix: unintended type convert by [@cutekibry](https://github.com/cutekibry) in [#252](https://github.com/jlowin/fastmcp/pull/252)
610
+ * Ensure openapi resources return valid responses by [@jlowin](https://github.com/jlowin) in [#254](https://github.com/jlowin/fastmcp/pull/254)
611
+ * Ensure servers expose template wildcards by [@jlowin](https://github.com/jlowin) in [#256](https://github.com/jlowin/fastmcp/pull/256)
612
+
613
+ ### Docs 📚
614
+
615
+ * Update README.md Grammar error by [@TechWithTy](https://github.com/TechWithTy) in [#249](https://github.com/jlowin/fastmcp/pull/249)
616
+
617
+ ### Other Changes 🦾
618
+
619
+ * Add resource template tests by [@jlowin](https://github.com/jlowin) in [#255](https://github.com/jlowin/fastmcp/pull/255)
620
+
621
+ ### New Contributors
622
+
623
+ * [@TechWithTy](https://github.com/TechWithTy) made their first contribution in [#249](https://github.com/jlowin/fastmcp/pull/249)
624
+ * [@cutekibry](https://github.com/cutekibry) made their first contribution in [#252](https://github.com/jlowin/fastmcp/pull/252)
625
+
626
+ **Full Changelog**: [v2.2.3...v2.2.4](https://github.com/jlowin/fastmcp/compare/v2.2.3...v2.2.4)
627
+ </Update>
628
+
629
+ <Update label="v2.2.3" description="2024-04-25">
630
+
631
+ ## [v2.2.3: The Wild Side](https://github.com/jlowin/fastmcp/releases/tag/v2.2.3)
632
+
633
+ ### New Features 🎉
634
+
635
+ * Add wildcard params for resource templates by [@jlowin](https://github.com/jlowin) in [#246](https://github.com/jlowin/fastmcp/pull/246)
636
+
637
+ ### Docs 📚
638
+
639
+ * Indicate that Image class is for returns by [@jlowin](https://github.com/jlowin) in [#242](https://github.com/jlowin/fastmcp/pull/242)
640
+ * Update mermaid diagram by [@jlowin](https://github.com/jlowin) in [#243](https://github.com/jlowin/fastmcp/pull/243)
641
+
642
+ ### Other Changes 🦾
643
+
644
+ * update version badges by [@jlowin](https://github.com/jlowin) in [#248](https://github.com/jlowin/fastmcp/pull/248)
645
+
646
+ **Full Changelog**: [v2.2.2...v2.2.3](https://github.com/jlowin/fastmcp/compare/v2.2.2...v2.2.3)
647
+ </Update>
648
+
649
+ <Update label="v2.2.2" description="2024-04-24">
650
+
651
+ ## [v2.2.2: Prompt and Circumstance](https://github.com/jlowin/fastmcp/releases/tag/v2.2.2)
652
+
653
+ ### New Features 🎉
654
+
655
+ * Add prompt support by [@jlowin](https://github.com/jlowin) in [#235](https://github.com/jlowin/fastmcp/pull/235)
656
+
657
+ ### Fixes 🐞
658
+
659
+ * Ensure that resource templates are properly exposed by [@jlowin](https://github.com/jlowin) in [#238](https://github.com/jlowin/fastmcp/pull/238)
660
+
661
+ ### Docs 📚
662
+
663
+ * Update docs for prompts by [@jlowin](https://github.com/jlowin) in [#236](https://github.com/jlowin/fastmcp/pull/236)
664
+
665
+ ### Other Changes 🦾
666
+
667
+ * Add prompt tests by [@jlowin](https://github.com/jlowin) in [#239](https://github.com/jlowin/fastmcp/pull/239)
668
+
669
+ **Full Changelog**: [v2.2.1...v2.2.2](https://github.com/jlowin/fastmcp/compare/v2.2.1...v2.2.2)
670
+ </Update>
671
+
672
+ <Update label="v2.2.1" description="2024-04-23">
673
+
674
+ ## [v2.2.1: Template for Success](https://github.com/jlowin/fastmcp/releases/tag/v2.2.1)
675
+
676
+ ### New Features 🎉
677
+
678
+ * Add resource templates by [@jlowin](https://github.com/jlowin) in [#230](https://github.com/jlowin/fastmcp/pull/230)
679
+
680
+ ### Fixes 🐞
681
+
682
+ * Ensure that resource templates are properly exposed by [@jlowin](https://github.com/jlowin) in [#231](https://github.com/jlowin/fastmcp/pull/231)
683
+
684
+ ### Docs 📚
685
+
686
+ * Update docs for resource templates by [@jlowin](https://github.com/jlowin) in [#232](https://github.com/jlowin/fastmcp/pull/232)
687
+
688
+ ### Other Changes 🦾
689
+
690
+ * Add resource template tests by [@jlowin](https://github.com/jlowin) in [#233](https://github.com/jlowin/fastmcp/pull/233)
691
+
692
+ **Full Changelog**: [v2.2.0...v2.2.1](https://github.com/jlowin/fastmcp/compare/v2.2.0...v2.2.1)
693
+ </Update>
694
+
695
+ <Update label="v2.2.0" description="2024-04-22">
696
+
697
+ ## [v2.2.0: Compose Yourself](https://github.com/jlowin/fastmcp/releases/tag/v2.2.0)
698
+
699
+ ### New Features 🎉
700
+
701
+ * Add support for mounting FastMCP servers by [@jlowin](https://github.com/jlowin) in [#175](https://github.com/jlowin/fastmcp/pull/175)
702
+ * Add support for duplicate behavior == ignore by [@jlowin](https://github.com/jlowin) in [#169](https://github.com/jlowin/fastmcp/pull/169)
703
+
704
+ ### Breaking Changes 🛫
705
+
706
+ * Refactor MCP composition by [@jlowin](https://github.com/jlowin) in [#176](https://github.com/jlowin/fastmcp/pull/176)
707
+
708
+ ### Docs 📚
709
+
710
+ * Improve integration documentation by [@jlowin](https://github.com/jlowin) in [#184](https://github.com/jlowin/fastmcp/pull/184)
711
+ * Improve documentation by [@jlowin](https://github.com/jlowin) in [#185](https://github.com/jlowin/fastmcp/pull/185)
712
+
713
+ ### Other Changes 🦾
714
+
715
+ * Add transport kwargs for mcp.run() and fastmcp run by [@jlowin](https://github.com/jlowin) in [#161](https://github.com/jlowin/fastmcp/pull/161)
716
+ * Allow resource templates to have optional / excluded arguments by [@jlowin](https://github.com/jlowin) in [#164](https://github.com/jlowin/fastmcp/pull/164)
717
+ * Update resources.mdx by [@jlowin](https://github.com/jlowin) in [#165](https://github.com/jlowin/fastmcp/pull/165)
718
+
719
+ ### New Contributors
720
+
721
+ * [@kongqi404](https://github.com/kongqi404) made their first contribution in [#181](https://github.com/jlowin/fastmcp/pull/181)
722
+
723
+ **Full Changelog**: [v2.1.2...v2.2.0](https://github.com/jlowin/fastmcp/compare/v2.1.2...v2.2.0)
724
+ </Update>
725
+
726
+ <Update label="v2.1.2" description="2024-04-14">
727
+
728
+ ## [v2.1.2: Copy That, Good Buddy](https://github.com/jlowin/fastmcp/releases/tag/v2.1.2)
729
+
730
+ The main improvement in this release is a fix that allows FastAPI / OpenAPI-generated servers to be mounted as sub-servers.
731
+
732
+ ### Fixes 🐞
733
+
734
+ * Ensure objects are copied properly and test mounting fastapi by [@jlowin](https://github.com/jlowin) in [#153](https://github.com/jlowin/fastmcp/pull/153)
735
+
736
+ ### Docs 📚
737
+
738
+ * Fix broken links in docs by [@jlowin](https://github.com/jlowin) in [#154](https://github.com/jlowin/fastmcp/pull/154)
739
+
740
+ ### Other Changes 🦾
741
+
742
+ * Update README.md by [@jlowin](https://github.com/jlowin) in [#149](https://github.com/jlowin/fastmcp/pull/149)
743
+ * Only apply log config to FastMCP loggers by [@jlowin](https://github.com/jlowin) in [#155](https://github.com/jlowin/fastmcp/pull/155)
744
+ * Update pyproject.toml by [@jlowin](https://github.com/jlowin) in [#156](https://github.com/jlowin/fastmcp/pull/156)
745
+
746
+ **Full Changelog**: [v2.1.1...v2.1.2](https://github.com/jlowin/fastmcp/compare/v2.1.1...v2.1.2)
747
+ </Update>
748
+
749
+ <Update label="v2.1.1" description="2024-04-14">
750
+
751
+ ## [v2.1.1: Doc Holiday](https://github.com/jlowin/fastmcp/releases/tag/v2.1.1)
752
+
753
+ FastMCP's docs are now available at gofastmcp.com.
754
+
755
+ ### Docs 📚
756
+
757
+ * Add docs by [@jlowin](https://github.com/jlowin) in [#136](https://github.com/jlowin/fastmcp/pull/136)
758
+ * Add docs link to readme by [@jlowin](https://github.com/jlowin) in [#137](https://github.com/jlowin/fastmcp/pull/137)
759
+ * Minor docs updates by [@jlowin](https://github.com/jlowin) in [#138](https://github.com/jlowin/fastmcp/pull/138)
760
+
761
+ ### Fixes 🐞
762
+
763
+ * fix branch name in example by [@zzstoatzz](https://github.com/zzstoatzz) in [#140](https://github.com/jlowin/fastmcp/pull/140)
764
+
765
+ ### Other Changes 🦾
766
+
767
+ * smart home example by [@zzstoatzz](https://github.com/zzstoatzz) in [#115](https://github.com/jlowin/fastmcp/pull/115)
768
+ * Remove mac os tests by [@jlowin](https://github.com/jlowin) in [#142](https://github.com/jlowin/fastmcp/pull/142)
769
+ * Expand support for various method interactions by [@jlowin](https://github.com/jlowin) in [#143](https://github.com/jlowin/fastmcp/pull/143)
770
+ * Update docs and add\_resource\_fn by [@jlowin](https://github.com/jlowin) in [#144](https://github.com/jlowin/fastmcp/pull/144)
771
+ * Update description by [@jlowin](https://github.com/jlowin) in [#145](https://github.com/jlowin/fastmcp/pull/145)
772
+ * Support openapi 3.0 and 3.1 by [@jlowin](https://github.com/jlowin) in [#147](https://github.com/jlowin/fastmcp/pull/147)
773
+
774
+ **Full Changelog**: [v2.1.0...v2.1.1](https://github.com/jlowin/fastmcp/compare/v2.1.0...v2.1.1)
775
+ </Update>
776
+
777
+ <Update label="v2.1.0" description="2024-04-13">
778
+
779
+ ## [v2.1.0: Tag, You're It](https://github.com/jlowin/fastmcp/releases/tag/v2.1.0)
780
+
781
+ The primary motivation for this release is the fix in #128 for Claude desktop compatibility, but the primary new feature of this release is per-object tags. Currently these are for bookkeeping only but will become useful in future releases.
782
+
783
+ ### New Features 🎉
784
+
785
+ * Add tags for all core MCP objects by [@jlowin](https://github.com/jlowin) in [#121](https://github.com/jlowin/fastmcp/pull/121)
786
+ * Ensure that openapi tags are transferred to MCP objects by [@jlowin](https://github.com/jlowin) in [#124](https://github.com/jlowin/fastmcp/pull/124)
787
+
788
+ ### Fixes 🐞
789
+
790
+ * Change default mounted tool separator from / to \_ by [@jlowin](https://github.com/jlowin) in [#128](https://github.com/jlowin/fastmcp/pull/128)
791
+ * Enter mounted app lifespans by [@jlowin](https://github.com/jlowin) in [#129](https://github.com/jlowin/fastmcp/pull/129)
792
+ * Fix CLI that called mcp instead of fastmcp by [@jlowin](https://github.com/jlowin) in [#128](https://github.com/jlowin/fastmcp/pull/128)
793
+
794
+ ### Breaking Changes 🛫
795
+
796
+ * Changed configuration for duplicate resources/tools/prompts by [@jlowin](https://github.com/jlowin) in [#121](https://github.com/jlowin/fastmcp/pull/121)
797
+ * Improve client return types by [@jlowin](https://github.com/jlowin) in [#123](https://github.com/jlowin/fastmcp/pull/123)
798
+
799
+ ### Other Changes 🦾
800
+
801
+ * Add tests for tags in server decorators by [@jlowin](https://github.com/jlowin) in [#122](https://github.com/jlowin/fastmcp/pull/122)
802
+ * Clean up server tests by [@jlowin](https://github.com/jlowin) in [#125](https://github.com/jlowin/fastmcp/pull/125)
803
+
804
+ **Full Changelog**: [v2.0.0...v2.1.0](https://github.com/jlowin/fastmcp/compare/v2.0.0...v2.1.0)
805
+ </Update>
806
+
807
+ <Update label="v2.0.0" description="2024-04-11">
808
+
809
+ ## [v2.0.0: Second to None](https://github.com/jlowin/fastmcp/releases/tag/v2.0.0)
810
+
811
+ ### New Features 🎉
812
+
813
+ * Support mounting FastMCP instances as sub-MCPs by [@jlowin](https://github.com/jlowin) in [#99](https://github.com/jlowin/fastmcp/pull/99)
814
+ * Add in-memory client for calling FastMCP servers (and tests) by [@jlowin](https://github.com/jlowin) in [#100](https://github.com/jlowin/fastmcp/pull/100)
815
+ * Add MCP proxy server by [@jlowin](https://github.com/jlowin) in [#105](https://github.com/jlowin/fastmcp/pull/105)
816
+ * Update FastMCP for upstream changes by [@jlowin](https://github.com/jlowin) in [#107](https://github.com/jlowin/fastmcp/pull/107)
817
+ * Generate FastMCP servers from OpenAPI specs and FastAPI by [@jlowin](https://github.com/jlowin) in [#110](https://github.com/jlowin/fastmcp/pull/110)
818
+ * Reorganize all client / transports by [@jlowin](https://github.com/jlowin) in [#111](https://github.com/jlowin/fastmcp/pull/111)
819
+ * Add sampling and roots by [@jlowin](https://github.com/jlowin) in [#117](https://github.com/jlowin/fastmcp/pull/117)
820
+
821
+ ### Fixes 🐞
822
+
823
+ * Fix bug with tools that return lists by [@jlowin](https://github.com/jlowin) in [#116](https://github.com/jlowin/fastmcp/pull/116)
824
+
825
+ ### Other Changes 🦾
826
+
827
+ * Add back FastMCP CLI by [@jlowin](https://github.com/jlowin) in [#108](https://github.com/jlowin/fastmcp/pull/108)
828
+ * Update Readme for v2 by [@jlowin](https://github.com/jlowin) in [#112](https://github.com/jlowin/fastmcp/pull/112)
829
+ * fix deprecation warnings by [@zzstoatzz](https://github.com/zzstoatzz) in [#113](https://github.com/jlowin/fastmcp/pull/113)
830
+ * Readme by [@jlowin](https://github.com/jlowin) in [#118](https://github.com/jlowin/fastmcp/pull/118)
831
+ * FastMCP 2.0 by [@jlowin](https://github.com/jlowin) in [#119](https://github.com/jlowin/fastmcp/pull/119)
832
+
833
+ **Full Changelog**: [v1.0...v2.0.0](https://github.com/jlowin/fastmcp/compare/v1.0...v2.0.0)
834
+ </Update>
835
+
836
+ <Update label="v1.0" description="2024-04-11">
837
+
838
+ ## [v1.0: It's Official](https://github.com/jlowin/fastmcp/releases/tag/v1.0)
839
+
840
+ This release commemorates FastMCP 1.0, which is included in the official Model Context Protocol SDK:
841
+
842
+ ```python
843
+ from mcp.server.fastmcp import FastMCP
844
+ ```
845
+
846
+ To the best of my knowledge, v1 is identical to the upstream version included with `mcp`.
847
+
848
+ ### Docs 📚
849
+
850
+ * Update readme to redirect to the official SDK by [@jlowin](https://github.com/jlowin) in [#79](https://github.com/jlowin/fastmcp/pull/79)
851
+
852
+ ### Other Changes 🦾
853
+
854
+ * fix: use Mount instead of Route for SSE message handling by [@samihamine](https://github.com/samihamine) in [#77](https://github.com/jlowin/fastmcp/pull/77)
855
+
856
+ ### New Contributors
857
+
858
+ * [@samihamine](https://github.com/samihamine) made their first contribution in [#77](https://github.com/jlowin/fastmcp/pull/77)
859
+
860
+ **Full Changelog**: [v0.4.1...v1.0](https://github.com/jlowin/fastmcp/compare/v0.4.1...v1.0)
861
+ </Update>
862
+
863
+ <Update label="v0.4.1" description="2024-12-09">
864
+
865
+ ## [v0.4.1: String Theory](https://github.com/jlowin/fastmcp/releases/tag/v0.4.1)
866
+
867
+ ### Fixes 🐞
868
+
869
+ * fix: handle strings containing numbers correctly by [@sd2k](https://github.com/sd2k) in [#63](https://github.com/jlowin/fastmcp/pull/63)
870
+
871
+ ### Docs 📚
872
+
873
+ * patch: Update pyproject.toml license by [@leonkozlowski](https://github.com/leonkozlowski) in [#67](https://github.com/jlowin/fastmcp/pull/67)
874
+
875
+ ### Other Changes 🦾
876
+
877
+ * Avoid new try\_eval\_type unavailable with older pydantic by [@jurasofish](https://github.com/jurasofish) in [#57](https://github.com/jlowin/fastmcp/pull/57)
878
+ * Decorator typing by [@jurasofish](https://github.com/jurasofish) in [#56](https://github.com/jlowin/fastmcp/pull/56)
879
+
880
+ ### New Contributors
881
+
882
+ * [@leonkozlowski](https://github.com/leonkozlowski) made their first contribution in [#67](https://github.com/jlowin/fastmcp/pull/67)
883
+
884
+ **Full Changelog**: [v0.4.0...v0.4.1](https://github.com/jlowin/fastmcp/compare/v0.4.0...v0.4.1)
885
+ </Update>
886
+
887
+ <Update label="v0.4.0" description="2024-12-05">
888
+
889
+ ## [v0.4.0: Nice to MIT You](https://github.com/jlowin/fastmcp/releases/tag/v0.4.0)
890
+
891
+ This is a relatively small release in terms of features, but the version is bumped to 0.4 to reflect that the code is being relicensed from Apache 2.0 to MIT. This is to facilitate FastMCP's inclusion in the official MCP SDK.
892
+
893
+ ### New Features 🎉
894
+
895
+ * Add pyright + tests by [@jlowin](https://github.com/jlowin) in [#52](https://github.com/jlowin/fastmcp/pull/52)
896
+ * add pgvector memory example by [@zzstoatzz](https://github.com/zzstoatzz) in [#49](https://github.com/jlowin/fastmcp/pull/49)
897
+
898
+ ### Fixes 🐞
899
+
900
+ * fix: use stderr for logging by [@sd2k](https://github.com/sd2k) in [#51](https://github.com/jlowin/fastmcp/pull/51)
901
+
902
+ ### Docs 📚
903
+
904
+ * Update ai-labeler.yml by [@jlowin](https://github.com/jlowin) in [#48](https://github.com/jlowin/fastmcp/pull/48)
905
+ * Relicense from Apache 2.0 to MIT by [@jlowin](https://github.com/jlowin) in [#54](https://github.com/jlowin/fastmcp/pull/54)
906
+
907
+ ### Other Changes 🦾
908
+
909
+ * fix warning and flake by [@zzstoatzz](https://github.com/zzstoatzz) in [#47](https://github.com/jlowin/fastmcp/pull/47)
910
+
911
+ ### New Contributors
912
+
913
+ * [@sd2k](https://github.com/sd2k) made their first contribution in [#51](https://github.com/jlowin/fastmcp/pull/51)
914
+
915
+ **Full Changelog**: [v0.3.5...v0.4.0](https://github.com/jlowin/fastmcp/compare/v0.3.5...v0.4.0)
916
+ </Update>
917
+
918
+ <Update label="v0.3.5" description="2024-12-03">
919
+
920
+ ## [v0.3.5: Windows of Opportunity](https://github.com/jlowin/fastmcp/releases/tag/v0.3.5)
921
+
922
+ This release is highlighted by the ability to handle complex JSON objects as MCP inputs and improved Windows compatibility.
923
+
924
+ ### New Features 🎉
925
+
926
+ * Set up multiple os tests by [@jlowin](https://github.com/jlowin) in [#44](https://github.com/jlowin/fastmcp/pull/44)
927
+ * Changes to accomodate windows users. by [@justjoehere](https://github.com/justjoehere) in [#42](https://github.com/jlowin/fastmcp/pull/42)
928
+ * Handle complex inputs by [@jurasofish](https://github.com/jurasofish) in [#31](https://github.com/jlowin/fastmcp/pull/31)
929
+
930
+ ### Docs 📚
931
+
932
+ * Make AI labeler more conservative by [@jlowin](https://github.com/jlowin) in [#46](https://github.com/jlowin/fastmcp/pull/46)
933
+
934
+ ### Other Changes 🦾
935
+
936
+ * Additional Windows Fixes for Dev running and for importing modules in a server by [@justjoehere](https://github.com/justjoehere) in [#43](https://github.com/jlowin/fastmcp/pull/43)
937
+
938
+ ### New Contributors
939
+
940
+ * [@justjoehere](https://github.com/justjoehere) made their first contribution in [#42](https://github.com/jlowin/fastmcp/pull/42)
941
+ * [@jurasofish](https://github.com/jurasofish) made their first contribution in [#31](https://github.com/jlowin/fastmcp/pull/31)
942
+
943
+ **Full Changelog**: [v0.3.4...v0.3.5](https://github.com/jlowin/fastmcp/compare/v0.3.4...v0.3.5)
944
+ </Update>
945
+
946
+ <Update label="v0.3.4" description="2024-12-02">
947
+
948
+ ## [v0.3.4: URL's Well That Ends Well](https://github.com/jlowin/fastmcp/releases/tag/v0.3.4)
949
+
950
+ ### Fixes 🐞
951
+
952
+ * Handle missing config file when installing by [@jlowin](https://github.com/jlowin) in [#37](https://github.com/jlowin/fastmcp/pull/37)
953
+ * Remove BaseURL reference and use AnyURL by [@jlowin](https://github.com/jlowin) in [#40](https://github.com/jlowin/fastmcp/pull/40)
954
+
955
+ **Full Changelog**: [v0.3.3...v0.3.4](https://github.com/jlowin/fastmcp/compare/v0.3.3...v0.3.4)
956
+ </Update>
957
+
958
+ <Update label="v0.3.3" description="2024-12-02">
959
+
960
+ ## [v0.3.3: Dependence Day](https://github.com/jlowin/fastmcp/releases/tag/v0.3.3)
961
+
962
+ ### New Features 🎉
963
+
964
+ * Surge example by [@zzstoatzz](https://github.com/zzstoatzz) in [#29](https://github.com/jlowin/fastmcp/pull/29)
965
+ * Support Python dependencies in Server by [@jlowin](https://github.com/jlowin) in [#34](https://github.com/jlowin/fastmcp/pull/34)
966
+
967
+ ### Docs 📚
968
+
969
+ * add `Contributing` section to README by [@zzstoatzz](https://github.com/zzstoatzz) in [#32](https://github.com/jlowin/fastmcp/pull/32)
970
+
971
+ **Full Changelog**: [v0.3.2...v0.3.3](https://github.com/jlowin/fastmcp/compare/v0.3.2...v0.3.3)
972
+ </Update>
973
+
974
+ <Update label="v0.3.2" date="2024-12-01" description="Green with ENVy">
975
+
976
+ ## [v0.3.2: Green with ENVy](https://github.com/jlowin/fastmcp/releases/tag/v0.3.2)
977
+
978
+ ### New Features 🎉
979
+
980
+ * Support env vars when installing by [@jlowin](https://github.com/jlowin) in [#27](https://github.com/jlowin/fastmcp/pull/27)
981
+
982
+ ### Docs 📚
983
+
984
+ * Remove top level env var by [@jlowin](https://github.com/jlowin) in [#28](https://github.com/jlowin/fastmcp/pull/28)
985
+
986
+ **Full Changelog**: [v0.3.1...v0.3.2](https://github.com/jlowin/fastmcp/compare/v0.3.1...v0.3.2)
987
+ </Update>
988
+
989
+ <Update label="v0.3.1" description="2024-12-01">
990
+
991
+ ## [v0.3.1](https://github.com/jlowin/fastmcp/releases/tag/v0.3.1)
992
+
993
+ ### New Features 🎉
994
+
995
+ * Update README.md by [@jlowin](https://github.com/jlowin) in [#23](https://github.com/jlowin/fastmcp/pull/23)
996
+ * add rich handler and dotenv loading for settings by [@zzstoatzz](https://github.com/zzstoatzz) in [#22](https://github.com/jlowin/fastmcp/pull/22)
997
+ * print exception when server can't start by [@jlowin](https://github.com/jlowin) in [#25](https://github.com/jlowin/fastmcp/pull/25)
998
+
999
+ ### Docs 📚
1000
+
1001
+ * Update README.md by [@jlowin](https://github.com/jlowin) in [#24](https://github.com/jlowin/fastmcp/pull/24)
1002
+
1003
+ ### Other Changes 🦾
1004
+
1005
+ * Remove log by [@jlowin](https://github.com/jlowin) in [#26](https://github.com/jlowin/fastmcp/pull/26)
1006
+
1007
+ **Full Changelog**: [v0.3.0...v0.3.1](https://github.com/jlowin/fastmcp/compare/v0.3.0...v0.3.1)
1008
+ </Update>
1009
+
1010
+ <Update label="v0.3.0" description="2024-12-01">
1011
+
1012
+ ## [v0.3.0: Prompt and Circumstance](https://github.com/jlowin/fastmcp/releases/tag/v0.3.0)
1013
+
1014
+ ### New Features 🎉
1015
+
1016
+ * Update README by [@jlowin](https://github.com/jlowin) in [#3](https://github.com/jlowin/fastmcp/pull/3)
1017
+ * Make log levels strings by [@jlowin](https://github.com/jlowin) in [#4](https://github.com/jlowin/fastmcp/pull/4)
1018
+ * Make content method a function by [@jlowin](https://github.com/jlowin) in [#5](https://github.com/jlowin/fastmcp/pull/5)
1019
+ * Add template support by [@jlowin](https://github.com/jlowin) in [#6](https://github.com/jlowin/fastmcp/pull/6)
1020
+ * Refactor resources module by [@jlowin](https://github.com/jlowin) in [#7](https://github.com/jlowin/fastmcp/pull/7)
1021
+ * Clean up cli imports by [@jlowin](https://github.com/jlowin) in [#8](https://github.com/jlowin/fastmcp/pull/8)
1022
+ * Prepare to list templates by [@jlowin](https://github.com/jlowin) in [#11](https://github.com/jlowin/fastmcp/pull/11)
1023
+ * Move image to separate module by [@jlowin](https://github.com/jlowin) in [#9](https://github.com/jlowin/fastmcp/pull/9)
1024
+ * Add support for request context, progress, logging, etc. by [@jlowin](https://github.com/jlowin) in [#12](https://github.com/jlowin/fastmcp/pull/12)
1025
+ * Add context tests and better runtime loads by [@jlowin](https://github.com/jlowin) in [#13](https://github.com/jlowin/fastmcp/pull/13)
1026
+ * Refactor tools + resourcemanager by [@jlowin](https://github.com/jlowin) in [#14](https://github.com/jlowin/fastmcp/pull/14)
1027
+ * func → fn everywhere by [@jlowin](https://github.com/jlowin) in [#15](https://github.com/jlowin/fastmcp/pull/15)
1028
+ * Add support for prompts by [@jlowin](https://github.com/jlowin) in [#16](https://github.com/jlowin/fastmcp/pull/16)
1029
+ * Create LICENSE by [@jlowin](https://github.com/jlowin) in [#18](https://github.com/jlowin/fastmcp/pull/18)
1030
+ * Update cli file spec by [@jlowin](https://github.com/jlowin) in [#19](https://github.com/jlowin/fastmcp/pull/19)
1031
+ * Update readmeUpdate README by [@jlowin](https://github.com/jlowin) in [#20](https://github.com/jlowin/fastmcp/pull/20)
1032
+ * Use hatchling for version by [@jlowin](https://github.com/jlowin) in [#21](https://github.com/jlowin/fastmcp/pull/21)
1033
+
1034
+ ### Other Changes 🦾
1035
+
1036
+ * Add echo server by [@jlowin](https://github.com/jlowin) in [#1](https://github.com/jlowin/fastmcp/pull/1)
1037
+ * Add github workflows by [@jlowin](https://github.com/jlowin) in [#2](https://github.com/jlowin/fastmcp/pull/2)
1038
+ * typing updates by [@zzstoatzz](https://github.com/zzstoatzz) in [#17](https://github.com/jlowin/fastmcp/pull/17)
1039
+
1040
+ ### New Contributors
1041
+
1042
+ * [@jlowin](https://github.com/jlowin) made their first contribution in [#1](https://github.com/jlowin/fastmcp/pull/1)
1043
+ * [@zzstoatzz](https://github.com/zzstoatzz) made their first contribution in [#17](https://github.com/jlowin/fastmcp/pull/17)
1044
+
1045
+ **Full Changelog**: [v0.2.0...v0.3.0](https://github.com/jlowin/fastmcp/compare/v0.2.0...v0.3.0)
1046
+ </Update>
1047
+
1048
+ <Update label="v0.2.0" description="2024-11-30">
1049
+
1050
+ ## [v0.2.0](https://github.com/jlowin/fastmcp/releases/tag/v0.2.0)
1051
+
1052
+ **Full Changelog**: [v0.1.0...v0.2.0](https://github.com/jlowin/fastmcp/compare/v0.1.0...v0.2.0)
1053
+ </Update>
1054
+
1055
+ <Update label="v0.1.0" description="2024-11-30">
1056
+
1057
+ ## [v0.1.0](https://github.com/jlowin/fastmcp/releases/tag/v0.1.0)
1058
+
1059
+ The very first release of FastMCP! 🎉
1060
+
1061
+ **Full Changelog**: [Initial commits](https://github.com/jlowin/fastmcp/commits/v0.1.0)
1062
+ </Update>
docs/clients/auth/oauth.mdx CHANGED
@@ -102,7 +102,7 @@ OAuth access tokens are automatically cached in `~/.fastmcp/oauth-mcp-client-cac
102
  To clear the tokens for a specific server, instantiate a `FileTokenStorage` instance and call the `clear` method:
103
 
104
  ```python
105
- from fastmcp.client.auth import FileTokenStorage
106
 
107
  storage = FileTokenStorage(server_url="https://fastmcp.cloud/mcp")
108
  await storage.clear()
@@ -111,7 +111,7 @@ await storage.clear()
111
  To clear *all* tokens for all servers, call the `clear_all` method on the `FileTokenStorage` class:
112
 
113
  ```python
114
- from fastmcp.client.auth import FileTokenStorage
115
 
116
  FileTokenStorage.clear_all()
117
- ```
 
102
  To clear the tokens for a specific server, instantiate a `FileTokenStorage` instance and call the `clear` method:
103
 
104
  ```python
105
+ from fastmcp.client.auth.oauth import FileTokenStorage
106
 
107
  storage = FileTokenStorage(server_url="https://fastmcp.cloud/mcp")
108
  await storage.clear()
 
111
  To clear *all* tokens for all servers, call the `clear_all` method on the `FileTokenStorage` class:
112
 
113
  ```python
114
+ from fastmcp.client.auth.oauth import FileTokenStorage
115
 
116
  FileTokenStorage.clear_all()
117
+ ```
docs/docs.json CHANGED
@@ -19,14 +19,6 @@
19
  "light": "#4cc9f0",
20
  "primary": "#2d00f7"
21
  },
22
- "contextual": {
23
- "options": [
24
- "copy",
25
- "view",
26
- "chatgpt",
27
- "claude"
28
- ]
29
- },
30
  "description": "The fast, Pythonic way to build MCP servers and clients.",
31
  "favicon": {
32
  "dark": "/assets/favicon.ico",
@@ -52,83 +44,106 @@
52
  }
53
  },
54
  "navigation": {
55
- "groups": [
56
  {
57
- "group": "Get Started",
58
- "pages": [
59
- "getting-started/welcome",
60
- "getting-started/installation",
61
- "getting-started/quickstart",
62
- "updates"
63
- ]
64
- },
65
- {
66
- "group": "Servers",
67
- "pages": [
68
- "servers/fastmcp",
69
  {
70
- "group": "Core Components",
71
- "icon": "toolbox",
72
  "pages": [
73
- "servers/tools",
74
- "servers/resources",
75
- "servers/prompts",
76
- "servers/context"
77
  ]
78
  },
79
  {
80
- "group": "Authentication",
81
- "icon": "shield-check",
82
  "pages": [
83
- "servers/auth/bearer"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  ]
85
  },
86
- "servers/openapi",
87
- "servers/proxy",
88
- "servers/composition",
89
  {
90
- "group": "Deployment",
91
- "icon": "upload",
92
  "pages": [
93
- "deployment/running-server",
94
- "deployment/asgi"
 
 
 
 
 
 
 
 
 
95
  ]
96
- }
97
- ]
98
- },
99
- {
100
- "group": "Clients",
101
- "pages": [
102
- "clients/client",
103
- "clients/transports",
104
  {
105
- "group": "Authentication",
106
- "icon": "user-shield",
107
  "pages": [
108
- "clients/auth/oauth",
109
- "clients/auth/bearer"
 
 
 
110
  ]
111
  },
112
- "clients/advanced-features"
113
- ]
 
 
 
 
 
 
 
 
 
 
114
  },
115
  {
116
- "group": "Integrations",
 
117
  "pages": [
118
- "integrations/anthropic",
119
- "integrations/claude-desktop",
120
- "integrations/openai",
121
- "integrations/gemini",
122
- "integrations/contrib"
123
  ]
124
  },
125
  {
126
- "group": "Patterns",
 
127
  "pages": [
128
- "patterns/decorating-methods",
129
- "patterns/http-requests",
130
- "patterns/testing",
131
- "patterns/cli"
132
  ]
133
  }
134
  ]
 
19
  "light": "#4cc9f0",
20
  "primary": "#2d00f7"
21
  },
 
 
 
 
 
 
 
 
22
  "description": "The fast, Pythonic way to build MCP servers and clients.",
23
  "favicon": {
24
  "dark": "/assets/favicon.ico",
 
44
  }
45
  },
46
  "navigation": {
47
+ "anchors": [
48
  {
49
+ "anchor": "Documentation",
50
+ "groups": [
 
 
 
 
 
 
 
 
 
 
51
  {
52
+ "group": "Get Started",
 
53
  "pages": [
54
+ "getting-started/welcome",
55
+ "getting-started/installation",
56
+ "getting-started/quickstart",
57
+ "updates"
58
  ]
59
  },
60
  {
61
+ "group": "Servers",
 
62
  "pages": [
63
+ "servers/fastmcp",
64
+ {
65
+ "group": "Core Components",
66
+ "icon": "toolbox",
67
+ "pages": [
68
+ "servers/tools",
69
+ "servers/resources",
70
+ "servers/prompts",
71
+ "servers/context"
72
+ ]
73
+ },
74
+ {
75
+ "group": "Authentication",
76
+ "icon": "shield-check",
77
+ "pages": [
78
+ "servers/auth/bearer"
79
+ ]
80
+ },
81
+ "servers/openapi",
82
+ "servers/proxy",
83
+ "servers/composition",
84
+ {
85
+ "group": "Deployment",
86
+ "icon": "upload",
87
+ "pages": [
88
+ "deployment/running-server",
89
+ "deployment/asgi"
90
+ ]
91
+ }
92
  ]
93
  },
 
 
 
94
  {
95
+ "group": "Clients",
 
96
  "pages": [
97
+ "clients/client",
98
+ "clients/transports",
99
+ {
100
+ "group": "Authentication",
101
+ "icon": "user-shield",
102
+ "pages": [
103
+ "clients/auth/oauth",
104
+ "clients/auth/bearer"
105
+ ]
106
+ },
107
+ "clients/advanced-features"
108
  ]
109
+ },
 
 
 
 
 
 
 
110
  {
111
+ "group": "Integrations",
 
112
  "pages": [
113
+ "integrations/anthropic",
114
+ "integrations/claude-desktop",
115
+ "integrations/openai",
116
+ "integrations/gemini",
117
+ "integrations/contrib"
118
  ]
119
  },
120
+ {
121
+ "group": "Patterns",
122
+ "pages": [
123
+ "patterns/tool-transformation",
124
+ "patterns/decorating-methods",
125
+ "patterns/http-requests",
126
+ "patterns/testing",
127
+ "patterns/cli"
128
+ ]
129
+ }
130
+ ],
131
+ "icon": "book"
132
  },
133
  {
134
+ "anchor": "Tutorials",
135
+ "icon": "graduation-cap",
136
  "pages": [
137
+ "tutorials/mcp",
138
+ "tutorials/create-mcp-server",
139
+ "tutorials/rest-api"
 
 
140
  ]
141
  },
142
  {
143
+ "anchor": "Changelog",
144
+ "icon": "list-check",
145
  "pages": [
146
+ "changelog"
 
 
 
147
  ]
148
  }
149
  ]
docs/getting-started/installation.mdx CHANGED
@@ -72,7 +72,7 @@ For users concerned about stability in production environments, we recommend pin
72
 
73
  Whenever possible, FastMCP will issue deprecation warnings when users attempt to use APIs that are either deprecated or destined for future removal. These warnings will be maintained for at least 1 minor version release, and may be maintained longer.
74
 
75
- Note that the "public API" includes the core functionality of the `FastMCP` server and its methods. It does not include private methods or objects that are stored as private attributes, as we do not expect users to rely on those implementation details.
76
 
77
  ## Installing for Development
78
 
 
72
 
73
  Whenever possible, FastMCP will issue deprecation warnings when users attempt to use APIs that are either deprecated or destined for future removal. These warnings will be maintained for at least 1 minor version release, and may be maintained longer.
74
 
75
+ Note that the "public API" includes the public functionality of the `FastMCP` server, core FastMCP components like `Tool`, `Prompt`, `Resource`, and `ResourceTemplate`, and their respective public methods. It does not include private methods, utilities, or objects that are stored as private attributes, as we do not expect users to rely on those implementation details.
76
 
77
  ## Installing for Development
78
 
docs/getting-started/welcome.mdx CHANGED
@@ -2,7 +2,6 @@
2
  title: "Welcome to FastMCP 2.0!"
3
  sidebarTitle: "Welcome!"
4
  description: The fast, Pythonic way to build MCP servers and clients.
5
-
6
  icon: hand-wave
7
  ---
8
 
@@ -60,11 +59,18 @@ FastMCP aims to be:
60
 
61
  🔍 **Complete**: A comprehensive platform for all MCP use cases, from dev to prod
62
 
 
 
 
63
 
64
- ## `llms.txt`
65
 
66
  This documentation is also available in [llms.txt format](https://llmstxt.org/), which is a simple markdown standard that LLMs can consume easily.
67
 
68
  There are two ways to access the LLM-friendly documentation:
69
- - [`llms.txt`](https://gofastmcp.com/llms.txt) is essentially a sitemap, listing all the pages in the documentation.
70
- - [`llms-full.txt`](https://gofastmcp.com/llms-full.txt) contains the entire documentation. Note this may exceed the context window of your LLM.
 
 
 
 
 
2
  title: "Welcome to FastMCP 2.0!"
3
  sidebarTitle: "Welcome!"
4
  description: The fast, Pythonic way to build MCP servers and clients.
 
5
  icon: hand-wave
6
  ---
7
 
 
59
 
60
  🔍 **Complete**: A comprehensive platform for all MCP use cases, from dev to prod
61
 
62
+ FastMCP is made with 💙 by [Prefect](https://www.prefect.io/).
63
+
64
+
65
 
66
+ ## LLM-Friendly Docs
67
 
68
  This documentation is also available in [llms.txt format](https://llmstxt.org/), which is a simple markdown standard that LLMs can consume easily.
69
 
70
  There are two ways to access the LLM-friendly documentation:
71
+ - [llms.txt](https://gofastmcp.com/llms.txt) is essentially a sitemap, listing all the pages in the documentation.
72
+ - [llms-full.txt](https://gofastmcp.com/llms-full.txt) contains the entire documentation. Note this may exceed the context window of your LLM.
73
+
74
+ In addition, any page can be accessed as markdown by appending `.md` to the URL. For example, this page would become `https://gofastmcp.com/getting-started/welcome.md`, which you can view [here](/getting-started/welcome.md).
75
+
76
+ Finally, you can copy the contents of any page as markdown by pressing "Cmd+C" (or "Ctrl+C" on Windows) on your keyboard.
docs/integrations/anthropic.mdx CHANGED
@@ -1,6 +1,6 @@
1
  ---
2
- title: Anthropic
3
- sidebarTitle: Anthropic
4
  description: Call FastMCP servers from the Anthropic API
5
  icon: message-smile
6
  tag: "New!"
@@ -8,9 +8,6 @@ tag: "New!"
8
 
9
  import { VersionBadge } from "/snippets/version-badge.mdx"
10
 
11
- Anthropic supports MCP servers through the [MCP connector](https://docs.anthropic.com/en/docs/agents-and-tools/mcp-connector) feature in the Messages API, allowing you to extend AI capabilities with custom tools from remote MCP servers.
12
-
13
- ## Messages API
14
 
15
  Anthropic's [Messages API](https://docs.anthropic.com/en/api/messages) supports MCP servers as remote tool sources. This tutorial will show you how to create a FastMCP server and deploy it to a public URL, then how to call it from the Messages API.
16
 
@@ -18,7 +15,7 @@ Anthropic's [Messages API](https://docs.anthropic.com/en/api/messages) supports
18
  Currently, the MCP connector only accesses **tools** from MCP servers—it queries the `list_tools` endpoint and exposes those functions to Claude. Other MCP features like resources and prompts are not currently supported. You can read more about the MCP connector in the [Anthropic documentation](https://docs.anthropic.com/en/docs/agents-and-tools/mcp-connector).
19
  </Tip>
20
 
21
- ### Create a Server
22
 
23
  First, create a FastMCP server with the tools you want to expose. For this example, we'll create a server with a single tool that rolls dice.
24
 
@@ -37,7 +34,7 @@ if __name__ == "__main__":
37
  mcp.run(transport="sse", port=8000)
38
  ```
39
 
40
- ### Deploy the Server
41
 
42
  Your server must be deployed to a public URL in order for Anthropic to access it. The MCP connector supports both SSE and Streamable HTTP transports.
43
 
@@ -59,7 +56,7 @@ ngrok http 8000
59
  This exposes your unauthenticated server to the internet. Only run this command in a safe environment if you understand the risks.
60
  </Warning>
61
 
62
- ### Call the Server
63
 
64
  To use the Messages API with MCP servers, you'll need to install the Anthropic Python SDK (not included with FastMCP):
65
 
@@ -114,13 +111,13 @@ The results were 4, 2, and 6. Would you like me to roll again or roll a differen
114
  ```
115
 
116
 
117
- ### Authentication
118
 
119
  <VersionBadge version="2.6.0" />
120
 
121
  The MCP connector supports OAuth authentication through authorization tokens, which means you can secure your server while still allowing Anthropic to access it.
122
 
123
- #### Server Authentication
124
 
125
  The simplest way to add authentication to the server is to use a bearer token scheme.
126
 
@@ -181,7 +178,7 @@ if __name__ == "__main__":
181
  mcp.run(transport="sse", port=8000)
182
  ```
183
 
184
- #### Client Authentication
185
 
186
  If you try to call the authenticated server with the same Anthropic code we wrote earlier, you'll get an error indicating that the server rejected the request because it's not authenticated.
187
 
 
1
  ---
2
+ title: Anthropic API + FastMCP
3
+ sidebarTitle: Anthropic API
4
  description: Call FastMCP servers from the Anthropic API
5
  icon: message-smile
6
  tag: "New!"
 
8
 
9
  import { VersionBadge } from "/snippets/version-badge.mdx"
10
 
 
 
 
11
 
12
  Anthropic's [Messages API](https://docs.anthropic.com/en/api/messages) supports MCP servers as remote tool sources. This tutorial will show you how to create a FastMCP server and deploy it to a public URL, then how to call it from the Messages API.
13
 
 
15
  Currently, the MCP connector only accesses **tools** from MCP servers—it queries the `list_tools` endpoint and exposes those functions to Claude. Other MCP features like resources and prompts are not currently supported. You can read more about the MCP connector in the [Anthropic documentation](https://docs.anthropic.com/en/docs/agents-and-tools/mcp-connector).
16
  </Tip>
17
 
18
+ ## Create a Server
19
 
20
  First, create a FastMCP server with the tools you want to expose. For this example, we'll create a server with a single tool that rolls dice.
21
 
 
34
  mcp.run(transport="sse", port=8000)
35
  ```
36
 
37
+ ## Deploy the Server
38
 
39
  Your server must be deployed to a public URL in order for Anthropic to access it. The MCP connector supports both SSE and Streamable HTTP transports.
40
 
 
56
  This exposes your unauthenticated server to the internet. Only run this command in a safe environment if you understand the risks.
57
  </Warning>
58
 
59
+ ## Call the Server
60
 
61
  To use the Messages API with MCP servers, you'll need to install the Anthropic Python SDK (not included with FastMCP):
62
 
 
111
  ```
112
 
113
 
114
+ ## Authentication
115
 
116
  <VersionBadge version="2.6.0" />
117
 
118
  The MCP connector supports OAuth authentication through authorization tokens, which means you can secure your server while still allowing Anthropic to access it.
119
 
120
+ ### Server Authentication
121
 
122
  The simplest way to add authentication to the server is to use a bearer token scheme.
123
 
 
178
  mcp.run(transport="sse", port=8000)
179
  ```
180
 
181
+ ### Client Authentication
182
 
183
  If you try to call the authenticated server with the same Anthropic code we wrote earlier, you'll get an error indicating that the server rejected the request because it's not authenticated.
184
 
docs/integrations/claude-desktop.mdx CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: Claude Desktop
3
  sidebarTitle: Claude Desktop
4
  description: Call FastMCP servers from Claude Desktop
5
  icon: desktop
 
1
  ---
2
+ title: Claude Desktop + FastMCP
3
  sidebarTitle: Claude Desktop
4
  description: Call FastMCP servers from Claude Desktop
5
  icon: desktop
docs/integrations/contrib.mdx CHANGED
@@ -12,7 +12,7 @@ FastMCP includes a `contrib` package that holds community-contributed modules. T
12
 
13
  Contrib modules provide additional features, integrations, or patterns that complement the core FastMCP library. They offer a way for the community to share useful extensions while keeping the core library focused and maintainable.
14
 
15
- The available modules can be viewed in the [contrib directory](https://github.com/jlowin/fastmcp/tree/main/src/contrib).
16
 
17
  ## Usage
18
 
 
12
 
13
  Contrib modules provide additional features, integrations, or patterns that complement the core FastMCP library. They offer a way for the community to share useful extensions while keeping the core library focused and maintainable.
14
 
15
+ The available modules can be viewed in the [contrib directory](https://github.com/jlowin/fastmcp/tree/main/src/fastmcp/contrib).
16
 
17
  ## Usage
18
 
docs/integrations/gemini.mdx CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: Gemini SDK
3
  sidebarTitle: Gemini SDK
4
  description: Call FastMCP servers from the Google Gemini SDK
5
  icon: message-smile
@@ -98,7 +98,7 @@ For example, to connect to a remote, authenticated server, you can use the follo
98
  from fastmcp import Client
99
  from fastmcp.client.auth import BearerAuth
100
 
101
- client = Client(
102
  "https://my-server.com/sse",
103
  auth=BearerAuth("<your-token>"),
104
  )
 
1
  ---
2
+ title: Gemini SDK + FastMCP
3
  sidebarTitle: Gemini SDK
4
  description: Call FastMCP servers from the Google Gemini SDK
5
  icon: message-smile
 
98
  from fastmcp import Client
99
  from fastmcp.client.auth import BearerAuth
100
 
101
+ mcp_client = Client(
102
  "https://my-server.com/sse",
103
  auth=BearerAuth("<your-token>"),
104
  )
docs/integrations/openai.mdx CHANGED
@@ -1,6 +1,6 @@
1
  ---
2
- title: OpenAI
3
- sidebarTitle: OpenAI
4
  description: Call FastMCP servers from the OpenAI API
5
  icon: message-smile
6
  tag: "New!"
@@ -8,14 +8,13 @@ tag: "New!"
8
 
9
  import { VersionBadge } from "/snippets/version-badge.mdx"
10
 
11
- OpenAI recently announced support for MCP servers in the Responses API. Note that at this time, MCP is not supported in ChatGPT.
12
 
13
  ## Responses API
14
 
15
  OpenAI's [Responses API](https://platform.openai.com/docs/api-reference/responses) supports [MCP servers](https://platform.openai.com/docs/guides/tools-remote-mcp) as remote tool sources, allowing you to extend AI capabilities with custom functions.
16
 
17
  <Note>
18
- The Responses API is a distinct API from OpenAI's Completions API, Assistants API, or ChatGPT. At this time, only the Responses API supports MCP.
19
  </Note>
20
 
21
  <Tip>
 
1
  ---
2
+ title: OpenAI API + FastMCP
3
+ sidebarTitle: OpenAI API
4
  description: Call FastMCP servers from the OpenAI API
5
  icon: message-smile
6
  tag: "New!"
 
8
 
9
  import { VersionBadge } from "/snippets/version-badge.mdx"
10
 
 
11
 
12
  ## Responses API
13
 
14
  OpenAI's [Responses API](https://platform.openai.com/docs/api-reference/responses) supports [MCP servers](https://platform.openai.com/docs/guides/tools-remote-mcp) as remote tool sources, allowing you to extend AI capabilities with custom functions.
15
 
16
  <Note>
17
+ The Responses API is a distinct API from OpenAI's Completions API or Assistants API. At this time, only the Responses API supports MCP.
18
  </Note>
19
 
20
  <Tip>
docs/patterns/tool-transformation.mdx ADDED
@@ -0,0 +1,454 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Tool Transformation
3
+ sidebarTitle: Tool Transformation
4
+ description: Create enhanced tool variants with modified schemas, argument mappings, and custom behavior.
5
+ icon: wand-magic-sparkles
6
+ ---
7
+
8
+ import { VersionBadge } from '/snippets/version-badge.mdx'
9
+
10
+ <VersionBadge version="2.8.0" />
11
+
12
+ Tool transformation allows you to create new, enhanced tools from existing ones. This powerful feature enables you to adapt tools for different contexts, simplify complex interfaces, or add custom logic without duplicating code.
13
+
14
+ ## Why Transform Tools?
15
+
16
+ Often, an existing tool is *almost* perfect for your use case, but it might have:
17
+ - A confusing description (or no description at all).
18
+ - Argument names or descriptions that are not intuitive for an LLM (e.g., `q` instead of `query`).
19
+ - Unnecessary parameters that you want to hide from the LLM.
20
+ - A need for input validation before the original tool is called.
21
+ - A need to modify or format the tool's output.
22
+
23
+ Instead of rewriting the tool from scratch, you can **transform** it to fit your needs.
24
+
25
+ ## Basic Transformation
26
+
27
+ The primary way to create a transformed tool is with the `Tool.from_tool()` class method. At its simplest, you can use it to change a tool's top-level metadata like its `name`, `description`, or `tags`.
28
+
29
+ In the following simple example, we take a generic `search` tool and adjust its name and description to help an LLM client better understand its purpose.
30
+
31
+ ```python {13-21}
32
+ from fastmcp import FastMCP
33
+ from fastmcp.tools import Tool
34
+
35
+ mcp = FastMCP()
36
+
37
+ # The original, generic tool
38
+ @mcp.tool
39
+ def search(query: str, category: str = "all") -> list[dict]:
40
+ """Searches for items in the database."""
41
+ return database.search(query, category)
42
+
43
+ # Create a more domain-specific version by changing its metadata
44
+ product_search_tool = Tool.from_tool(
45
+ search,
46
+ name="find_products",
47
+ description="""
48
+ Search for products in the e-commerce catalog.
49
+ Use this when customers ask about finding specific items,
50
+ checking availability, or browsing product categories.
51
+ """,
52
+ )
53
+
54
+ mcp.add_tool(product_search_tool)
55
+ ```
56
+
57
+ <Tip>
58
+ When you transform a tool, the original tool remains registered on the server. To avoid confusing an LLM with two similar tools, you can disable the original one:
59
+
60
+ ```python
61
+ from fastmcp import FastMCP
62
+ from fastmcp.tools import Tool
63
+
64
+ mcp = FastMCP()
65
+
66
+ # The original, generic tool
67
+ @mcp.tool
68
+ def search(query: str, category: str = "all") -> list[dict]:
69
+ ...
70
+
71
+ # Create a more domain-specific version
72
+ product_search_tool = Tool.from_tool(search, ...)
73
+ mcp.add_tool(product_search_tool)
74
+
75
+ # Disable the original tool
76
+ search.disable()
77
+ ```
78
+ </Tip>
79
+
80
+ Now, clients see a tool named `find_products` with a clear, domain-specific purpose and relevant tags, even though it still uses the original generic `search` function's logic.
81
+
82
+ ### Parameters
83
+
84
+ The `Tool.from_tool()` class method is the primary way to create a transformed tool. It takes the following parameters:
85
+
86
+ - `tool`: The tool to transform. This is the only required argument.
87
+ - `name`: An optional name for the new tool.
88
+ - `description`: An optional description for the new tool.
89
+ - `transform_args`: A dictionary of `ArgTransform` objects, one for each argument you want to modify.
90
+ - `transform_fn`: An optional function that will be called instead of the parent tool's logic.
91
+ - `tags`: An optional set of tags for the new tool.
92
+ - `annotations`: An optional set of `ToolAnnotations` for the new tool.
93
+ - `serializer`: An optional function that will be called to serialize the result of the new tool.
94
+
95
+ The result is a new `TransformedTool` object that wraps the parent tool and applies the transformations you specify. You can add this tool to your MCP server using its `add_tool()` method.
96
+
97
+
98
+
99
+ ## Modifying Arguments
100
+
101
+ To modify a tool's parameters, provide a dictionary of `ArgTransform` objects to the `transform_args` parameter of `Tool.from_tool()`. Each key is the name of the *original* argument you want to modify.
102
+
103
+ <Tip>
104
+ You only need to provide a `transform_args` entry for arguments you want to modify. All other arguments will be passed through unchanged.
105
+ </Tip>
106
+
107
+ ### The ArgTransform Class
108
+
109
+ To modify an argument, you need to create an `ArgTransform` object. This object has the following parameters:
110
+
111
+ - `name`: The new name for the argument.
112
+ - `description`: The new description for the argument.
113
+ - `default`: The new default value for the argument.
114
+ - `default_factory`: A function that will be called to generate a default value for the argument. This is useful for arguments that need to be generated for each tool call, such as timestamps or unique IDs.
115
+ - `hide`: Whether to hide the argument from the LLM.
116
+ - `required`: Whether the argument is required, usually used to make an optional argument be required instead.
117
+ - `type`: The new type for the argument.
118
+
119
+ <Tip>
120
+ Certain combinations of parameters are not allowed. For example, you can only use `default_factory` with `hide=True`, because dynamic defaults cannot be represented in a JSON schema for the client. You can only set required=True for arguments that do not declare a default value.
121
+ </Tip>
122
+
123
+
124
+ ### Descriptions
125
+
126
+ By far the most common reason to transform a tool, after its own description, is to improve its argument descriptions. A good description is crucial for helping an LLM understand how to use a parameter correctly. This is especially important when wrapping tools from external APIs, whose argument descriptions may be missing or written for developers, not LLMs.
127
+
128
+ In this example, we add a helpful description to the `user_id` argument:
129
+
130
+ ```python {16-19}
131
+ from fastmcp import FastMCP
132
+ from fastmcp.tools import Tool
133
+ from fastmcp.tools.tool_transform import ArgTransform
134
+
135
+ mcp = FastMCP()
136
+
137
+ @mcp.tool
138
+ def find_user(user_id: str):
139
+ """Finds a user by their ID."""
140
+ ...
141
+
142
+ new_tool = Tool.from_tool(
143
+ find_user,
144
+ transform_args={
145
+ "user_id": ArgTransform(
146
+ description=(
147
+ "The unique identifier for the user, "
148
+ "usually in the format 'usr-xxxxxxxx'."
149
+ )
150
+ )
151
+ }
152
+ )
153
+ ```
154
+
155
+ ### Names
156
+
157
+ At times, you may want to rename an argument to make it more intuitive for an LLM.
158
+
159
+ For example, in the following example, we take a generic `q` argument and expand it to `search_query`:
160
+
161
+ ```python {15}
162
+ from fastmcp import FastMCP
163
+ from fastmcp.tools import Tool
164
+ from fastmcp.tools.tool_transform import ArgTransform
165
+
166
+ mcp = FastMCP()
167
+
168
+ @mcp.tool
169
+ def search(q: str):
170
+ """Searches for items in the database."""
171
+ return database.search(q)
172
+
173
+ new_tool = Tool.from_tool(
174
+ search,
175
+ transform_args={
176
+ "q": ArgTransform(name="search_query")
177
+ }
178
+ )
179
+ ```
180
+
181
+ ### Default Values
182
+
183
+ You can update the default value for any argument using the `default` parameter. Here, we change the default value of the `y` argument to 10:
184
+
185
+ ```python{15}
186
+ from fastmcp import FastMCP
187
+ from fastmcp.tools import Tool
188
+ from fastmcp.tools.tool_transform import ArgTransform
189
+
190
+ mcp = FastMCP()
191
+
192
+ @mcp.tool
193
+ def add(x: int, y: int) -> int:
194
+ """Adds two numbers."""
195
+ return x + y
196
+
197
+ new_tool = Tool.from_tool(
198
+ add,
199
+ transform_args={
200
+ "y": ArgTransform(default=10)
201
+ }
202
+ )
203
+ ```
204
+
205
+ Default values are especially useful in combination with hidden arguments.
206
+
207
+ ### Hiding Arguments
208
+
209
+ Sometimes a tool requires arguments that shouldn't be exposed to the LLM, such as API keys, configuration flags, or internal IDs. You can hide these parameters using `hide=True`. Note that you can only hide arguments that have a default value (or for which you provide a new default), because the LLM can't provide a value at call time.
210
+
211
+ <Tip>
212
+ To pass a constant value to the parent tool, combine `hide=True` with `default=<value>`.
213
+ </Tip>
214
+
215
+ ```python {19-20}
216
+ import os
217
+ from fastmcp import FastMCP
218
+ from fastmcp.tools import Tool
219
+ from fastmcp.tools.tool_transform import ArgTransform
220
+
221
+ mcp = FastMCP()
222
+
223
+ @mcp.tool
224
+ def send_email(to: str, subject: str, body: str, api_key: str):
225
+ """Sends an email."""
226
+ ...
227
+
228
+ # Create a simplified version that hides the API key
229
+ new_tool = Tool.from_tool(
230
+ send_email,
231
+ name="send_notification",
232
+ transform_args={
233
+ "api_key": ArgTransform(
234
+ hide=True,
235
+ default=os.environ.get("EMAIL_API_KEY"),
236
+ )
237
+ }
238
+ )
239
+ ```
240
+ The LLM now only sees the `to`, `subject`, and `body` parameters. The `api_key` is supplied automatically from an environment variable.
241
+
242
+ For values that must be generated for each tool call (like timestamps or unique IDs), use `default_factory`, which is called with no arguments every time the tool is called. For example,
243
+
244
+ ```python {3-4}
245
+ transform_args = {
246
+ 'timestamp': ArgTransform(
247
+ hide=True,
248
+ default_factory=lambda: datetime.now(),
249
+ )
250
+ }
251
+ ```
252
+
253
+ <Warning>
254
+ `default_factory` can only be used with `hide=True`. This is because visible parameters need static defaults that can be represented in a JSON schema for the client.
255
+ </Warning>
256
+
257
+ ### Required Values
258
+
259
+ In rare cases where you want to make an optional argument required, you can set `required=True`. This has no effect if the argument was already required.
260
+
261
+ ```python {3}
262
+ transform_args = {
263
+ 'user_id': ArgTransform(
264
+ required=True,
265
+ )
266
+ }
267
+ ```
268
+
269
+ ## Modifying Tool Behavior
270
+
271
+ <Warning>
272
+ With great power comes great responsibility. Modifying tool behavior is a very advanced feature.
273
+ </Warning>
274
+
275
+ In addition to changing a tool's schema, advanced users can also modify its behavior. This is useful for adding validation logic, or for post-processing the tool's output.
276
+
277
+ The `from_tool()` method takes a `transform_fn` parameter, which is an async function that replaces the parent tool's logic and gives you complete control over the tool's execution.
278
+
279
+ ### The Transform Function
280
+
281
+ The `transform_fn` is an async function that **completely replaces** the parent tool's logic.
282
+
283
+ Critically, the transform function's arguments are used to determine the new tool's final schema. Any arguments that are not already present in the parent tool schema OR the `transform_args` will be added to the new tool's schema. Note that when `transform_args` and your function have the same argument name, the `transform_args` metadata will take precedence, if provided.
284
+
285
+ ```python
286
+ async def my_custom_logic(user_input: str, max_length: int = 100) -> str:
287
+ # Your custom logic here - this completely replaces the parent tool
288
+ return f"Custom result for: {user_input[:max_length]}"
289
+
290
+ Tool.from_tool(transform_fn=my_custom_logic)
291
+ ```
292
+
293
+ <Tip>
294
+ The name / docstring of the `transform_fn` are ignored. Only its arguments are used to determine the final schema.
295
+ </Tip>
296
+
297
+ ### Calling the Parent Tool
298
+
299
+ Most of the time, you don't want to completely replace the parent tool's behavior. Instead, you want to add validation, modify inputs, or post-process outputs while still leveraging the parent tool's core functionality. For this, FastMCP provides the special `forward()` and `forward_raw()` functions.
300
+
301
+ Both `forward()` and `forward_raw()` are async functions that let you call the parent tool from within your `transform_fn`:
302
+
303
+ - **`forward()`** (recommended): Automatically handles argument mapping based on your `ArgTransform` configurations. Call it with the transformed argument names.
304
+ - **`forward_raw()`**: Bypasses all transformation and calls the parent tool directly with its original argument names. This is rarely needed unless you're doing complex argument manipulation, perhaps without `arg_transforms`.
305
+
306
+ The most common transformation pattern is to validate (potentially renamed) arguments before calling the parent tool. Here's an example that validates that `x` and `y` are positive before calling the parent tool:
307
+ <Tabs>
308
+ <Tab title="Using forward()">
309
+
310
+ In the simplest case, your parent tool and your transform function have the same arguments. You can call `forward()` with the same argument names as the parent tool:
311
+
312
+ ```python {15}
313
+ from fastmcp import FastMCP
314
+ from fastmcp.tools import Tool
315
+ from fastmcp.tools.tool_transform import forward
316
+
317
+ mcp = FastMCP()
318
+
319
+ @mcp.tool
320
+ def add(x: int, y: int) -> int:
321
+ """Adds two numbers."""
322
+ return x + y
323
+
324
+ async def ensure_positive(x: int, y: int) -> int:
325
+ if x <= 0 or y <= 0:
326
+ raise ValueError("x and y must be positive")
327
+ return await forward(x=x, y=y)
328
+
329
+ new_tool = Tool.from_tool(
330
+ add,
331
+ transform_fn=ensure_positive,
332
+ )
333
+
334
+ mcp.add_tool(new_tool)
335
+ ```
336
+ </Tab>
337
+ <Tab title="Using forward() with renamed args">
338
+
339
+ When your transformed tool has different argument names than the parent tool, you can call `forward()` with the renamed arguments and it will automatically map the arguments to the parent tool's arguments:
340
+
341
+ ```python {15, 20-23}
342
+ from fastmcp import FastMCP
343
+ from fastmcp.tools import Tool
344
+ from fastmcp.tools.tool_transform import forward
345
+
346
+ mcp = FastMCP()
347
+
348
+ @mcp.tool
349
+ def add(x: int, y: int) -> int:
350
+ """Adds two numbers."""
351
+ return x + y
352
+
353
+ async def ensure_positive(a: int, b: int) -> int:
354
+ if a <= 0 or b <= 0:
355
+ raise ValueError("a and b must be positive")
356
+ return await forward(a=a, b=b)
357
+
358
+ new_tool = Tool.from_tool(
359
+ add,
360
+ transform_fn=ensure_positive,
361
+ transform_args={
362
+ "x": ArgTransform(name="a"),
363
+ "y": ArgTransform(name="b"),
364
+ }
365
+ )
366
+
367
+ mcp.add_tool(new_tool)
368
+ ```
369
+ </Tab>
370
+ <Tab title="Using forward_raw()">
371
+ Finally, you can use `forward_raw()` to bypass all argument mapping and call the parent tool directly with its original argument names.
372
+
373
+ ```python {15, 20-23}
374
+ from fastmcp import FastMCP
375
+ from fastmcp.tools import Tool
376
+ from fastmcp.tools.tool_transform import forward
377
+
378
+ mcp = FastMCP()
379
+
380
+ @mcp.tool
381
+ def add(x: int, y: int) -> int:
382
+ """Adds two numbers."""
383
+ return x + y
384
+
385
+ async def ensure_positive(a: int, b: int) -> int:
386
+ if a <= 0 or b <= 0:
387
+ raise ValueError("a and b must be positive")
388
+ return await forward_raw(x=a, y=b)
389
+
390
+ new_tool = Tool.from_tool(
391
+ add,
392
+ transform_fn=ensure_positive,
393
+ transform_args={
394
+ "x": ArgTransform(name="a"),
395
+ "y": ArgTransform(name="b"),
396
+ }
397
+ )
398
+
399
+ mcp.add_tool(new_tool)
400
+ ```
401
+ </Tab>
402
+ </Tabs>
403
+
404
+ ### Passing Arguments with **kwargs
405
+
406
+ If your `transform_fn` includes `**kwargs` in its signature, it will receive **all arguments from the parent tool after `ArgTransform` configurations have been applied**. This is powerful for creating flexible validation functions that don't require you to add every argument to the function signature.
407
+
408
+ In the following example, we wrap a parent tool that accepts two arguments `x` and `y`. These are renamed to `a` and `b` in the transformed tool, and the transform only validates `a`, passing the other argument through as `**kwargs`.
409
+
410
+ ```python {12, 15}
411
+ from fastmcp import FastMCP
412
+ from fastmcp.tools import Tool
413
+ from fastmcp.tools.tool_transform import forward
414
+
415
+ mcp = FastMCP()
416
+
417
+ @mcp.tool
418
+ def add(x: int, y: int) -> int:
419
+ """Adds two numbers."""
420
+ return x + y
421
+
422
+ async def ensure_a_positive(a: int, **kwargs) -> int:
423
+ if a <= 0:
424
+ raise ValueError("a must be positive")
425
+ return await forward(a=a, **kwargs)
426
+
427
+ new_tool = Tool.from_tool(
428
+ add,
429
+ transform_fn=ensure_a_positive,
430
+ transform_args={
431
+ "x": ArgTransform(name="a"),
432
+ "y": ArgTransform(name="b"),
433
+ }
434
+ )
435
+
436
+ mcp.add_tool(new_tool)
437
+ ```
438
+
439
+ <Tip>
440
+ In the above example, `**kwargs` receives the renamed argument `b`, not the original argument `y`. It is therefore recommended to use with `forward()`, not `forward_raw()`.
441
+ </Tip>
442
+
443
+ ## Common Patterns
444
+
445
+ Tool transformation is a flexible feature that supports many powerful patterns. Here are a few common use cases to give you ideas.
446
+
447
+ ### Adapting Remote or Generated Tools
448
+ This is one of the most common reasons to use tool transformation. Tools from remote servers (via a [proxy](/servers/proxy)) or generated from an [OpenAPI spec](/servers/openapi) are often too generic for direct use by an LLM. You can use transformation to create a simpler, more intuitive version for your specific needs.
449
+
450
+ ### Chaining Transformations
451
+ You can chain transformations by using an already transformed tool as the parent for a new transformation. This lets you build up complex behaviors in layers, for example, first renaming arguments, and then adding validation logic to the renamed tool.
452
+
453
+ ### Context-Aware Tool Factories
454
+ You can write functions that act as "factories," generating specialized versions of a tool for different contexts. For example, you could create a `get_my_data` tool that is specific to the currently logged-in user by hiding the `user_id` parameter and providing it automatically.
docs/servers/composition.mdx CHANGED
@@ -215,7 +215,7 @@ You can configure the prefix format globally in code:
215
 
216
  ```python
217
  import fastmcp
218
- fastmcp.settings.settings.resource_prefix_format = "protocol"
219
  ```
220
 
221
  Or via environment variable:
 
215
 
216
  ```python
217
  import fastmcp
218
+ fastmcp.settings.resource_prefix_format = "protocol"
219
  ```
220
 
221
  Or via environment variable:
docs/servers/openapi.mdx CHANGED
@@ -41,17 +41,9 @@ That's it! Your entire API is now available as an MCP server. Clients can discov
41
 
42
  ## Route Mapping
43
 
 
44
 
45
-
46
- FastMCP analyzes your API specification and automatically creates MCP components based on HTTP semantics and REST conventions. By default, the following rules are used to determine what MCP component to create for each route:
47
-
48
- | OpenAPI Route | Example | MCP Component |
49
- |---------------|---------|---------------|
50
- | `GET` with path params | `GET /users/{id}` | **Resource Template** |
51
- | `GET` without path params | `GET /stats` | **Resource** |
52
- | `POST`, `PUT`, `PATCH`, `DELETE`, etc. | `POST /users` | **Tool** |
53
-
54
- Interally, FastMCP uses an ordered list of `RouteMap` objects to determine how to map OpenAPI routes to various MCP component types.
55
 
56
  Each `RouteMap` specifies a combination of methods, patterns, and tags, as well as a corresponding MCP component type. Each OpenAPI route is checked against each `RouteMap` in order, and the first one that matches every criteria is used to determine its converted MCP type. A special type, `EXCLUDE`, can be used to exclude routes from the MCP server entirely.
57
 
@@ -60,33 +52,14 @@ Each `RouteMap` specifies a combination of methods, patterns, and tags, as well
60
  - **Tags**: A set of OpenAPI tags that must all be present. An empty set (`{}`) means no tag filtering, so the route matches regardless of its tags.
61
  - **MCP type**: What MCP component type to create (`TOOL`, `RESOURCE`, `RESOURCE_TEMPLATE`, or `EXCLUDE`)
62
 
63
- To illustrate this in practice, here are FastMCP's default rules as a list of `RouteMap` objects:
64
 
65
  ```python
66
  from fastmcp.server.openapi import RouteMap, MCPType
67
 
68
  DEFAULT_ROUTE_MAPPINGS = [
69
-
70
- # GET with path parameters → ResourceTemplate
71
- RouteMap(
72
- methods=["GET"],
73
- pattern=r".*\{.*\}.*",
74
- mcp_type=MCPType.RESOURCE_TEMPLATE
75
- ),
76
-
77
- # GET without path parameters → Resource
78
- RouteMap(
79
- methods=["GET"],
80
- pattern=r".*",
81
- mcp_type=MCPType.RESOURCE
82
- ),
83
-
84
- # All other methods → Tool
85
- RouteMap(
86
- methods=["*"],
87
- pattern=r".*",
88
- mcp_type=MCPType.TOOL
89
- ),
90
  ]
91
  ```
92
 
@@ -94,20 +67,28 @@ DEFAULT_ROUTE_MAPPINGS = [
94
 
95
  When creating your FastMCP server, you can customize routing behavior by providing your own list of `RouteMap` objects. Your custom maps are processed before the default route maps, and routes will be assigned to the first matching custom map.
96
 
97
- For example, the following simple rule will treat every OpenAPI route as a tool:
98
 
99
- ```python {7}
100
  from fastmcp import FastMCP
101
  from fastmcp.server.openapi import RouteMap, MCPType
102
 
 
 
 
 
 
 
 
 
103
  mcp = FastMCP.from_openapi(
104
  ...,
105
- route_maps=[
106
- RouteMap(mcp_type=MCPType.TOOL),
107
- ],
108
  )
109
  ```
110
 
 
 
111
  Here is a more complete example that uses custom route maps to convert all `GET` endpoints under `/analytics/` to tools while excluding all admin endpoints and all routes tagged "internal". All other routes will be handled by the default rules:
112
 
113
  ```python
 
41
 
42
  ## Route Mapping
43
 
44
+ By default, FastMCP converts **every endpoint** in your OpenAPI specification into an MCP **Tool**. This provides a simple, predictable starting point that ensures all your API's functionality is immediately available to the vast majority of LLM clients which only support MCP tools.
45
 
46
+ While this is a pragmatic default for maximum compatibility, you can easily customize this behavior. Internally, FastMCP uses an ordered list of `RouteMap` objects to determine how to map OpenAPI routes to various MCP component types.
 
 
 
 
 
 
 
 
 
47
 
48
  Each `RouteMap` specifies a combination of methods, patterns, and tags, as well as a corresponding MCP component type. Each OpenAPI route is checked against each `RouteMap` in order, and the first one that matches every criteria is used to determine its converted MCP type. A special type, `EXCLUDE`, can be used to exclude routes from the MCP server entirely.
49
 
 
52
  - **Tags**: A set of OpenAPI tags that must all be present. An empty set (`{}`) means no tag filtering, so the route matches regardless of its tags.
53
  - **MCP type**: What MCP component type to create (`TOOL`, `RESOURCE`, `RESOURCE_TEMPLATE`, or `EXCLUDE`)
54
 
55
+ Here is FastMCP's default rule:
56
 
57
  ```python
58
  from fastmcp.server.openapi import RouteMap, MCPType
59
 
60
  DEFAULT_ROUTE_MAPPINGS = [
61
+ # All routes become tools
62
+ RouteMap(mcp_type=MCPType.TOOL),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  ]
64
  ```
65
 
 
67
 
68
  When creating your FastMCP server, you can customize routing behavior by providing your own list of `RouteMap` objects. Your custom maps are processed before the default route maps, and routes will be assigned to the first matching custom map.
69
 
70
+ For example, prior to FastMCP 2.8.0, GET requests were automatically mapped to `Resource` and `ResourceTemplate` components based on whether they had path parameters. (This was changed solely for client compatibility reasons.) You can restore this behavior by providing custom route maps:
71
 
72
+ ```python {2, 5-10}
73
  from fastmcp import FastMCP
74
  from fastmcp.server.openapi import RouteMap, MCPType
75
 
76
+ # Restore pre-2.8.0 semantic mapping
77
+ semantic_maps = [
78
+ # GET requests with path parameters become ResourceTemplates
79
+ RouteMap(methods=["GET"], pattern=r".*\{.*\}.*", mcp_type=MCPType.RESOURCE_TEMPLATE),
80
+ # All other GET requests become Resources
81
+ RouteMap(methods=["GET"], pattern=r".*", mcp_type=MCPType.RESOURCE),
82
+ ]
83
+
84
  mcp = FastMCP.from_openapi(
85
  ...,
86
+ route_maps=semantic_maps,
 
 
87
  )
88
  ```
89
 
90
+ With these maps, `GET` requests are handled semantically, and all other methods (`POST`, `PUT`, etc.) will fall through to the default rule and become `Tool`s.
91
+
92
  Here is a more complete example that uses custom route maps to convert all `GET` endpoints under `/analytics/` to tools while excluding all admin endpoints and all routes tagged "internal". All other routes will be handled by the default rules:
93
 
94
  ```python
docs/servers/prompts.mdx CHANGED
@@ -147,7 +147,32 @@ def data_analysis_prompt(
147
  - **`name`**: Sets the explicit prompt name exposed via MCP.
148
  - **`description`**: Provides the description exposed via MCP. If set, the function's docstring is ignored for this purpose.
149
  - **`tags`**: A set of strings used to categorize the prompt. Clients *might* use tags to filter or group available prompts.
 
 
150
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  ### Asynchronous Prompts
152
 
153
  FastMCP seamlessly supports both standard (`def`) and asynchronous (`async def`) functions as prompts.
@@ -191,6 +216,8 @@ async def generate_report_request(report_type: str, ctx: Context) -> str:
191
 
192
  For full documentation on the Context object and all its capabilities, see the [Context documentation](/servers/context).
193
 
 
 
194
  ## Server Behavior
195
 
196
  ### Duplicate Prompts
 
147
  - **`name`**: Sets the explicit prompt name exposed via MCP.
148
  - **`description`**: Provides the description exposed via MCP. If set, the function's docstring is ignored for this purpose.
149
  - **`tags`**: A set of strings used to categorize the prompt. Clients *might* use tags to filter or group available prompts.
150
+ - **`enabled`**: A boolean to enable or disable the prompt (defaults to `True`). See [Disabling Prompts](#disabling-prompts) for more information.
151
+ ### Disabling Prompts
152
 
153
+ <VersionBadge version="2.8.0" />
154
+
155
+ You can control the visibility and availability of prompts by enabling or disabling them. Disabled prompts will not appear in the list of available prompts, and attempting to call a disabled prompt will result in an "Unknown prompt" error.
156
+
157
+ By default, all prompts are enabled. You can disable a prompt upon creation using the `enabled` parameter in the decorator:
158
+
159
+ ```python
160
+ @mcp.prompt(enabled=False)
161
+ def experimental_prompt():
162
+ """This prompt is not ready for use."""
163
+ return "This is an experimental prompt."
164
+ ```
165
+
166
+ You can also toggle a prompt's state programmatically after it has been created:
167
+
168
+ ```python
169
+ @mcp.prompt
170
+ def seasonal_prompt(): return "Happy Holidays!"
171
+
172
+ # Disable and re-enable the prompt
173
+ seasonal_prompt.disable()
174
+ seasonal_prompt.enable()
175
+ ```
176
  ### Asynchronous Prompts
177
 
178
  FastMCP seamlessly supports both standard (`def`) and asynchronous (`async def`) functions as prompts.
 
216
 
217
  For full documentation on the Context object and all its capabilities, see the [Context documentation](/servers/context).
218
 
219
+
220
+
221
  ## Server Behavior
222
 
223
  ### Duplicate Prompts
docs/servers/resources.mdx CHANGED
@@ -94,6 +94,33 @@ def get_application_status() -> dict:
94
  - **`description`**: Explanation of the resource (defaults to docstring).
95
  - **`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).
96
  - **`tags`**: A set of strings for categorization, potentially used by clients for filtering.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
  ### Accessing MCP Context
99
 
 
94
  - **`description`**: Explanation of the resource (defaults to docstring).
95
  - **`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).
96
  - **`tags`**: A set of strings for categorization, potentially used by clients for filtering.
97
+ - **`enabled`**: A boolean to enable or disable the resource (defaults to `True`). See [Disabling Resources](#disabling-resources) for more information.
98
+
99
+ ### Disabling Resources
100
+
101
+ <VersionBadge version="2.8.0" />
102
+
103
+ You can control the visibility and availability of resources and templates by enabling or disabling them. Disabled resources will not appear in the list of available resources or templates, and attempting to read a disabled resource will result in an "Unknown resource" error.
104
+
105
+ By default, all resources are enabled. You can disable a resource upon creation using the `enabled` parameter in the decorator:
106
+
107
+ ```python
108
+ @mcp.resource("data://secret", enabled=False)
109
+ def get_secret_data():
110
+ """This resource is currently disabled."""
111
+ return "Secret data"
112
+ ```
113
+
114
+ You can also toggle a resource's state programmatically after it has been created:
115
+
116
+ ```python
117
+ @mcp.resource("data://config")
118
+ def get_config(): return {"version": 1}
119
+
120
+ # Disable and re-enable the resource
121
+ get_config.disable()
122
+ get_config.enable()
123
+ ```
124
 
125
  ### Accessing MCP Context
126
 
docs/servers/tools.mdx CHANGED
@@ -169,27 +169,58 @@ def search_products_implementation(query: str, category: str | None = None) -> l
169
 
170
  - **`name`**: Sets the explicit tool name exposed via MCP.
171
  - **`description`**: Provides the description exposed via MCP. If set, the function's docstring is ignored for this purpose.
172
- - **`tags`**: A set of strings used to categorize the tool. Clients *might* use tags to filter or group available tools.
 
 
173
 
 
174
 
175
- - **`exclude_args`**:
176
- <VersionBadge version="2.6.0" />
177
- A list of argument names to exclude from the tool schema shown to the LLM. This is useful for arguments that are injected at runtime (such as `state`, `user_id`, or credentials) and should not be exposed to the LLM or client. Only arguments with default values can be excluded; attempting to exclude a required argument will raise an error.
178
-
179
 
180
- Example:
181
 
182
- ```python
183
- @mcp.tool(
184
- name="get_user_details",
185
- exclude_args=["user_id"]
186
- )
187
- def get_user_details(user_id: str = None) -> str:
188
- # user_id will be injected by the server, not provided by the LLM
189
- ...
190
- ```
191
 
192
- With this configuration, `user_id` will not appear in the tool's parameter schema, but can still be set by the server or framework at runtime.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
 
194
  ### Async Tools
195
 
 
169
 
170
  - **`name`**: Sets the explicit tool name exposed via MCP.
171
  - **`description`**: Provides the description exposed via MCP. If set, the function's docstring is ignored for this purpose.
172
+ - **`tags`**: A set of strings to categorize the tool. Clients *might* use tags to filter or group available tools.
173
+ - **`enabled`**: A boolean to enable or disable the tool (defaults to `True`). See [Disabling Tools](#disabling-tools) for more information.
174
+ - **`exclude_args`**: A list of argument names to exclude from the tool schema shown to the LLM. See [Excluding Arguments](#excluding-arguments) for more information.
175
 
176
+ ### Excluding Arguments
177
 
178
+ <VersionBadge version="2.6.0" />
 
 
 
179
 
180
+ You can exclude certain arguments from the tool schema shown to the LLM. This is useful for arguments that are injected at runtime (such as `state`, `user_id`, or credentials) and should not be exposed to the LLM or client. Only arguments with default values can be excluded; attempting to exclude a required argument will raise an error.
181
 
182
+ Example:
 
 
 
 
 
 
 
 
183
 
184
+ ```python
185
+ @mcp.tool(
186
+ name="get_user_details",
187
+ exclude_args=["user_id"]
188
+ )
189
+ def get_user_details(user_id: str = None) -> str:
190
+ # user_id will be injected by the server, not provided by the LLM
191
+ ...
192
+ ```
193
+
194
+ With this configuration, `user_id` will not appear in the tool's parameter schema, but can still be set by the server or framework at runtime.
195
+
196
+ For more complex tool transformations, see [Transforming Tools](/patterns/tool-transformation).
197
+
198
+ ### Disabling Tools
199
+
200
+ <VersionBadge version="2.8.0" />
201
+
202
+ You can control the visibility and availability of tools by enabling or disabling them. This is useful for feature flagging, maintenance, or dynamically changing the toolset available to a client. Disabled tools will not appear in the list of available tools returned by `list_tools`, and attempting to call a disabled tool will result in an "Unknown tool" error, just as if the tool did not exist.
203
+
204
+ By default, all tools are enabled. You can disable a tool upon creation using the `enabled` parameter in the decorator:
205
+
206
+ ```python
207
+ @mcp.tool(enabled=False)
208
+ def maintenance_tool():
209
+ """This tool is currently under maintenance."""
210
+ return "This tool is disabled."
211
+ ```
212
+
213
+ You can also toggle a tool's state programmatically after it has been created:
214
+
215
+ ```python
216
+ @mcp.tool
217
+ def dynamic_tool():
218
+ return "I am a dynamic tool."
219
+
220
+ # Disable and re-enable the tool
221
+ dynamic_tool.disable()
222
+ dynamic_tool.enable()
223
+ ```
224
 
225
  ### Async Tools
226
 
docs/tutorials/create-mcp-server.mdx ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: "How to Create an MCP Server in Python"
3
+ sidebarTitle: "Creating an MCP Server"
4
+ description: "A step-by-step guide to building a Model Context Protocol (MCP) server using Python and FastMCP, from basic tools to dynamic resources."
5
+ icon: server
6
+ ---
7
+
8
+ So you want to build a Model Context Protocol (MCP) server in Python. The goal is to create a service that can provide tools and data to AI models like Claude, Gemini, or others that support the protocol. While the [MCP specification](https://modelcontextprotocol.io/specification/) is powerful, implementing it from scratch involves a lot of boilerplate: handling JSON-RPC, managing session state, and correctly formatting requests and responses.
9
+
10
+ This is where **FastMCP** comes in. It's a high-level framework that handles all the protocol complexities for you, letting you focus on what matters: writing the Python functions that power your server.
11
+
12
+ This guide will walk you through creating a fully-featured MCP server from scratch using FastMCP.
13
+
14
+ <Tip>
15
+ Every code block in this tutorial is a complete, runnable example. You can copy and paste it into a file and run it, or paste it directly into a Python REPL like IPython to try it out.
16
+ </Tip>
17
+
18
+ ### Prerequisites
19
+
20
+ Make sure you have FastMCP installed. If not, follow the [installation guide](/getting-started/installation).
21
+
22
+ ```bash
23
+ pip install fastmcp
24
+ ```
25
+
26
+
27
+ ## Step 1: Create the Basic Server
28
+
29
+ Every FastMCP application starts with an instance of the `FastMCP` class. This object acts as the container for all your tools and resources.
30
+
31
+ Create a new file called `my_mcp_server.py`:
32
+
33
+ ```python my_mcp_server.py
34
+ from fastmcp import FastMCP
35
+
36
+ # Create a server instance with a descriptive name
37
+ mcp = FastMCP(name="My First MCP Server")
38
+ ```
39
+
40
+ That's it! You have a valid (though empty) MCP server. Now, let's add some functionality.
41
+
42
+ ## Step 2: Add a Tool
43
+
44
+ Tools are functions that an LLM can execute. Let's create a simple tool that adds two numbers.
45
+
46
+ To do this, simply write a standard Python function and decorate it with `@mcp.tool`.
47
+
48
+ ```python my_mcp_server.py {5-8}
49
+ from fastmcp import FastMCP
50
+
51
+ mcp = FastMCP(name="My First MCP Server")
52
+
53
+ @mcp.tool
54
+ def add(a: int, b: int) -> int:
55
+ """Adds two integer numbers together."""
56
+ return a + b
57
+ ```
58
+
59
+ FastMCP automatically handles the rest:
60
+ - **Tool Name:** It uses the function name (`add`) as the tool's name.
61
+ - **Description:** It uses the function's docstring as the tool's description for the LLM.
62
+ - **Schema:** It inspects the type hints (`a: int`, `b: int`) to generate a JSON schema for the inputs.
63
+
64
+ This is the core philosophy of FastMCP: **write Python, not protocol boilerplate.**
65
+
66
+ ## Step 3: Expose Data with Resources
67
+
68
+ Resources provide read-only data to the LLM. You can define a resource by decorating a function with `@mcp.resource`, providing a unique URI.
69
+
70
+ Let's expose a simple configuration dictionary as a resource.
71
+
72
+ ```python my_mcp_server.py {10-13}
73
+ from fastmcp import FastMCP
74
+
75
+ mcp = FastMCP(name="My First MCP Server")
76
+
77
+ @mcp.tool
78
+ def add(a: int, b: int) -> int:
79
+ """Adds two integer numbers together."""
80
+ return a + b
81
+
82
+ @mcp.resource("resource://config")
83
+ def get_config() -> dict:
84
+ """Provides the application's configuration."""
85
+ return {"version": "1.0", "author": "MyTeam"}
86
+ ```
87
+
88
+ When a client requests the URI `resource://config`, FastMCP will execute the `get_config` function and return its output (serialized as JSON) to the client. The function is only called when the resource is requested, enabling lazy-loading of data.
89
+
90
+ ## Step 4: Generate Dynamic Content with Resource Templates
91
+
92
+ Sometimes, you need to generate resources based on parameters. This is what **Resource Templates** are for. You define them using the same `@mcp.resource` decorator but with placeholders in the URI.
93
+
94
+ Let's create a template that provides a personalized greeting.
95
+
96
+ ```python my_mcp_server.py {15-17}
97
+ from fastmcp import FastMCP
98
+
99
+ mcp = FastMCP(name="My First MCP Server")
100
+
101
+ @mcp.tool
102
+ def add(a: int, b: int) -> int:
103
+ """Adds two integer numbers together."""
104
+ return a + b
105
+
106
+ @mcp.resource("resource://config")
107
+ def get_config() -> dict:
108
+ """Provides the application's configuration."""
109
+ return {"version": "1.0", "author": "MyTeam"}
110
+
111
+ @mcp.resource("greetings://{name}")
112
+ def personalized_greeting(name: str) -> str:
113
+ """Generates a personalized greeting for the given name."""
114
+ return f"Hello, {name}! Welcome to the MCP server."
115
+ ```
116
+
117
+ Now, clients can request dynamic URIs:
118
+ - `greetings://Ford` will call `personalized_greeting(name="Ford")`.
119
+ - `greetings://Marvin` will call `personalized_greeting(name="Marvin")`.
120
+
121
+ FastMCP automatically maps the `{name}` placeholder in the URI to the `name` parameter in your function.
122
+
123
+ ## Step 5: Run the Server
124
+
125
+ To make your server executable, add a `__main__` block to your script that calls `mcp.run()`.
126
+
127
+ ```python my_mcp_server.py {19-20}
128
+ from fastmcp import FastMCP
129
+
130
+ mcp = FastMCP(name="My First MCP Server")
131
+
132
+ @mcp.tool
133
+ def add(a: int, b: int) -> int:
134
+ """Adds two integer numbers together."""
135
+ return a + b
136
+
137
+ @mcp.resource("resource://config")
138
+ def get_config() -> dict:
139
+ """Provides the application's configuration."""
140
+ return {"version": "1.0", "author": "MyTeam"}
141
+
142
+ @mcp.resource("greetings://{name}")
143
+ def personalized_greeting(name: str) -> str:
144
+ """Generates a personalized greeting for the given name."""
145
+ return f"Hello, {name}! Welcome to the MCP server."
146
+
147
+ if __name__ == "__main__":
148
+ mcp.run()
149
+ ```
150
+
151
+ Now you can run your server from the command line:
152
+ ```bash
153
+ python my_mcp_server.py
154
+ ```
155
+ This starts the server using the default **STDIO transport**, which is how clients like Claude Desktop communicate with local servers. To learn about other transports, like HTTP, see the [Running Your Server](/deployment/running-server) guide.
156
+
157
+ ## The Complete Server
158
+
159
+ Here is the full code for `my_mcp_server.py` (click to expand):
160
+
161
+ ```python my_mcp_server.py [expandable]
162
+ from fastmcp import FastMCP
163
+
164
+ # 1. Create the server
165
+ mcp = FastMCP(name="My First MCP Server")
166
+
167
+ # 2. Add a tool
168
+ @mcp.tool
169
+ def add(a: int, b: int) -> int:
170
+ """Adds two integer numbers together."""
171
+ return a + b
172
+
173
+ # 3. Add a static resource
174
+ @mcp.resource("resource://config")
175
+ def get_config() -> dict:
176
+ """Provides the application's configuration."""
177
+ return {"version": "1.0", "author": "MyTeam"}
178
+
179
+ # 4. Add a resource template for dynamic content
180
+ @mcp.resource("greetings://{name}")
181
+ def personalized_greeting(name: str) -> str:
182
+ """Generates a personalized greeting for the given name."""
183
+ return f"Hello, {name}! Welcome to the MCP server."
184
+
185
+ # 5. Make the server runnable
186
+ if __name__ == "__main__":
187
+ mcp.run()
188
+ ```
189
+
190
+ ## Next Steps
191
+
192
+ You've successfully built an MCP server! From here, you can explore more advanced topics:
193
+
194
+ - [**Tools in Depth**](/servers/tools): Learn about asynchronous tools, error handling, and custom return types.
195
+ - [**Resources & Templates**](/servers/resources): Discover different resource types, including files and HTTP endpoints.
196
+ - [**Prompts**](/servers/prompts): Create reusable prompt templates for your LLM.
197
+ - [**Running Your Server**](/deployment/running-server): Deploy your server with different transports like HTTP.
198
+
docs/tutorials/mcp.mdx ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: "What is the Model Context Protocol (MCP)?"
3
+ sidebarTitle: "What is MCP?"
4
+ description: "An introduction to the core concepts of the Model Context Protocol (MCP), explaining what it is, why it's useful, and how it works."
5
+ icon: "diagram-project"
6
+ ---
7
+
8
+ The Model Context Protocol (MCP) is an open standard designed to solve a fundamental problem in AI development: how can Large Language Models (LLMs) reliably and securely interact with external tools, data, and services?
9
+
10
+ It's the **bridge between the probabilistic, non-deterministic world of AI and the deterministic, reliable world of your code and data.**
11
+
12
+ While you could build a custom REST API for your LLM, MCP provides a specialized, standardized "port" for AI-native communication. Think of it as **USB-C for AI**: a single, well-defined interface for connecting any compliant LLM to any compliant tool or data source.
13
+
14
+ This guide provides a high-level overview of the protocol itself. We'll use **FastMCP**, the leading Python framework for MCP, to illustrate the concepts with simple code examples.
15
+
16
+ ## Why Do We Need a Protocol?
17
+
18
+ With countless APIs already in existence, the most common question is: "Why do we need another one?"
19
+
20
+ The answer lies in **standardization**. The AI ecosystem is fragmented. Every model provider has its own way of defining and calling tools. MCP's goal is to create a common language that offers several key advantages:
21
+
22
+ 1. **Interoperability:** Build one MCP server, and it can be used by any MCP-compliant client (Claude, Gemini, OpenAI, custom agents, etc.) without custom integration code. This is the protocol's most important promise.
23
+ 2. **Discoverability:** Clients can dynamically ask a server what it's capable of at runtime. They receive a structured, machine-readable "menu" of tools and resources.
24
+ 3. **Security & Safety:** MCP provides a clear, sandboxed boundary. An LLM can't execute arbitrary code on your server; it can only *request* to run the specific, typed, and validated functions you explicitly expose.
25
+ 4. **Composability:** You can build small, specialized MCP servers and combine them to create powerful, complex applications.
26
+
27
+ ## Core MCP Components
28
+
29
+ An MCP server exposes its capabilities through three primary components: Tools, Resources, and Prompts.
30
+
31
+ ### Tools: Executable Actions
32
+
33
+ Tools are functions that the LLM can ask the server to execute. They are the action-oriented part of MCP.
34
+
35
+ In the spirit of a REST API, you can think of **Tools as being like `POST` requests.** They are used to *perform an action*, *change state*, or *trigger a side effect*, like sending an email, adding a user to a database, or making a calculation.
36
+
37
+ With FastMCP, creating a tool is as simple as decorating a Python function.
38
+
39
+ ```python
40
+ from fastmcp import FastMCP
41
+
42
+ mcp = FastMCP()
43
+
44
+ # This function is now an MCP tool named "get_weather"
45
+ @mcp.tool
46
+ def get_weather(city: str) -> dict:
47
+ """Gets the current weather for a specific city."""
48
+ # In a real app, this would call a weather API
49
+ return {"city": city, "temperature": "72F", "forecast": "Sunny"}
50
+ ```
51
+
52
+ [**Learn more about Tools →**](/servers/tools)
53
+
54
+ ### Resources: Read-Only Data
55
+
56
+ Resources are data sources that the LLM can read. They are used to load information into the LLM's context, providing it with knowledge it doesn't have from its training data.
57
+
58
+ Following the REST API analogy, **Resources are like `GET` requests.** Their purpose is to *retrieve information* idempotently, ideally without causing side effects. A resource can be anything from a static text file to a dynamic piece of data from a database. Each resource is identified by a unique URI.
59
+
60
+ ```python
61
+ from fastmcp import FastMCP
62
+
63
+ mcp = FastMCP()
64
+
65
+ # This function provides a resource at the URI "system://status"
66
+ @mcp.resource("system://status")
67
+ def get_system_status() -> dict:
68
+ """Returns the current operational status of the service."""
69
+ return {"status": "all systems normal"}
70
+ ```
71
+
72
+ #### Resource Templates
73
+
74
+ You can also create **Resource Templates** for dynamic data. A client could request `users://42/profile` to get the profile for a specific user.
75
+
76
+ ```python
77
+ from fastmcp import FastMCP
78
+
79
+ mcp = FastMCP()
80
+
81
+ # This template provides user data for any given user ID
82
+ @mcp.resource("users://{user_id}/profile")
83
+ def get_user_profile(user_id: str) -> dict:
84
+ """Returns the profile for a specific user."""
85
+ # Fetch user from a database...
86
+ return {"id": user_id, "name": "Zaphod Beeblebrox"}
87
+ ```
88
+
89
+ [**Learn more about Resources & Templates →**](/servers/resources)
90
+
91
+ ### Prompts: Reusable Instructions
92
+
93
+ Prompts are reusable, parameterized message templates. They provide a way to define consistent, structured instructions that a client can request to guide the LLM's behavior for a specific task.
94
+
95
+ ```python
96
+ from fastmcp import FastMCP
97
+
98
+ mcp = FastMCP()
99
+
100
+ @mcp.prompt
101
+ def summarize_text(text_to_summarize: str) -> str:
102
+ """Creates a prompt asking the LLM to summarize a piece of text."""
103
+ return f"""
104
+ Please provide a concise, one-paragraph summary of the following text:
105
+
106
+ {text_to_summarize}
107
+ """
108
+ ```
109
+
110
+ [**Learn more about Prompts →**](/servers/prompts)
111
+
112
+ ## Advanced Capabilities
113
+
114
+ Beyond the core components, MCP also supports more advanced interaction patterns, such as a server requesting that the *client's* LLM generate a completion (known as **sampling**), or a server sending asynchronous **notifications** to a client. These features enable more complex, bidirectional workflows and are fully supported by FastMCP.
115
+
116
+ ## Next Steps
117
+
118
+ Now that you understand the core concepts of the Model Context Protocol, you're ready to start building. The best place to begin is our step-by-step tutorial.
119
+
120
+ [**Tutorial: How to Create an MCP Server in Python →**](/tutorials/create-mcp-server)
docs/tutorials/rest-api.mdx ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: "How to Connect an LLM to a REST API"
3
+ sidebarTitle: "Connect LLMs to REST APIs"
4
+ description: "A step-by-step guide to making any REST API with an OpenAPI spec available to LLMs using FastMCP."
5
+ icon: "plug"
6
+ ---
7
+
8
+ You've built a powerful REST API, and now you want your LLM to be able to use it. Manually writing a wrapper function for every single endpoint is tedious, error-prone, and hard to maintain.
9
+
10
+ This is where **FastMCP** shines. If your API has an OpenAPI (or Swagger) specification, FastMCP can automatically convert your entire API into a fully-featured MCP server, making every endpoint available as a secure, typed tool for your AI model.
11
+
12
+ This guide will walk you through converting a public REST API into an MCP server in just a few lines of code.
13
+
14
+ <Tip>
15
+ Every code block in this tutorial is a complete, runnable example. You can copy and paste it into a file and run it, or paste it directly into a Python REPL like IPython to try it out.
16
+ </Tip>
17
+
18
+ ### Prerequisites
19
+
20
+ Make sure you have FastMCP installed. If not, follow the [installation guide](/getting-started/installation).
21
+
22
+ ```bash
23
+ pip install fastmcp
24
+ ```
25
+
26
+ ## Step 1: Choose a Target API
27
+
28
+ For this tutorial, we'll use the [JSONPlaceholder API](https://jsonplaceholder.typicode.com/), a free, fake online REST API for testing and prototyping. It's perfect because it's simple and has a public OpenAPI specification.
29
+
30
+ - **API Base URL:** `https://jsonplaceholder.typicode.com`
31
+ - **OpenAPI Spec URL:** We'll use a community-provided spec for it.
32
+
33
+ ## Step 2: Create the MCP Server
34
+
35
+ Now for the magic. We'll use `FastMCP.from_openapi`. This method takes an `httpx.AsyncClient` configured for your API and its OpenAPI specification, and automatically converts **every endpoint** into a callable MCP `Tool`.
36
+
37
+ <Tip>
38
+ Learn more about working with OpenAPI specs in the [OpenAPI integration docs](/servers/openapi).
39
+ </Tip>
40
+
41
+ <Note>
42
+ For this tutorial, we'll use a simplified OpenAPI spec directly in the code. In a real project, you would typically load the spec from a URL or local file.
43
+ </Note>
44
+
45
+ Create a file named `api_server.py`:
46
+
47
+ ```python api_server.py {31-35}
48
+ import httpx
49
+ from fastmcp import FastMCP
50
+
51
+ # Create an HTTP client for the target API
52
+ client = httpx.AsyncClient(base_url="https://jsonplaceholder.typicode.com")
53
+
54
+ # Define a simplified OpenAPI spec for JSONPlaceholder
55
+ openapi_spec = {
56
+ "openapi": "3.0.0",
57
+ "info": {"title": "JSONPlaceholder API", "version": "1.0"},
58
+ "paths": {
59
+ "/users": {
60
+ "get": {
61
+ "summary": "Get all users",
62
+ "operationId": "get_users",
63
+ "responses": {"200": {"description": "A list of users."}}
64
+ }
65
+ },
66
+ "/users/{id}": {
67
+ "get": {
68
+ "summary": "Get a user by ID",
69
+ "operationId": "get_user_by_id",
70
+ "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}],
71
+ "responses": {"200": {"description": "A single user."}}
72
+ }
73
+ }
74
+ }
75
+ }
76
+
77
+ # Create the MCP server from the OpenAPI spec
78
+ mcp = FastMCP.from_openapi(
79
+ openapi_spec=openapi_spec,
80
+ client=client,
81
+ name="JSONPlaceholder MCP Server"
82
+ )
83
+
84
+ if __name__ == "__main__":
85
+ mcp.run(transport="streamable-http", port=8000)
86
+ ```
87
+
88
+ And that's it! With just a few lines of code, you've created an MCP server that exposes the entire JSONPlaceholder API as a collection of tools.
89
+
90
+ ## Step 3: Test the Generated Server
91
+
92
+ Let's verify that our new MCP server works. We can use the `fastmcp.Client` to connect to it and inspect its tools.
93
+
94
+ <Tip>
95
+ Learn more about the FastMCP client in the [client docs](/clients/client).
96
+ </Tip>
97
+
98
+ Create a separate file, `api_client.py`:
99
+
100
+ ```python api_client.py {2, 6, 9, 16}
101
+ import asyncio
102
+ from fastmcp import Client
103
+
104
+ async def main():
105
+ # Connect to the MCP server we just created
106
+ async with Client("http://127.0.0.1:8000/mcp") as client:
107
+
108
+ # List the tools that were automatically generated
109
+ tools = await client.list_tools()
110
+ print("Generated Tools:")
111
+ for tool in tools:
112
+ print(f"- {tool.name}")
113
+
114
+ # Call one of the generated tools
115
+ print("\n\nCalling tool 'get_user_by_id'...")
116
+ user = await client.call_tool("get_user_by_id", {"id": 1})
117
+ print(f"Result:\n{user[0].text}")
118
+
119
+ if __name__ == "__main__":
120
+ asyncio.run(main())
121
+ ```
122
+
123
+ First, run your server:
124
+ ```bash
125
+ python api_server.py
126
+ ```
127
+
128
+ Then, in another terminal, run the client:
129
+ ```bash
130
+ python api_client.py
131
+ ```
132
+
133
+ You should see a list of generated tools (`get_users`, `get_user_by_id`) and the result of calling the `get_user_by_id` tool, which fetches data from the live JSONPlaceholder API.
134
+
135
+ ![](/assets/images/tutorial-rest-api-result.png)
136
+
137
+
138
+ ## Step 4: Customizing Route Maps
139
+
140
+ By default, FastMCP converts every API endpoint into an MCP `Tool`. This ensures maximum compatibility with contemporary LLM clients, many of which **only support the `tools` part of the MCP specification.**
141
+
142
+ However, for clients that support the full MCP spec, representing `GET` requests as `Resources` can be more semantically correct and efficient.
143
+
144
+ FastMCP allows users to customize this behavior using the concept of "route maps". A `RouteMap` is a mapping of an API route to an MCP type. FastMCP checks each API route against your custom maps in order. If a route matches a map, it's converted to the specified `mcp_type`. Any route that doesn't match your custom maps will fall back to the default behavior (becoming a `Tool`).
145
+
146
+ <Tip>
147
+ Learn more about route maps in the [OpenAPI integration docs](/servers/openapi#route-mapping).
148
+ </Tip>
149
+
150
+ Here’s how you can add custom route maps to turn `GET` requests into `Resources` and `ResourceTemplates` (if they have path parameters):
151
+
152
+ ```python api_server_with_resources.py {3, 37-42}
153
+ import httpx
154
+ from fastmcp import FastMCP
155
+ from fastmcp.server.openapi import RouteMap, MCPType
156
+
157
+
158
+ # Create an HTTP client for the target API
159
+ client = httpx.AsyncClient(base_url="https://jsonplaceholder.typicode.com")
160
+
161
+ # Define a simplified OpenAPI spec for JSONPlaceholder
162
+ openapi_spec = {
163
+ "openapi": "3.0.0",
164
+ "info": {"title": "JSONPlaceholder API", "version": "1.0"},
165
+ "paths": {
166
+ "/users": {
167
+ "get": {
168
+ "summary": "Get all users",
169
+ "operationId": "get_users",
170
+ "responses": {"200": {"description": "A list of users."}}
171
+ }
172
+ },
173
+ "/users/{id}": {
174
+ "get": {
175
+ "summary": "Get a user by ID",
176
+ "operationId": "get_user_by_id",
177
+ "parameters": [{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}],
178
+ "responses": {"200": {"description": "A single user."}}
179
+ }
180
+ }
181
+ }
182
+ }
183
+
184
+ # Create the MCP server with custom route mapping
185
+ mcp = FastMCP.from_openapi(
186
+ openapi_spec=openapi_spec,
187
+ client=client,
188
+ name="JSONPlaceholder MCP Server",
189
+ route_maps=[
190
+ # Map GET requests with path parameters (e.g., /users/{id}) to ResourceTemplate
191
+ RouteMap(methods=["GET"], pattern=r".*\{.*\}.*", mcp_type=MCPType.RESOURCE_TEMPLATE),
192
+ # Map all other GET requests to Resource
193
+ RouteMap(methods=["GET"], mcp_type=MCPType.RESOURCE),
194
+ ]
195
+ )
196
+
197
+ if __name__ == "__main__":
198
+ mcp.run(transport="streamable-http", port=8000)
199
+ ```
200
+ With this configuration:
201
+ - `GET /users/{id}` becomes a `ResourceTemplate`.
202
+ - `GET /users` becomes a `Resource`.
203
+ - Any `POST`, `PUT`, etc. endpoints would still become `Tools` by default.
docs/updates.mdx CHANGED
@@ -4,18 +4,140 @@ sidebarTitle: "Updates"
4
  icon: "sparkles"
5
  tag: "New!"
6
  ---
7
- <Update label="FastMCP 2.6" description="June 6, 2025">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
 
10
 
 
11
  <Card
12
  title="Blast Auth with FastMCP 2.6" href="https://www.jlowin.dev/blog/fastmcp-2-6"
13
  img="https://www.jlowin.dev/_image?href=%2F_astro%2Fhero.Bsu8afiw.png&w=1000&h=500&f=webp"
14
- cta="Read more"
15
- arrow="false"
16
- horizontal="true">
17
  FastMCP 2.6 is here!
18
 
19
  This release introduces first-class authentication for MCP servers and clients, including pragmatic Bearer token support and seamless OAuth 2.1 integration. This release aligns with how major AI platforms are adopting MCP today, making it easier than ever to securely connect your tools to real-world AI models. Dive into the update and secure your stack with minimal friction.
20
  </Card>
21
  </Update>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  icon: "sparkles"
5
  tag: "New!"
6
  ---
7
+ <Update label="FastMCP 2.7" description="June 6, 2025" tags={["Releases"]}>
8
+ <Card
9
+ title="FastMCP 2.7: Pare Programming" href="https://github.com/jlowin/fastmcp/releases/tag/v2.7.0"
10
+ img="assets/updates/release-2-7.png"
11
+ cta="Read the release notes"
12
+ >
13
+ FastMCP 2.7 has been released!
14
+
15
+ Most notably, it introduces the highly requested (and Pythonic) "naked" decorator usage:
16
+
17
+ ```python {3}
18
+ mcp = FastMCP()
19
+
20
+ @mcp.tool
21
+ def add(a: int, b: int) -> int:
22
+ return a + b
23
+ ```
24
+
25
+ In addition, decorators now return the objects they create, instead of the decorated function. This is an important usability enhancement.
26
+
27
+ The bulk of the update is focused on improving the FastMCP internals, including a few breaking internal changes to private APIs. A number of functions that have clung on since 1.0 are now deprecated.
28
+ </Card>
29
+ </Update>
30
 
31
 
32
 
33
+ <Update label="FastMCP 2.6" description="June 2, 2025" tags={["Releases", "Blog Posts"]}>
34
  <Card
35
  title="Blast Auth with FastMCP 2.6" href="https://www.jlowin.dev/blog/fastmcp-2-6"
36
  img="https://www.jlowin.dev/_image?href=%2F_astro%2Fhero.Bsu8afiw.png&w=1000&h=500&f=webp"
37
+ cta="Read more"
38
+ >
 
39
  FastMCP 2.6 is here!
40
 
41
  This release introduces first-class authentication for MCP servers and clients, including pragmatic Bearer token support and seamless OAuth 2.1 integration. This release aligns with how major AI platforms are adopting MCP today, making it easier than ever to securely connect your tools to real-world AI models. Dive into the update and secure your stack with minimal friction.
42
  </Card>
43
  </Update>
44
+
45
+ <Update description="May 21, 2025" label="Vibe-Testing" tags={["Blog Posts", "Tutorials"]}>
46
+ <Card
47
+ title="Stop Vibe-Testing Your MCP Server"
48
+ href="https://www.jlowin.dev/blog/stop-vibe-testing-mcp-servers"
49
+ img="https://www.jlowin.dev/_image?href=%2F_astro%2Fhero.BUPy9I9c.png&w=1000&h=500&f=webp"
50
+ cta="Read more"
51
+ >
52
+
53
+ Your tests are bad and you should feel bad.
54
+
55
+ Stop vibe-testing your MCP server through LLM guesswork. FastMCP 2.0 introduces in-memory testing for fast, deterministic, and fully Pythonic validation of your MCP logic—no network, no subprocesses, no vibes.
56
+
57
+ </Card>
58
+ </Update>
59
+
60
+
61
+ <Update description="May 8, 2025" label="10,000 Stars" tags={["Blog Posts"]}>
62
+ <Card
63
+ title="Reflecting on FastMCP at 10k stars 🌟"
64
+ href="https://www.jlowin.dev/blog/fastmcp-2-10k-stars"
65
+ img="https://www.jlowin.dev/_image?href=%2F_astro%2Fhero.Cnvci9Q_.png&w=1000&h=500&f=webp"
66
+ cta="Read more"
67
+ >
68
+
69
+ In just six weeks since its relaunch, FastMCP has surpassed 10,000 GitHub stars—becoming the fastest-growing OSS project in our orbit. What started as a personal itch has become the backbone of Python-based MCP servers, powering a rapidly expanding ecosystem. While the protocol itself evolves, FastMCP continues to lead with clarity, developer experience, and opinionated tooling. Here’s to what’s next.
70
+
71
+ </Card>
72
+ </Update>
73
+
74
+ <Update description="May 8, 2025" label="FastMCP 2.3" tags={["Blog Posts", "Releases"]}>
75
+ <Card
76
+ title="Now Streaming: FastMCP 2.3"
77
+ href="https://www.jlowin.dev/blog/fastmcp-2-3-streamable-http"
78
+ img="https://www.jlowin.dev/_image?href=%2F_astro%2Fhero.M_hv6gEB.png&w=1000&h=500&f=webp"
79
+ cta="Read more"
80
+ >
81
+
82
+ FastMCP 2.3 introduces full support for Streamable HTTP, a modern alternative to SSE that simplifies MCP deployments over the web. It’s efficient, reliable, and now the default HTTP transport. Just run your server with transport="streamable-http" and connect clients via a standard URL—FastMCP handles the rest. No special setup required. This release makes deploying MCP servers easier and more portable than ever.
83
+
84
+ </Card>
85
+ </Update>
86
+
87
+ <Update description="April 23, 2025" label="Proxy Servers" tags={["Blog Posts", "Tutorials"]}>
88
+ <Card
89
+ title="MCP Proxy Servers with FastMCP 2.0"
90
+ href="https://www.jlowin.dev/blog/fastmcp-proxy"
91
+ img="https://www.jlowin.dev/_image?href=%2F_astro%2Frobot-hero.DpmAqgui.png&w=1000&h=500&f=webp"
92
+ cta="Read more"
93
+ >
94
+
95
+ Even AI needs a good travel adapter 🔌
96
+
97
+
98
+ FastMCP now supports proxying arbitrary MCP servers, letting you run a local FastMCP instance that transparently forwards requests to any remote or third-party server—regardless of transport. This enables transport bridging (e.g., stdio ⇄ SSE), simplified client configuration, and powerful gateway patterns. Proxies are fully composable with other FastMCP servers, letting you mount or import them just like local servers. Use `FastMCP.from_client()` to wrap any backend in a clean, Pythonic proxy.
99
+ </Card>
100
+ </Update>
101
+
102
+ <Update label="FastMCP 2.0" description="April 16, 2025" tags={["Releases", "Blog Posts"]}>
103
+ <Card
104
+ title="Introducing FastMCP 2.0 🚀"
105
+ href="https://www.jlowin.dev/blog/fastmcp-2"
106
+ img="https://www.jlowin.dev/_image?href=%2F_astro%2Fhero.DpbmGNrr.png&w=1000&h=500&f=webp"
107
+ cta="Read more"
108
+ >
109
+
110
+ This major release reimagines FastMCP as a full ecosystem platform, with powerful new features for composition, integration, and client interaction. You can now compose local and remote servers, proxy arbitrary MCP servers (with transport translation), and generate MCP servers from OpenAPI or FastAPI apps. A new client infrastructure supports advanced workflows like LLM sampling.
111
+
112
+ FastMCP 2.0 builds on the success of v1 with a cleaner, more flexible foundation—try it out today!
113
+ </Card>
114
+ </Update>
115
+
116
+
117
+
118
+ <Update label="Official SDK" description="December 3, 2024" tags={["Announcements"]}>
119
+ <Card
120
+ title="FastMCP is joining the official MCP Python SDK!"
121
+ href="https://bsky.app/profile/jlowin.dev/post/3lch4xk5cf22c"
122
+ icon="sparkles"
123
+ cta="Read the announcement"
124
+ >
125
+ FastMCP 1.0 will become part of the official MCP Python SDK!
126
+ </Card>
127
+ </Update>
128
+
129
+
130
+
131
+ <Update label="FastMCP 1.0" description="December 1, 2024" tags={["Releases", "Blog Posts"]}>
132
+ <Card
133
+ title="Introducing FastMCP 🚀"
134
+ href="https://www.jlowin.dev/blog/introducing-fastmcp"
135
+ img="https://www.jlowin.dev/_image?href=%2F_astro%2Ffastmcp.Bep7YlTw.png&w=1000&h=500&f=webp"
136
+ cta="Read more"
137
+ >
138
+ Because life's too short for boilerplate.
139
+
140
+ This is where it all started. FastMCP’s launch post introduced a clean, Pythonic way to build MCP servers without the protocol overhead. Just write functions; FastMCP handles the rest. What began as a weekend project quickly became the foundation of a growing ecosystem.
141
+ </Card>
142
+ </Update>
143
+
pyproject.toml CHANGED
@@ -114,3 +114,5 @@ extend-select = ["I", "UP"]
114
 
115
  [tool.ruff.lint.per-file-ignores]
116
  "__init__.py" = ["F401", "I001", "RUF013"]
 
 
 
114
 
115
  [tool.ruff.lint.per-file-ignores]
116
  "__init__.py" = ["F401", "I001", "RUF013"]
117
+ # allow imports not at the top of the file
118
+ "src/fastmcp/__init__.py" = ["E402"]
src/fastmcp/__init__.py CHANGED
@@ -1,6 +1,9 @@
1
  """FastMCP - An ergonomic MCP interface."""
2
 
3
  from importlib.metadata import version
 
 
 
4
 
5
  from fastmcp.server.server import FastMCP
6
  from fastmcp.server.context import Context
@@ -8,7 +11,7 @@ import fastmcp.server
8
 
9
  from fastmcp.client import Client
10
  from fastmcp.utilities.types import Image
11
- from . import client, settings
12
 
13
  __version__ = version("fastmcp")
14
  __all__ = [
 
1
  """FastMCP - An ergonomic MCP interface."""
2
 
3
  from importlib.metadata import version
4
+ from fastmcp.settings import Settings
5
+
6
+ settings = Settings()
7
 
8
  from fastmcp.server.server import FastMCP
9
  from fastmcp.server.context import Context
 
11
 
12
  from fastmcp.client import Client
13
  from fastmcp.utilities.types import Image
14
+ from . import client
15
 
16
  __version__ = version("fastmcp")
17
  __all__ = [
src/fastmcp/cli/cli.py CHANGED
@@ -18,6 +18,7 @@ from typer import Context, Exit
18
  import fastmcp
19
  from fastmcp.cli import claude
20
  from fastmcp.cli import run as run_module
 
21
  from fastmcp.utilities.logging import get_logger
22
 
23
  logger = get_logger("cli")
@@ -165,8 +166,8 @@ def dev(
165
 
166
  try:
167
  # Import server to get dependencies
168
- server = run_module.import_server(file, server_object)
169
- if hasattr(server, "dependencies") and server.dependencies is not None:
170
  with_packages = list(set(with_packages + server.dependencies))
171
 
172
  env_vars = {}
 
18
  import fastmcp
19
  from fastmcp.cli import claude
20
  from fastmcp.cli import run as run_module
21
+ from fastmcp.server.server import FastMCP
22
  from fastmcp.utilities.logging import get_logger
23
 
24
  logger = get_logger("cli")
 
166
 
167
  try:
168
  # Import server to get dependencies
169
+ server: FastMCP = run_module.import_server(file, server_object)
170
+ if server.dependencies is not None:
171
  with_packages = list(set(with_packages + server.dependencies))
172
 
173
  env_vars = {}
src/fastmcp/client/auth/oauth.py CHANGED
@@ -23,10 +23,10 @@ from mcp.shared.auth import (
23
  )
24
  from pydantic import AnyHttpUrl, ValidationError
25
 
 
26
  from fastmcp.client.oauth_callback import (
27
  create_oauth_callback_server,
28
  )
29
- from fastmcp.settings import settings as fastmcp_global_settings
30
  from fastmcp.utilities.http import find_available_port
31
  from fastmcp.utilities.logging import get_logger
32
 
 
23
  )
24
  from pydantic import AnyHttpUrl, ValidationError
25
 
26
+ from fastmcp import settings as fastmcp_global_settings
27
  from fastmcp.client.oauth_callback import (
28
  create_oauth_callback_server,
29
  )
 
30
  from fastmcp.utilities.http import find_available_port
31
  from fastmcp.utilities.logging import get_logger
32
 
src/fastmcp/client/client.py CHANGED
@@ -145,6 +145,7 @@ class Client(Generic[ClientTransportT]):
145
  progress_handler: ProgressHandler | None = None,
146
  timeout: datetime.timedelta | float | int | None = None,
147
  init_timeout: datetime.timedelta | float | int | None = None,
 
148
  auth: httpx.Auth | Literal["oauth"] | str | None = None,
149
  ):
150
  self.transport = cast(ClientTransportT, infer_transport(transport))
@@ -165,7 +166,7 @@ class Client(Generic[ClientTransportT]):
165
 
166
  # handle init handshake timeout
167
  if init_timeout is None:
168
- init_timeout = fastmcp.settings.settings.client_init_timeout
169
  if isinstance(init_timeout, datetime.timedelta):
170
  init_timeout = init_timeout.total_seconds()
171
  elif not init_timeout:
@@ -180,6 +181,7 @@ class Client(Generic[ClientTransportT]):
180
  "logging_callback": create_log_callback(log_handler),
181
  "message_handler": message_handler,
182
  "read_timeout_seconds": timeout,
 
183
  }
184
 
185
  if roots is not None:
 
145
  progress_handler: ProgressHandler | None = None,
146
  timeout: datetime.timedelta | float | int | None = None,
147
  init_timeout: datetime.timedelta | float | int | None = None,
148
+ client_info: mcp.types.Implementation | None = None,
149
  auth: httpx.Auth | Literal["oauth"] | str | None = None,
150
  ):
151
  self.transport = cast(ClientTransportT, infer_transport(transport))
 
166
 
167
  # handle init handshake timeout
168
  if init_timeout is None:
169
+ init_timeout = fastmcp.settings.client_init_timeout
170
  if isinstance(init_timeout, datetime.timedelta):
171
  init_timeout = init_timeout.total_seconds()
172
  elif not init_timeout:
 
181
  "logging_callback": create_log_callback(log_handler),
182
  "message_handler": message_handler,
183
  "read_timeout_seconds": timeout,
184
+ "client_info": client_info,
185
  }
186
 
187
  if roots is not None:
src/fastmcp/client/transports.py CHANGED
@@ -8,39 +8,25 @@ import sys
8
  import warnings
9
  from collections.abc import AsyncIterator, Callable
10
  from pathlib import Path
11
- from typing import (
12
- TYPE_CHECKING,
13
- Any,
14
- Literal,
15
- TypedDict,
16
- TypeVar,
17
- cast,
18
- overload,
19
- )
20
 
21
  import anyio
22
  import httpx
 
23
  from mcp import ClientSession, StdioServerParameters
24
- from mcp.client.session import (
25
- ListRootsFnT,
26
- LoggingFnT,
27
- MessageHandlerFnT,
28
- SamplingFnT,
29
- )
30
  from mcp.server.fastmcp import FastMCP as FastMCP1Server
31
- from mcp.shared.memory import create_connected_server_and_client_session
32
  from pydantic import AnyUrl
33
  from typing_extensions import Unpack
34
 
 
35
  from fastmcp.client.auth.oauth import OAuth
36
  from fastmcp.server.dependencies import get_http_headers
37
  from fastmcp.server.server import FastMCP
38
  from fastmcp.utilities.logging import get_logger
39
  from fastmcp.utilities.mcp_config import MCPConfig, infer_transport_type_from_url
40
 
41
- if TYPE_CHECKING:
42
- from fastmcp.utilities.mcp_config import MCPConfig
43
-
44
  logger = get_logger(__name__)
45
 
46
  # TypeVar for preserving specific ClientTransport subclass types
@@ -64,11 +50,12 @@ __all__ = [
64
  class SessionKwargs(TypedDict, total=False):
65
  """Keyword arguments for the MCP ClientSession constructor."""
66
 
 
67
  sampling_callback: SamplingFnT | None
68
  list_roots_callback: ListRootsFnT | None
69
  logging_callback: LoggingFnT | None
70
  message_handler: MessageHandlerFnT | None
71
- read_timeout_seconds: datetime.timedelta | None
72
 
73
 
74
  class ClientTransport(abc.ABC):
@@ -152,7 +139,7 @@ class WSTransport(ClientTransport):
152
  yield session
153
 
154
  def __repr__(self) -> str:
155
- return f"<WebSocket(url='{self.url}')>"
156
 
157
 
158
  class SSETransport(ClientTransport):
@@ -183,8 +170,7 @@ class SSETransport(ClientTransport):
183
  if auth == "oauth":
184
  auth = OAuth(self.url)
185
  elif isinstance(auth, str):
186
- self.headers["Authorization"] = auth
187
- auth = None
188
  self.auth = auth
189
 
190
  @contextlib.asynccontextmanager
@@ -221,7 +207,7 @@ class SSETransport(ClientTransport):
221
  yield session
222
 
223
  def __repr__(self) -> str:
224
- return f"<SSE(url='{self.url}')>"
225
 
226
 
227
  class StreamableHttpTransport(ClientTransport):
@@ -252,8 +238,7 @@ class StreamableHttpTransport(ClientTransport):
252
  if auth == "oauth":
253
  auth = OAuth(self.url)
254
  elif isinstance(auth, str):
255
- self.headers["Authorization"] = auth
256
- auth = None
257
  self.auth = auth
258
 
259
  @contextlib.asynccontextmanager
@@ -291,7 +276,7 @@ class StreamableHttpTransport(ClientTransport):
291
  yield session
292
 
293
  def __repr__(self) -> str:
294
- return f"<StreamableHttp(url='{self.url}')>"
295
 
296
 
297
  class StdioTransport(ClientTransport):
@@ -663,27 +648,49 @@ class FastMCPTransport(ClientTransport):
663
  tests or scenarios where client and server run in the same runtime.
664
  """
665
 
666
- def __init__(self, mcp: FastMCP | FastMCP1Server):
667
  """Initialize a FastMCPTransport from a FastMCP server instance."""
668
 
669
  # Accept both FastMCP 2.x and FastMCP 1.0 servers. Both expose a
670
  # ``_mcp_server`` attribute pointing to the underlying MCP server
671
  # implementation, so we can treat them identically.
672
  self.server = mcp
 
673
 
674
  @contextlib.asynccontextmanager
675
  async def connect_session(
676
  self, **session_kwargs: Unpack[SessionKwargs]
677
  ) -> AsyncIterator[ClientSession]:
678
- # create_connected_server_and_client_session manages the session lifecycle itself
679
- async with create_connected_server_and_client_session(
680
- server=self.server._mcp_server,
681
- **session_kwargs,
682
- ) as session:
683
- yield session
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
684
 
685
  def __repr__(self) -> str:
686
- return f"<FastMCP(server='{self.server.name}')>"
687
 
688
 
689
  class MCPConfigTransport(ClientTransport):
@@ -769,7 +776,7 @@ class MCPConfigTransport(ClientTransport):
769
  yield session
770
 
771
  def __repr__(self) -> str:
772
- return f"<MCPConfig(config='{self.config}')>"
773
 
774
 
775
  @overload
@@ -860,7 +867,6 @@ def infer_transport(
860
  transport = infer_transport(config)
861
  ```
862
  """
863
- from fastmcp.utilities.mcp_config import MCPConfig
864
 
865
  # the transport is already a ClientTransport
866
  if isinstance(transport, ClientTransport):
 
8
  import warnings
9
  from collections.abc import AsyncIterator, Callable
10
  from pathlib import Path
11
+ from typing import Any, Literal, TypedDict, TypeVar, cast, overload
 
 
 
 
 
 
 
 
12
 
13
  import anyio
14
  import httpx
15
+ import mcp.types
16
  from mcp import ClientSession, StdioServerParameters
17
+ from mcp.client.session import ListRootsFnT, LoggingFnT, MessageHandlerFnT, SamplingFnT
 
 
 
 
 
18
  from mcp.server.fastmcp import FastMCP as FastMCP1Server
19
+ from mcp.shared.memory import create_client_server_memory_streams
20
  from pydantic import AnyUrl
21
  from typing_extensions import Unpack
22
 
23
+ from fastmcp.client.auth.bearer import BearerAuth
24
  from fastmcp.client.auth.oauth import OAuth
25
  from fastmcp.server.dependencies import get_http_headers
26
  from fastmcp.server.server import FastMCP
27
  from fastmcp.utilities.logging import get_logger
28
  from fastmcp.utilities.mcp_config import MCPConfig, infer_transport_type_from_url
29
 
 
 
 
30
  logger = get_logger(__name__)
31
 
32
  # TypeVar for preserving specific ClientTransport subclass types
 
50
  class SessionKwargs(TypedDict, total=False):
51
  """Keyword arguments for the MCP ClientSession constructor."""
52
 
53
+ read_timeout_seconds: datetime.timedelta | None
54
  sampling_callback: SamplingFnT | None
55
  list_roots_callback: ListRootsFnT | None
56
  logging_callback: LoggingFnT | None
57
  message_handler: MessageHandlerFnT | None
58
+ client_info: mcp.types.Implementation | None
59
 
60
 
61
  class ClientTransport(abc.ABC):
 
139
  yield session
140
 
141
  def __repr__(self) -> str:
142
+ return f"<WebSocketTransport(url='{self.url}')>"
143
 
144
 
145
  class SSETransport(ClientTransport):
 
170
  if auth == "oauth":
171
  auth = OAuth(self.url)
172
  elif isinstance(auth, str):
173
+ auth = BearerAuth(auth)
 
174
  self.auth = auth
175
 
176
  @contextlib.asynccontextmanager
 
207
  yield session
208
 
209
  def __repr__(self) -> str:
210
+ return f"<SSETransport(url='{self.url}')>"
211
 
212
 
213
  class StreamableHttpTransport(ClientTransport):
 
238
  if auth == "oauth":
239
  auth = OAuth(self.url)
240
  elif isinstance(auth, str):
241
+ auth = BearerAuth(auth)
 
242
  self.auth = auth
243
 
244
  @contextlib.asynccontextmanager
 
276
  yield session
277
 
278
  def __repr__(self) -> str:
279
+ return f"<StreamableHttpTransport(url='{self.url}')>"
280
 
281
 
282
  class StdioTransport(ClientTransport):
 
648
  tests or scenarios where client and server run in the same runtime.
649
  """
650
 
651
+ def __init__(self, mcp: FastMCP | FastMCP1Server, raise_exceptions: bool = False):
652
  """Initialize a FastMCPTransport from a FastMCP server instance."""
653
 
654
  # Accept both FastMCP 2.x and FastMCP 1.0 servers. Both expose a
655
  # ``_mcp_server`` attribute pointing to the underlying MCP server
656
  # implementation, so we can treat them identically.
657
  self.server = mcp
658
+ self.raise_exceptions = raise_exceptions
659
 
660
  @contextlib.asynccontextmanager
661
  async def connect_session(
662
  self, **session_kwargs: Unpack[SessionKwargs]
663
  ) -> AsyncIterator[ClientSession]:
664
+ async with create_client_server_memory_streams() as (
665
+ client_streams,
666
+ server_streams,
667
+ ):
668
+ client_read, client_write = client_streams
669
+ server_read, server_write = server_streams
670
+
671
+ # Create a cancel scope for the server task
672
+ async with anyio.create_task_group() as tg:
673
+ tg.start_soon(
674
+ lambda: self.server._mcp_server.run(
675
+ server_read,
676
+ server_write,
677
+ self.server._mcp_server.create_initialization_options(),
678
+ raise_exceptions=self.raise_exceptions,
679
+ )
680
+ )
681
+
682
+ try:
683
+ async with ClientSession(
684
+ read_stream=client_read,
685
+ write_stream=client_write,
686
+ **session_kwargs,
687
+ ) as client_session:
688
+ yield client_session
689
+ finally:
690
+ tg.cancel_scope.cancel()
691
 
692
  def __repr__(self) -> str:
693
+ return f"<FastMCPTransport(server='{self.server.name}')>"
694
 
695
 
696
  class MCPConfigTransport(ClientTransport):
 
776
  yield session
777
 
778
  def __repr__(self) -> str:
779
+ return f"<MCPConfigTransport(config='{self.config}')>"
780
 
781
 
782
  @overload
 
867
  transport = infer_transport(config)
868
  ```
869
  """
 
870
 
871
  # the transport is already a ClientTransport
872
  if isinstance(transport, ClientTransport):
src/fastmcp/exceptions.py CHANGED
@@ -33,3 +33,7 @@ class ClientError(Exception):
33
 
34
  class NotFoundError(Exception):
35
  """Object not found."""
 
 
 
 
 
33
 
34
  class NotFoundError(Exception):
35
  """Object not found."""
36
+
37
+
38
+ class DisabledError(Exception):
39
+ """Object is disabled."""
src/fastmcp/prompts/prompt.py CHANGED
@@ -5,21 +5,21 @@ from __future__ import annotations as _annotations
5
  import inspect
6
  from abc import ABC, abstractmethod
7
  from collections.abc import Awaitable, Callable, Sequence
8
- from typing import TYPE_CHECKING, Annotated, Any
9
 
10
  import pydantic_core
11
  from mcp.types import EmbeddedResource, ImageContent, PromptMessage, Role, TextContent
12
  from mcp.types import Prompt as MCPPrompt
13
  from mcp.types import PromptArgument as MCPPromptArgument
14
- from pydantic import BeforeValidator, Field, TypeAdapter, validate_call
15
 
16
  from fastmcp.exceptions import PromptError
17
  from fastmcp.server.dependencies import get_context
 
18
  from fastmcp.utilities.json_schema import compress_schema
19
  from fastmcp.utilities.logging import get_logger
20
  from fastmcp.utilities.types import (
21
  FastMCPBaseModel,
22
- _convert_set_defaults,
23
  find_kwarg_by_type,
24
  get_cached_typeadapter,
25
  )
@@ -66,26 +66,13 @@ class PromptArgument(FastMCPBaseModel):
66
  )
67
 
68
 
69
- class Prompt(FastMCPBaseModel, ABC):
70
  """A prompt template that can be rendered with parameters."""
71
 
72
- name: str = Field(description="Name of the prompt")
73
- description: str | None = Field(
74
- default=None, description="Description of what the prompt does"
75
- )
76
- tags: Annotated[set[str], BeforeValidator(_convert_set_defaults)] = Field(
77
- default_factory=set, description="Tags for the prompt"
78
- )
79
  arguments: list[PromptArgument] | None = Field(
80
  default=None, description="Arguments that can be passed to the prompt"
81
  )
82
 
83
- def __eq__(self, other: object) -> bool:
84
- if type(self) is not type(other):
85
- return False
86
- assert isinstance(other, type(self))
87
- return self.model_dump() == other.model_dump()
88
-
89
  def to_mcp_prompt(self, **overrides: Any) -> MCPPrompt:
90
  """Convert the prompt to an MCP prompt."""
91
  arguments = [
@@ -109,6 +96,7 @@ class Prompt(FastMCPBaseModel, ABC):
109
  name: str | None = None,
110
  description: str | None = None,
111
  tags: set[str] | None = None,
 
112
  ) -> FunctionPrompt:
113
  """Create a Prompt from a function.
114
 
@@ -119,7 +107,7 @@ class Prompt(FastMCPBaseModel, ABC):
119
  - A sequence of any of the above
120
  """
121
  return FunctionPrompt.from_function(
122
- fn=fn, name=name, description=description, tags=tags
123
  )
124
 
125
  @abstractmethod
@@ -143,6 +131,7 @@ class FunctionPrompt(Prompt):
143
  name: str | None = None,
144
  description: str | None = None,
145
  tags: set[str] | None = None,
 
146
  ) -> FunctionPrompt:
147
  """Create a Prompt from a function.
148
 
@@ -208,6 +197,7 @@ class FunctionPrompt(Prompt):
208
  description=description,
209
  arguments=arguments,
210
  tags=tags or set(),
 
211
  fn=fn,
212
  )
213
 
 
5
  import inspect
6
  from abc import ABC, abstractmethod
7
  from collections.abc import Awaitable, Callable, Sequence
8
+ from typing import TYPE_CHECKING, Any
9
 
10
  import pydantic_core
11
  from mcp.types import EmbeddedResource, ImageContent, PromptMessage, Role, TextContent
12
  from mcp.types import Prompt as MCPPrompt
13
  from mcp.types import PromptArgument as MCPPromptArgument
14
+ from pydantic import Field, TypeAdapter, validate_call
15
 
16
  from fastmcp.exceptions import PromptError
17
  from fastmcp.server.dependencies import get_context
18
+ from fastmcp.utilities.components import FastMCPComponent
19
  from fastmcp.utilities.json_schema import compress_schema
20
  from fastmcp.utilities.logging import get_logger
21
  from fastmcp.utilities.types import (
22
  FastMCPBaseModel,
 
23
  find_kwarg_by_type,
24
  get_cached_typeadapter,
25
  )
 
66
  )
67
 
68
 
69
+ class Prompt(FastMCPComponent, ABC):
70
  """A prompt template that can be rendered with parameters."""
71
 
 
 
 
 
 
 
 
72
  arguments: list[PromptArgument] | None = Field(
73
  default=None, description="Arguments that can be passed to the prompt"
74
  )
75
 
 
 
 
 
 
 
76
  def to_mcp_prompt(self, **overrides: Any) -> MCPPrompt:
77
  """Convert the prompt to an MCP prompt."""
78
  arguments = [
 
96
  name: str | None = None,
97
  description: str | None = None,
98
  tags: set[str] | None = None,
99
+ enabled: bool | None = None,
100
  ) -> FunctionPrompt:
101
  """Create a Prompt from a function.
102
 
 
107
  - A sequence of any of the above
108
  """
109
  return FunctionPrompt.from_function(
110
+ fn=fn, name=name, description=description, tags=tags, enabled=enabled
111
  )
112
 
113
  @abstractmethod
 
131
  name: str | None = None,
132
  description: str | None = None,
133
  tags: set[str] | None = None,
134
+ enabled: bool | None = None,
135
  ) -> FunctionPrompt:
136
  """Create a Prompt from a function.
137
 
 
197
  description=description,
198
  arguments=arguments,
199
  tags=tags or set(),
200
+ enabled=enabled if enabled is not None else True,
201
  fn=fn,
202
  )
203
 
src/fastmcp/prompts/prompt_manager.py CHANGED
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Any
6
 
7
  from mcp import GetPromptResult
8
 
 
9
  from fastmcp.exceptions import NotFoundError, PromptError
10
  from fastmcp.prompts.prompt import FunctionPrompt, Prompt, PromptResult
11
  from fastmcp.settings import DuplicateBehavior
@@ -23,10 +24,10 @@ class PromptManager:
23
  def __init__(
24
  self,
25
  duplicate_behavior: DuplicateBehavior | None = None,
26
- mask_error_details: bool = False,
27
  ):
28
  self._prompts: dict[str, Prompt] = {}
29
- self.mask_error_details = mask_error_details
30
 
31
  # Default to "warn" if None is provided
32
  if duplicate_behavior is None:
 
6
 
7
  from mcp import GetPromptResult
8
 
9
+ from fastmcp import settings
10
  from fastmcp.exceptions import NotFoundError, PromptError
11
  from fastmcp.prompts.prompt import FunctionPrompt, Prompt, PromptResult
12
  from fastmcp.settings import DuplicateBehavior
 
24
  def __init__(
25
  self,
26
  duplicate_behavior: DuplicateBehavior | None = None,
27
+ mask_error_details: bool | None = None,
28
  ):
29
  self._prompts: dict[str, Prompt] = {}
30
+ self.mask_error_details = mask_error_details or settings.mask_error_details
31
 
32
  # Default to "warn" if None is provided
33
  if duplicate_behavior is None:
src/fastmcp/resources/resource.py CHANGED
@@ -11,18 +11,17 @@ import pydantic_core
11
  from mcp.types import Resource as MCPResource
12
  from pydantic import (
13
  AnyUrl,
14
- BeforeValidator,
15
  ConfigDict,
16
  Field,
17
  UrlConstraints,
18
- ValidationInfo,
19
  field_validator,
 
20
  )
 
21
 
22
  from fastmcp.server.dependencies import get_context
 
23
  from fastmcp.utilities.types import (
24
- FastMCPBaseModel,
25
- _convert_set_defaults,
26
  find_kwarg_by_type,
27
  )
28
 
@@ -30,7 +29,7 @@ if TYPE_CHECKING:
30
  pass
31
 
32
 
33
- class Resource(FastMCPBaseModel, abc.ABC):
34
  """Base class for all resources."""
35
 
36
  model_config = ConfigDict(validate_default=True)
@@ -38,13 +37,7 @@ class Resource(FastMCPBaseModel, abc.ABC):
38
  uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] = Field(
39
  default=..., description="URI of the resource"
40
  )
41
- name: str | None = Field(default=None, description="Name of the resource")
42
- description: str | None = Field(
43
- default=None, description="Description of the resource"
44
- )
45
- tags: Annotated[set[str], BeforeValidator(_convert_set_defaults)] = Field(
46
- default_factory=set, description="Tags for the resource"
47
- )
48
  mime_type: str = Field(
49
  default="text/plain",
50
  description="MIME type of the resource content",
@@ -59,6 +52,7 @@ class Resource(FastMCPBaseModel, abc.ABC):
59
  description: str | None = None,
60
  mime_type: str | None = None,
61
  tags: set[str] | None = None,
 
62
  ) -> FunctionResource:
63
  return FunctionResource.from_function(
64
  fn=fn,
@@ -67,6 +61,7 @@ class Resource(FastMCPBaseModel, abc.ABC):
67
  description=description,
68
  mime_type=mime_type,
69
  tags=tags,
 
70
  )
71
 
72
  @field_validator("mime_type", mode="before")
@@ -77,27 +72,22 @@ class Resource(FastMCPBaseModel, abc.ABC):
77
  return mime_type
78
  return "text/plain"
79
 
80
- @field_validator("name", mode="before")
81
- @classmethod
82
- def set_default_name(cls, name: str | None, info: ValidationInfo) -> str:
83
  """Set default name from URI if not provided."""
84
- if name:
85
- return name
86
- if uri := info.data.get("uri"):
87
- return str(uri)
88
- raise ValueError("Either name or uri must be provided")
 
 
89
 
90
  @abc.abstractmethod
91
  async def read(self) -> str | bytes:
92
  """Read the resource content."""
93
  pass
94
 
95
- def __eq__(self, other: object) -> bool:
96
- if type(self) is not type(other):
97
- return False
98
- assert isinstance(other, type(self))
99
- return self.model_dump() == other.model_dump()
100
-
101
  def to_mcp_resource(self, **overrides: Any) -> MCPResource:
102
  """Convert the resource to an MCPResource."""
103
  kwargs = {
@@ -108,6 +98,9 @@ class Resource(FastMCPBaseModel, abc.ABC):
108
  }
109
  return MCPResource(**kwargs | overrides)
110
 
 
 
 
111
 
112
  class FunctionResource(Resource):
113
  """A resource that defers data loading by wrapping a function.
@@ -133,6 +126,7 @@ class FunctionResource(Resource):
133
  description: str | None = None,
134
  mime_type: str | None = None,
135
  tags: set[str] | None = None,
 
136
  ) -> FunctionResource:
137
  """Create a FunctionResource from a function."""
138
  if isinstance(uri, str):
@@ -144,6 +138,7 @@ class FunctionResource(Resource):
144
  description=description or fn.__doc__,
145
  mime_type=mime_type or "text/plain",
146
  tags=tags or set(),
 
147
  )
148
 
149
  async def read(self) -> str | bytes:
 
11
  from mcp.types import Resource as MCPResource
12
  from pydantic import (
13
  AnyUrl,
 
14
  ConfigDict,
15
  Field,
16
  UrlConstraints,
 
17
  field_validator,
18
+ model_validator,
19
  )
20
+ from typing_extensions import Self
21
 
22
  from fastmcp.server.dependencies import get_context
23
+ from fastmcp.utilities.components import FastMCPComponent
24
  from fastmcp.utilities.types import (
 
 
25
  find_kwarg_by_type,
26
  )
27
 
 
29
  pass
30
 
31
 
32
+ class Resource(FastMCPComponent, abc.ABC):
33
  """Base class for all resources."""
34
 
35
  model_config = ConfigDict(validate_default=True)
 
37
  uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] = Field(
38
  default=..., description="URI of the resource"
39
  )
40
+ name: str = Field(default="", description="Name of the resource")
 
 
 
 
 
 
41
  mime_type: str = Field(
42
  default="text/plain",
43
  description="MIME type of the resource content",
 
52
  description: str | None = None,
53
  mime_type: str | None = None,
54
  tags: set[str] | None = None,
55
+ enabled: bool | None = None,
56
  ) -> FunctionResource:
57
  return FunctionResource.from_function(
58
  fn=fn,
 
61
  description=description,
62
  mime_type=mime_type,
63
  tags=tags,
64
+ enabled=enabled,
65
  )
66
 
67
  @field_validator("mime_type", mode="before")
 
72
  return mime_type
73
  return "text/plain"
74
 
75
+ @model_validator(mode="after")
76
+ def set_default_name(self) -> Self:
 
77
  """Set default name from URI if not provided."""
78
+ if self.name:
79
+ pass
80
+ elif self.uri:
81
+ self.name = str(self.uri)
82
+ else:
83
+ raise ValueError("Either name or uri must be provided")
84
+ return self
85
 
86
  @abc.abstractmethod
87
  async def read(self) -> str | bytes:
88
  """Read the resource content."""
89
  pass
90
 
 
 
 
 
 
 
91
  def to_mcp_resource(self, **overrides: Any) -> MCPResource:
92
  """Convert the resource to an MCPResource."""
93
  kwargs = {
 
98
  }
99
  return MCPResource(**kwargs | overrides)
100
 
101
+ def __repr__(self) -> str:
102
+ return f"{self.__class__.__name__}(uri={self.uri!r}, name={self.name!r}, description={self.description!r}, tags={self.tags})"
103
+
104
 
105
  class FunctionResource(Resource):
106
  """A resource that defers data loading by wrapping a function.
 
126
  description: str | None = None,
127
  mime_type: str | None = None,
128
  tags: set[str] | None = None,
129
+ enabled: bool | None = None,
130
  ) -> FunctionResource:
131
  """Create a FunctionResource from a function."""
132
  if isinstance(uri, str):
 
138
  description=description or fn.__doc__,
139
  mime_type=mime_type or "text/plain",
140
  tags=tags or set(),
141
+ enabled=enabled if enabled is not None else True,
142
  )
143
 
144
  async def read(self) -> str | bytes:
src/fastmcp/resources/resource_manager.py CHANGED
@@ -7,6 +7,7 @@ from typing import Any
7
 
8
  from pydantic import AnyUrl
9
 
 
10
  from fastmcp.exceptions import NotFoundError, ResourceError
11
  from fastmcp.resources.resource import Resource
12
  from fastmcp.resources.template import (
@@ -25,7 +26,7 @@ class ResourceManager:
25
  def __init__(
26
  self,
27
  duplicate_behavior: DuplicateBehavior | None = None,
28
- mask_error_details: bool = False,
29
  ):
30
  """Initialize the ResourceManager.
31
 
@@ -37,7 +38,7 @@ class ResourceManager:
37
  """
38
  self._resources: dict[str, Resource] = {}
39
  self._templates: dict[str, ResourceTemplate] = {}
40
- self.mask_error_details = mask_error_details
41
 
42
  # Default to "warn" if None is provided
43
  if duplicate_behavior is None:
 
7
 
8
  from pydantic import AnyUrl
9
 
10
+ from fastmcp import settings
11
  from fastmcp.exceptions import NotFoundError, ResourceError
12
  from fastmcp.resources.resource import Resource
13
  from fastmcp.resources.template import (
 
26
  def __init__(
27
  self,
28
  duplicate_behavior: DuplicateBehavior | None = None,
29
+ mask_error_details: bool | None = None,
30
  ):
31
  """Initialize the ResourceManager.
32
 
 
38
  """
39
  self._resources: dict[str, Resource] = {}
40
  self._templates: dict[str, ResourceTemplate] = {}
41
+ self.mask_error_details = mask_error_details or settings.mask_error_details
42
 
43
  # Default to "warn" if None is provided
44
  if duplicate_behavior is None:
src/fastmcp/resources/template.py CHANGED
@@ -5,12 +5,11 @@ from __future__ import annotations
5
  import inspect
6
  import re
7
  from collections.abc import Callable
8
- from typing import Annotated, Any
9
  from urllib.parse import unquote
10
 
11
  from mcp.types import ResourceTemplate as MCPResourceTemplate
12
  from pydantic import (
13
- BeforeValidator,
14
  Field,
15
  field_validator,
16
  validate_call,
@@ -18,10 +17,9 @@ from pydantic import (
18
 
19
  from fastmcp.resources.types import Resource
20
  from fastmcp.server.dependencies import get_context
 
21
  from fastmcp.utilities.json_schema import compress_schema
22
  from fastmcp.utilities.types import (
23
- FastMCPBaseModel,
24
- _convert_set_defaults,
25
  find_kwarg_by_type,
26
  get_cached_typeadapter,
27
  )
@@ -51,17 +49,12 @@ def match_uri_template(uri: str, uri_template: str) -> dict[str, str] | None:
51
  return None
52
 
53
 
54
- class ResourceTemplate(FastMCPBaseModel):
55
  """A template for dynamically creating resources."""
56
 
57
  uri_template: str = Field(
58
  description="URI template with parameters (e.g. weather://{city}/current)"
59
  )
60
- name: str = Field(description="Name of the resource")
61
- description: str | None = Field(description="Description of what the resource does")
62
- tags: Annotated[set[str], BeforeValidator(_convert_set_defaults)] = Field(
63
- default_factory=set, description="Tags for the resource"
64
- )
65
  mime_type: str = Field(
66
  default="text/plain", description="MIME type of the resource content"
67
  )
@@ -77,6 +70,7 @@ class ResourceTemplate(FastMCPBaseModel):
77
  description: str | None = None,
78
  mime_type: str | None = None,
79
  tags: set[str] | None = None,
 
80
  ) -> FunctionResourceTemplate:
81
  return FunctionResourceTemplate.from_function(
82
  fn=fn,
@@ -85,6 +79,7 @@ class ResourceTemplate(FastMCPBaseModel):
85
  description=description,
86
  mime_type=mime_type,
87
  tags=tags,
 
88
  )
89
 
90
  @field_validator("mime_type", mode="before")
@@ -120,14 +115,9 @@ class ResourceTemplate(FastMCPBaseModel):
120
  description=self.description,
121
  mime_type=self.mime_type,
122
  tags=self.tags,
 
123
  )
124
 
125
- def __eq__(self, other: object) -> bool:
126
- if type(self) is not type(other):
127
- return False
128
- assert isinstance(other, type(self))
129
- return self.model_dump() == other.model_dump()
130
-
131
  def to_mcp_template(self, **overrides: Any) -> MCPResourceTemplate:
132
  """Convert the resource template to an MCPResourceTemplate."""
133
  kwargs = {
@@ -168,6 +158,7 @@ class FunctionResourceTemplate(ResourceTemplate):
168
  description: str | None = None,
169
  mime_type: str | None = None,
170
  tags: set[str] | None = None,
 
171
  ) -> FunctionResourceTemplate:
172
  """Create a template from a function."""
173
  from fastmcp.server.context import Context
@@ -250,4 +241,5 @@ class FunctionResourceTemplate(ResourceTemplate):
250
  fn=fn,
251
  parameters=parameters,
252
  tags=tags or set(),
 
253
  )
 
5
  import inspect
6
  import re
7
  from collections.abc import Callable
8
+ from typing import Any
9
  from urllib.parse import unquote
10
 
11
  from mcp.types import ResourceTemplate as MCPResourceTemplate
12
  from pydantic import (
 
13
  Field,
14
  field_validator,
15
  validate_call,
 
17
 
18
  from fastmcp.resources.types import Resource
19
  from fastmcp.server.dependencies import get_context
20
+ from fastmcp.utilities.components import FastMCPComponent
21
  from fastmcp.utilities.json_schema import compress_schema
22
  from fastmcp.utilities.types import (
 
 
23
  find_kwarg_by_type,
24
  get_cached_typeadapter,
25
  )
 
49
  return None
50
 
51
 
52
+ class ResourceTemplate(FastMCPComponent):
53
  """A template for dynamically creating resources."""
54
 
55
  uri_template: str = Field(
56
  description="URI template with parameters (e.g. weather://{city}/current)"
57
  )
 
 
 
 
 
58
  mime_type: str = Field(
59
  default="text/plain", description="MIME type of the resource content"
60
  )
 
70
  description: str | None = None,
71
  mime_type: str | None = None,
72
  tags: set[str] | None = None,
73
+ enabled: bool | None = None,
74
  ) -> FunctionResourceTemplate:
75
  return FunctionResourceTemplate.from_function(
76
  fn=fn,
 
79
  description=description,
80
  mime_type=mime_type,
81
  tags=tags,
82
+ enabled=enabled,
83
  )
84
 
85
  @field_validator("mime_type", mode="before")
 
115
  description=self.description,
116
  mime_type=self.mime_type,
117
  tags=self.tags,
118
+ enabled=self.enabled,
119
  )
120
 
 
 
 
 
 
 
121
  def to_mcp_template(self, **overrides: Any) -> MCPResourceTemplate:
122
  """Convert the resource template to an MCPResourceTemplate."""
123
  kwargs = {
 
158
  description: str | None = None,
159
  mime_type: str | None = None,
160
  tags: set[str] | None = None,
161
+ enabled: bool | None = None,
162
  ) -> FunctionResourceTemplate:
163
  """Create a template from a function."""
164
  from fastmcp.server.context import Context
 
241
  fn=fn,
242
  parameters=parameters,
243
  tags=tags or set(),
244
+ enabled=enabled if enabled is not None else True,
245
  )
src/fastmcp/server/auth/providers/bearer_env.py CHANGED
@@ -1,13 +1,10 @@
 
 
1
  from pydantic_settings import BaseSettings, SettingsConfigDict
2
 
3
  from fastmcp.server.auth.providers.bearer import BearerAuthProvider
4
 
5
 
6
- # Sentinel object to indicate that a setting is not set
7
- class _NotSet:
8
- pass
9
-
10
-
11
  class EnvBearerAuthProviderSettings(BaseSettings):
12
  """Settings for the BearerAuthProvider."""
13
 
@@ -33,11 +30,11 @@ class EnvBearerAuthProvider(BearerAuthProvider):
33
 
34
  def __init__(
35
  self,
36
- public_key: str | None | type[_NotSet] = _NotSet,
37
- jwks_uri: str | None | type[_NotSet] = _NotSet,
38
- issuer: str | None | type[_NotSet] = _NotSet,
39
- audience: str | None | type[_NotSet] = _NotSet,
40
- required_scopes: list[str] | None | type[_NotSet] = _NotSet,
41
  ):
42
  """
43
  Initialize the provider.
@@ -57,6 +54,6 @@ class EnvBearerAuthProvider(BearerAuthProvider):
57
  "required_scopes": required_scopes,
58
  }
59
  settings = EnvBearerAuthProviderSettings(
60
- **{k: v for k, v in kwargs.items() if v is not _NotSet}
61
  )
62
  super().__init__(**settings.model_dump())
 
1
+ from types import EllipsisType
2
+
3
  from pydantic_settings import BaseSettings, SettingsConfigDict
4
 
5
  from fastmcp.server.auth.providers.bearer import BearerAuthProvider
6
 
7
 
 
 
 
 
 
8
  class EnvBearerAuthProviderSettings(BaseSettings):
9
  """Settings for the BearerAuthProvider."""
10
 
 
30
 
31
  def __init__(
32
  self,
33
+ public_key: str | None | EllipsisType = ...,
34
+ jwks_uri: str | None | EllipsisType = ...,
35
+ issuer: str | None | EllipsisType = ...,
36
+ audience: str | None | EllipsisType = ...,
37
+ required_scopes: list[str] | None | EllipsisType = ...,
38
  ):
39
  """
40
  Initialize the provider.
 
54
  "required_scopes": required_scopes,
55
  }
56
  settings = EnvBearerAuthProviderSettings(
57
+ **{k: v for k, v in kwargs.items() if v is not ...}
58
  )
59
  super().__init__(**settings.model_dump())
src/fastmcp/server/dependencies.py CHANGED
@@ -67,6 +67,7 @@ def get_http_headers(include_all: bool = False) -> dict[str, str]:
67
  "te",
68
  "keep-alive",
69
  "expect",
 
70
  # Proxy-related headers
71
  "proxy-authenticate",
72
  "proxy-authorization",
 
67
  "te",
68
  "keep-alive",
69
  "expect",
70
+ "accept",
71
  # Proxy-related headers
72
  "proxy-authenticate",
73
  "proxy-authorization",
src/fastmcp/server/http.py CHANGED
@@ -13,6 +13,7 @@ from mcp.server.auth.middleware.bearer_auth import (
13
  from mcp.server.auth.routes import create_auth_routes
14
  from mcp.server.lowlevel.server import LifespanResultT
15
  from mcp.server.sse import SseServerTransport
 
16
  from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
17
  from starlette.applications import Starlette
18
  from starlette.middleware import Middleware
@@ -241,7 +242,7 @@ def create_sse_app(
241
  def create_streamable_http_app(
242
  server: FastMCP[LifespanResultT],
243
  streamable_http_path: str,
244
- event_store: None = None,
245
  auth: OAuthProvider | None = None,
246
  json_response: bool = False,
247
  stateless_http: bool = False,
 
13
  from mcp.server.auth.routes import create_auth_routes
14
  from mcp.server.lowlevel.server import LifespanResultT
15
  from mcp.server.sse import SseServerTransport
16
+ from mcp.server.streamable_http import EventStore
17
  from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
18
  from starlette.applications import Starlette
19
  from starlette.middleware import Middleware
 
242
  def create_streamable_http_app(
243
  server: FastMCP[LifespanResultT],
244
  streamable_http_path: str,
245
+ event_store: EventStore | None = None,
246
  auth: OAuthProvider | None = None,
247
  json_response: bool = False,
248
  stateless_http: bool = False,
src/fastmcp/server/openapi.py CHANGED
@@ -155,16 +155,10 @@ class RouteMap:
155
  self.route_type = self.mcp_type
156
 
157
 
158
- # Default route mappings as a list, where order determines priority
 
159
  DEFAULT_ROUTE_MAPPINGS = [
160
- # GET requests with path parameters go to ResourceTemplate
161
- RouteMap(
162
- methods=["GET"], pattern=r".*\{.*\}.*", mcp_type=MCPType.RESOURCE_TEMPLATE
163
- ),
164
- # GET requests without path parameters go to Resource
165
- RouteMap(methods=["GET"], pattern=r".*", mcp_type=MCPType.RESOURCE),
166
- # All other HTTP methods go to Tool
167
- RouteMap(methods="*", pattern=r".*", mcp_type=MCPType.TOOL),
168
  ]
169
 
170
 
@@ -226,7 +220,6 @@ class OpenAPITool(Tool):
226
  tags: set[str] = set(),
227
  timeout: float | None = None,
228
  annotations: ToolAnnotations | None = None,
229
- exclude_args: list[str] | None = None,
230
  serializer: Callable[[Any], str] | None = None,
231
  ):
232
  super().__init__(
@@ -235,7 +228,6 @@ class OpenAPITool(Tool):
235
  parameters=parameters,
236
  tags=tags,
237
  annotations=annotations,
238
- exclude_args=exclude_args,
239
  serializer=serializer,
240
  )
241
  self._client = client
 
155
  self.route_type = self.mcp_type
156
 
157
 
158
+ # Default route mapping: all routes become tools.
159
+ # Users can provide custom route_maps to override this behavior.
160
  DEFAULT_ROUTE_MAPPINGS = [
161
+ RouteMap(mcp_type=MCPType.TOOL),
 
 
 
 
 
 
 
162
  ]
163
 
164
 
 
220
  tags: set[str] = set(),
221
  timeout: float | None = None,
222
  annotations: ToolAnnotations | None = None,
 
223
  serializer: Callable[[Any], str] | None = None,
224
  ):
225
  super().__init__(
 
228
  parameters=parameters,
229
  tags=tags,
230
  annotations=annotations,
 
231
  serializer=serializer,
232
  )
233
  self._client = client
src/fastmcp/server/proxy.py CHANGED
@@ -186,8 +186,10 @@ class FastMCPProxy(FastMCP):
186
  else:
187
  raise e
188
  for tool in client_tools:
189
- tool_proxy = await ProxyTool.from_client(self.client, tool)
190
- tools[tool_proxy.name] = tool_proxy
 
 
191
 
192
  return tools
193
 
@@ -203,8 +205,12 @@ class FastMCPProxy(FastMCP):
203
  else:
204
  raise e
205
  for resource in client_resources:
206
- resource_proxy = await ProxyResource.from_client(self.client, resource)
207
- resources[str(resource_proxy.uri)] = resource_proxy
 
 
 
 
208
 
209
  return resources
210
 
@@ -220,8 +226,12 @@ class FastMCPProxy(FastMCP):
220
  else:
221
  raise e
222
  for template in client_templates:
223
- template_proxy = await ProxyTemplate.from_client(self.client, template)
224
- templates[template_proxy.uri_template] = template_proxy
 
 
 
 
225
 
226
  return templates
227
 
@@ -237,24 +247,27 @@ class FastMCPProxy(FastMCP):
237
  else:
238
  raise e
239
  for prompt in client_prompts:
240
- prompt_proxy = await ProxyPrompt.from_client(self.client, prompt)
241
- prompts[prompt_proxy.name] = prompt_proxy
 
 
 
242
  return prompts
243
 
244
- async def _mcp_call_tool(
245
  self, key: str, arguments: dict[str, Any]
246
  ) -> list[TextContent | ImageContent | EmbeddedResource]:
247
  try:
248
- result = await super()._mcp_call_tool(key, arguments)
249
  return result
250
  except NotFoundError:
251
  async with self.client:
252
  result = await self.client.call_tool(key, arguments)
253
  return result
254
 
255
- async def _mcp_read_resource(self, uri: AnyUrl | str) -> list[ReadResourceContents]:
256
  try:
257
- result = await super()._mcp_read_resource(uri)
258
  return result
259
  except NotFoundError:
260
  async with self.client:
@@ -270,11 +283,11 @@ class FastMCPProxy(FastMCP):
270
  ReadResourceContents(content=content, mime_type=resource[0].mimeType)
271
  ]
272
 
273
- async def _mcp_get_prompt(
274
  self, name: str, arguments: dict[str, Any] | None = None
275
  ) -> GetPromptResult:
276
  try:
277
- result = await super()._mcp_get_prompt(name, arguments)
278
  return result
279
  except NotFoundError:
280
  async with self.client:
 
186
  else:
187
  raise e
188
  for tool in client_tools:
189
+ # don't overwrite tools defined in the server
190
+ if tool.name not in tools:
191
+ tool_proxy = await ProxyTool.from_client(self.client, tool)
192
+ tools[tool_proxy.name] = tool_proxy
193
 
194
  return tools
195
 
 
205
  else:
206
  raise e
207
  for resource in client_resources:
208
+ # don't overwrite resources defined in the server
209
+ if str(resource.uri) not in resources:
210
+ resource_proxy = await ProxyResource.from_client(
211
+ self.client, resource
212
+ )
213
+ resources[str(resource_proxy.uri)] = resource_proxy
214
 
215
  return resources
216
 
 
226
  else:
227
  raise e
228
  for template in client_templates:
229
+ # don't overwrite templates defined in the server
230
+ if template.uriTemplate not in templates:
231
+ template_proxy = await ProxyTemplate.from_client(
232
+ self.client, template
233
+ )
234
+ templates[template_proxy.uri_template] = template_proxy
235
 
236
  return templates
237
 
 
247
  else:
248
  raise e
249
  for prompt in client_prompts:
250
+ # don't overwrite prompts defined in the server
251
+ if prompt.name not in prompts:
252
+ prompt_proxy = await ProxyPrompt.from_client(self.client, prompt)
253
+ prompts[prompt_proxy.name] = prompt_proxy
254
+
255
  return prompts
256
 
257
+ async def _call_tool(
258
  self, key: str, arguments: dict[str, Any]
259
  ) -> list[TextContent | ImageContent | EmbeddedResource]:
260
  try:
261
+ result = await super()._call_tool(key, arguments)
262
  return result
263
  except NotFoundError:
264
  async with self.client:
265
  result = await self.client.call_tool(key, arguments)
266
  return result
267
 
268
+ async def _read_resource(self, uri: AnyUrl | str) -> list[ReadResourceContents]:
269
  try:
270
+ result = await super()._read_resource(uri)
271
  return result
272
  except NotFoundError:
273
  async with self.client:
 
283
  ReadResourceContents(content=content, mime_type=resource[0].mimeType)
284
  ]
285
 
286
+ async def _get_prompt(
287
  self, name: str, arguments: dict[str, Any] | None = None
288
  ) -> GetPromptResult:
289
  try:
290
+ result = await super()._get_prompt(name, arguments)
291
  return result
292
  except NotFoundError:
293
  async with self.client:
src/fastmcp/server/server.py CHANGED
@@ -43,8 +43,7 @@ from starlette.routing import BaseRoute, Route
43
 
44
  import fastmcp
45
  import fastmcp.server
46
- import fastmcp.settings
47
- from fastmcp.exceptions import NotFoundError
48
  from fastmcp.prompts import Prompt, PromptManager
49
  from fastmcp.prompts.prompt import FunctionPrompt
50
  from fastmcp.resources import Resource, ResourceManager
@@ -56,9 +55,11 @@ from fastmcp.server.http import (
56
  create_sse_app,
57
  create_streamable_http_app,
58
  )
 
59
  from fastmcp.tools import ToolManager
60
  from fastmcp.tools.tool import FunctionTool, Tool
61
  from fastmcp.utilities.cache import TimedCache
 
62
  from fastmcp.utilities.logging import get_logger
63
  from fastmcp.utilities.mcp_config import MCPConfig
64
 
@@ -121,7 +122,6 @@ class FastMCP(Generic[LifespanResultT]):
121
  | None
122
  ) = None,
123
  tags: set[str] | None = None,
124
- dependencies: list[str] | None = None,
125
  tool_serializer: Callable[[Any], str] | None = None,
126
  cache_expiration_seconds: float | None = None,
127
  on_duplicate_tools: DuplicateBehavior | None = None,
@@ -130,6 +130,7 @@ class FastMCP(Generic[LifespanResultT]):
130
  resource_prefix_format: Literal["protocol", "path"] | None = None,
131
  mask_error_details: bool | None = None,
132
  tools: list[Tool | Callable[..., Any]] | None = None,
 
133
  include_tags: set[str]
134
  | set[tuple[str, ...]]
135
  | set[str | tuple[str, ...]]
@@ -138,47 +139,43 @@ class FastMCP(Generic[LifespanResultT]):
138
  | set[tuple[str, ...]]
139
  | set[str | tuple[str, ...]]
140
  | None = None,
141
- **settings: Any,
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  ):
143
- if cache_expiration_seconds is not None:
144
- settings["cache_expiration_seconds"] = cache_expiration_seconds
145
- self.settings = fastmcp.settings.ServerSettings(
146
- include_tags=include_tags, exclude_tags=exclude_tags, **settings
147
  )
148
 
149
- # If mask_error_details is provided, override the settings value
150
- if mask_error_details is not None:
151
- self.settings.mask_error_details = mask_error_details
152
-
153
- self.resource_prefix_format: Literal["protocol", "path"]
154
- if resource_prefix_format is None:
155
- self.resource_prefix_format = (
156
- fastmcp.settings.settings.resource_prefix_format
157
- )
158
- else:
159
- self.resource_prefix_format = resource_prefix_format
160
-
161
  self.tags: set[str] = tags or set()
162
 
163
- self.dependencies = dependencies
164
  self._cache = TimedCache(
165
- expiration=datetime.timedelta(
166
- seconds=self.settings.cache_expiration_seconds
167
- )
168
  )
169
  self._mounted_servers: dict[str, MountedServer] = {}
170
  self._additional_http_routes: list[BaseRoute] = []
171
  self._tool_manager = ToolManager(
172
  duplicate_behavior=on_duplicate_tools,
173
- mask_error_details=self.settings.mask_error_details,
174
  )
175
  self._resource_manager = ResourceManager(
176
  duplicate_behavior=on_duplicate_resources,
177
- mask_error_details=self.settings.mask_error_details,
178
  )
179
  self._prompt_manager = PromptManager(
180
  duplicate_behavior=on_duplicate_prompts,
181
- mask_error_details=self.settings.mask_error_details,
182
  )
183
  self._tool_serializer = tool_serializer
184
 
@@ -193,7 +190,7 @@ class FastMCP(Generic[LifespanResultT]):
193
  lifespan=_lifespan_wrapper(self, lifespan),
194
  )
195
 
196
- if auth is None and self.settings.default_auth_provider == "bearer_env":
197
  auth = EnvBearerAuthProvider()
198
  self.auth = auth
199
 
@@ -203,12 +200,67 @@ class FastMCP(Generic[LifespanResultT]):
203
  tool = Tool.from_function(tool, serializer=self._tool_serializer)
204
  self.add_tool(tool)
205
 
 
 
 
206
  # Set up MCP protocol handlers
207
  self._setup_handlers()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
 
209
  def __repr__(self) -> str:
210
  return f"{type(self).__name__}({self.name!r})"
211
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  @property
213
  def name(self) -> str:
214
  return self._mcp_server.name
@@ -279,6 +331,12 @@ class FastMCP(Generic[LifespanResultT]):
279
  self._cache.set("tools", tools)
280
  return tools
281
 
 
 
 
 
 
 
282
  async def get_resources(self) -> dict[str, Resource]:
283
  """Get all registered resources, indexed by registered key."""
284
  if (resources := self._cache.get("resources")) is self._cache.NOT_FOUND:
@@ -296,6 +354,12 @@ class FastMCP(Generic[LifespanResultT]):
296
  self._cache.set("resources", resources)
297
  return resources
298
 
 
 
 
 
 
 
299
  async def get_resource_templates(self) -> dict[str, ResourceTemplate]:
300
  """Get all registered resource templates, indexed by registered key."""
301
  if (
@@ -316,6 +380,12 @@ class FastMCP(Generic[LifespanResultT]):
316
  self._cache.set("resource_templates", templates)
317
  return templates
318
 
 
 
 
 
 
 
319
  async def get_prompts(self) -> dict[str, Prompt]:
320
  """
321
  List all available prompts.
@@ -335,6 +405,12 @@ class FastMCP(Generic[LifespanResultT]):
335
  self._cache.set("prompts", prompts)
336
  return prompts
337
 
 
 
 
 
 
 
338
  def custom_route(
339
  self,
340
  path: str,
@@ -389,7 +465,7 @@ class FastMCP(Generic[LifespanResultT]):
389
 
390
  mcp_tools: list[MCPTool] = []
391
  for key, tool in tools.items():
392
- if self.should_include_component(tool):
393
  mcp_tools.append(tool.to_mcp_tool(name=key))
394
 
395
  return mcp_tools
@@ -403,7 +479,7 @@ class FastMCP(Generic[LifespanResultT]):
403
  resources = await self.get_resources()
404
  mcp_resources: list[MCPResource] = []
405
  for key, resource in resources.items():
406
- if self.should_include_component(resource):
407
  mcp_resources.append(resource.to_mcp_resource(uri=key))
408
  return mcp_resources
409
 
@@ -416,7 +492,7 @@ class FastMCP(Generic[LifespanResultT]):
416
  templates = await self.get_resource_templates()
417
  mcp_templates: list[MCPResourceTemplate] = []
418
  for key, template in templates.items():
419
- if self.should_include_component(template):
420
  mcp_templates.append(template.to_mcp_template(uriTemplate=key))
421
  return mcp_templates
422
 
@@ -429,14 +505,17 @@ class FastMCP(Generic[LifespanResultT]):
429
  prompts = await self.get_prompts()
430
  mcp_prompts: list[MCPPrompt] = []
431
  for key, prompt in prompts.items():
432
- if self.should_include_component(prompt):
433
  mcp_prompts.append(prompt.to_mcp_prompt(name=key))
434
  return mcp_prompts
435
 
436
  async def _mcp_call_tool(
437
  self, key: str, arguments: dict[str, Any]
438
  ) -> list[TextContent | ImageContent | EmbeddedResource]:
439
- """Handle MCP 'callTool' requests.
 
 
 
440
 
441
  Args:
442
  key: The name of the tool to call
@@ -449,48 +528,109 @@ class FastMCP(Generic[LifespanResultT]):
449
 
450
  # Create and use context for the entire call
451
  with fastmcp.server.context.Context(fastmcp=self):
452
- # Get tool, checking first from our tools, then from the mounted servers
453
- if self._tool_manager.has_tool(key):
454
- tool = self._tool_manager.get_tool(key)
455
- if not self.should_include_component(tool):
456
- raise NotFoundError(f"Unknown tool: {key}")
457
- return await self._tool_manager.call_tool(key, arguments)
458
-
459
- # Check mounted servers to see if they have the tool
460
- for server in self._mounted_servers.values():
461
- if server.match_tool(key):
462
- tool_key = server.strip_tool_prefix(key)
463
- return await server.server._mcp_call_tool(tool_key, arguments)
 
 
 
464
 
465
- raise NotFoundError(f"Unknown tool: {key}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
466
 
467
  async def _mcp_read_resource(self, uri: AnyUrl | str) -> list[ReadResourceContents]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
468
  """
469
  Read a resource by URI, in the format expected by the low-level MCP
470
  server.
471
  """
472
- with fastmcp.server.context.Context(fastmcp=self):
473
- if self._resource_manager.has_resource(uri):
474
- resource = await self._resource_manager.get_resource(uri)
475
- if not self.should_include_component(resource):
476
- raise NotFoundError(f"Unknown resource: {uri}")
477
- content = await self._resource_manager.read_resource(uri)
478
- return [
479
- ReadResourceContents(
480
- content=content,
481
- mime_type=resource.mime_type,
482
- )
483
- ]
 
 
 
 
484
  else:
485
- for server in self._mounted_servers.values():
486
- if server.match_resource(str(uri)):
487
- new_uri = server.strip_resource_prefix(str(uri))
488
- return await server.server._mcp_read_resource(new_uri)
489
- else:
490
- raise NotFoundError(f"Unknown resource: {uri}")
491
 
492
  async def _mcp_get_prompt(
493
  self, name: str, arguments: dict[str, Any] | None = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
494
  ) -> GetPromptResult:
495
  """Handle MCP 'getPrompt' requests.
496
 
@@ -503,22 +643,20 @@ class FastMCP(Generic[LifespanResultT]):
503
  """
504
  logger.debug("Get prompt: %s with %s", name, arguments)
505
 
506
- # Create and use context for the entire call
507
- with fastmcp.server.context.Context(fastmcp=self):
508
- # Get prompt, checking first from our prompts, then from the mounted servers
509
- if self._prompt_manager.has_prompt(name):
510
- prompt = self._prompt_manager.get_prompt(name)
511
- if not self.should_include_component(prompt):
512
- raise NotFoundError(f"Unknown prompt: {name}")
513
- return await self._prompt_manager.render_prompt(name, arguments)
514
-
515
- # Check mounted servers to see if they have the prompt
516
- for server in self._mounted_servers.values():
517
- if server.match_prompt(name):
518
- prompt_name = server.strip_prompt_prefix(name)
519
- return await server.server._mcp_get_prompt(prompt_name, arguments)
520
 
521
- raise NotFoundError(f"Unknown prompt: {name}")
522
 
523
  def add_tool(self, tool: Tool) -> None:
524
  """Add a tool to the server.
@@ -554,6 +692,7 @@ class FastMCP(Generic[LifespanResultT]):
554
  tags: set[str] | None = None,
555
  annotations: ToolAnnotations | dict[str, Any] | None = None,
556
  exclude_args: list[str] | None = None,
 
557
  ) -> FunctionTool: ...
558
 
559
  @overload
@@ -566,6 +705,7 @@ class FastMCP(Generic[LifespanResultT]):
566
  tags: set[str] | None = None,
567
  annotations: ToolAnnotations | dict[str, Any] | None = None,
568
  exclude_args: list[str] | None = None,
 
569
  ) -> Callable[[AnyFunction], FunctionTool]: ...
570
 
571
  def tool(
@@ -577,6 +717,7 @@ class FastMCP(Generic[LifespanResultT]):
577
  tags: set[str] | None = None,
578
  annotations: ToolAnnotations | dict[str, Any] | None = None,
579
  exclude_args: list[str] | None = None,
 
580
  ) -> Callable[[AnyFunction], FunctionTool] | FunctionTool:
581
  """Decorator to register a tool.
582
 
@@ -593,11 +734,12 @@ class FastMCP(Generic[LifespanResultT]):
593
 
594
  Args:
595
  name_or_fn: Either a function (when used as @tool), a string name, or None
 
596
  description: Optional description of what the tool does
597
  tags: Optional set of tags for categorizing the tool
598
- annotations: Optional annotations about the tool's behavior
599
  exclude_args: Optional list of argument names to exclude from the tool schema
600
- name: Optional name for the tool (keyword-only, alternative to name_or_fn)
601
 
602
  Example:
603
  @server.tool
@@ -650,6 +792,7 @@ class FastMCP(Generic[LifespanResultT]):
650
  annotations=annotations,
651
  exclude_args=exclude_args,
652
  serializer=self._tool_serializer,
 
653
  )
654
  self.add_tool(tool)
655
  return tool
@@ -678,6 +821,7 @@ class FastMCP(Generic[LifespanResultT]):
678
  tags=tags,
679
  annotations=annotations,
680
  exclude_args=exclude_args,
 
681
  )
682
 
683
  def add_resource(self, resource: Resource, key: str | None = None) -> None:
@@ -744,6 +888,7 @@ class FastMCP(Generic[LifespanResultT]):
744
  description: str | None = None,
745
  mime_type: str | None = None,
746
  tags: set[str] | None = None,
 
747
  ) -> Callable[[AnyFunction], Resource | ResourceTemplate]:
748
  """Decorator to register a function as a resource.
749
 
@@ -766,6 +911,7 @@ class FastMCP(Generic[LifespanResultT]):
766
  description: Optional description of the resource
767
  mime_type: Optional MIME type for the resource
768
  tags: Optional set of tags for categorizing the resource
 
769
 
770
  Example:
771
  @server.resource("resource://my-resource")
@@ -830,6 +976,7 @@ class FastMCP(Generic[LifespanResultT]):
830
  description=description,
831
  mime_type=mime_type,
832
  tags=tags,
 
833
  )
834
  self.add_template(template)
835
  return template
@@ -841,6 +988,7 @@ class FastMCP(Generic[LifespanResultT]):
841
  description=description,
842
  mime_type=mime_type,
843
  tags=tags,
 
844
  )
845
  self.add_resource(resource)
846
  return resource
@@ -869,6 +1017,7 @@ class FastMCP(Generic[LifespanResultT]):
869
  name: str | None = None,
870
  description: str | None = None,
871
  tags: set[str] | None = None,
 
872
  ) -> FunctionPrompt: ...
873
 
874
  @overload
@@ -879,6 +1028,7 @@ class FastMCP(Generic[LifespanResultT]):
879
  name: str | None = None,
880
  description: str | None = None,
881
  tags: set[str] | None = None,
 
882
  ) -> Callable[[AnyFunction], FunctionPrompt]: ...
883
 
884
  def prompt(
@@ -888,6 +1038,7 @@ class FastMCP(Generic[LifespanResultT]):
888
  name: str | None = None,
889
  description: str | None = None,
890
  tags: set[str] | None = None,
 
891
  ) -> Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt:
892
  """Decorator to register a prompt.
893
 
@@ -897,16 +1048,17 @@ class FastMCP(Generic[LifespanResultT]):
897
 
898
  This decorator supports multiple calling patterns:
899
  - @server.prompt (without parentheses)
900
- - @server.prompt (with empty parentheses)
901
  - @server.prompt("custom_name") (with name as first argument)
902
  - @server.prompt(name="custom_name") (with name as keyword argument)
903
  - server.prompt(function, name="custom_name") (direct function call)
904
 
905
  Args:
906
  name_or_fn: Either a function (when used as @prompt), a string name, or None
 
907
  description: Optional description of what the prompt does
908
  tags: Optional set of tags for categorizing the prompt
909
- name: Optional name for the prompt (keyword-only, alternative to name_or_fn)
910
 
911
  Example:
912
  @server.prompt
@@ -919,7 +1071,7 @@ class FastMCP(Generic[LifespanResultT]):
919
  }
920
  ]
921
 
922
- @server.prompt
923
  def analyze_with_context(table_name: str, ctx: Context) -> list[Message]:
924
  ctx.info(f"Analyzing table {table_name}")
925
  schema = read_table_schema(table_name)
@@ -979,6 +1131,7 @@ class FastMCP(Generic[LifespanResultT]):
979
  name=prompt_name,
980
  description=description,
981
  tags=tags,
 
982
  )
983
  self.add_prompt(prompt)
984
 
@@ -1006,6 +1159,7 @@ class FastMCP(Generic[LifespanResultT]):
1006
  name=prompt_name,
1007
  description=description,
1008
  tags=tags,
 
1009
  )
1010
 
1011
  async def run_stdio_async(self) -> None:
@@ -1040,9 +1194,11 @@ class FastMCP(Generic[LifespanResultT]):
1040
  path: Path for the endpoint (defaults to settings.streamable_http_path or settings.sse_path)
1041
  uvicorn_config: Additional configuration for the Uvicorn server
1042
  """
1043
- host = host or self.settings.host
1044
- port = port or self.settings.port
1045
- default_log_level_to_use = (log_level or self.settings.log_level).lower()
 
 
1046
 
1047
  app = self.http_app(path=path, transport=transport, middleware=middleware)
1048
 
@@ -1116,10 +1272,10 @@ class FastMCP(Generic[LifespanResultT]):
1116
  )
1117
  return create_sse_app(
1118
  server=self,
1119
- message_path=message_path or self.settings.message_path,
1120
- sse_path=path or self.settings.sse_path,
1121
  auth=self.auth,
1122
- debug=self.settings.debug,
1123
  middleware=middleware,
1124
  )
1125
 
@@ -1147,6 +1303,8 @@ class FastMCP(Generic[LifespanResultT]):
1147
  self,
1148
  path: str | None = None,
1149
  middleware: list[Middleware] | None = None,
 
 
1150
  transport: Literal["streamable-http", "sse"] = "streamable-http",
1151
  ) -> StarletteWithLifespan:
1152
  """Create a Starlette app using the specified HTTP transport.
@@ -1163,21 +1321,22 @@ class FastMCP(Generic[LifespanResultT]):
1163
  if transport == "streamable-http":
1164
  return create_streamable_http_app(
1165
  server=self,
1166
- streamable_http_path=path or self.settings.streamable_http_path,
 
1167
  event_store=None,
1168
  auth=self.auth,
1169
- json_response=self.settings.json_response,
1170
- stateless_http=self.settings.stateless_http,
1171
- debug=self.settings.debug,
1172
  middleware=middleware,
1173
  )
1174
  elif transport == "sse":
1175
  return create_sse_app(
1176
  server=self,
1177
- message_path=self.settings.message_path,
1178
- sse_path=path or self.settings.sse_path,
1179
  auth=self.auth,
1180
- debug=self.settings.debug,
1181
  middleware=middleware,
1182
  )
1183
 
@@ -1408,28 +1567,12 @@ class FastMCP(Generic[LifespanResultT]):
1408
  route_map_fn: OpenAPIRouteMapFn | None = None,
1409
  mcp_component_fn: OpenAPIComponentFn | None = None,
1410
  mcp_names: dict[str, str] | None = None,
1411
- all_routes_as_tools: bool = False,
1412
  **settings: Any,
1413
  ) -> FastMCPOpenAPI:
1414
  """
1415
  Create a FastMCP server from an OpenAPI specification.
1416
  """
1417
- from .openapi import FastMCPOpenAPI, MCPType, RouteMap
1418
-
1419
- # Deprecated since 2.5.0
1420
- if all_routes_as_tools:
1421
- warnings.warn(
1422
- "The 'all_routes_as_tools' parameter is deprecated and will be removed in a future version. "
1423
- 'Use \'route_maps=[RouteMap(methods="*", pattern=r".*", mcp_type=MCPType.TOOL)]\' instead.',
1424
- DeprecationWarning,
1425
- stacklevel=2,
1426
- )
1427
-
1428
- if all_routes_as_tools and route_maps:
1429
- raise ValueError("Cannot specify both all_routes_as_tools and route_maps")
1430
-
1431
- elif all_routes_as_tools:
1432
- route_maps = [RouteMap(methods="*", pattern=r".*", mcp_type=MCPType.TOOL)]
1433
 
1434
  return FastMCPOpenAPI(
1435
  openapi_spec=openapi_spec,
@@ -1450,7 +1593,6 @@ class FastMCP(Generic[LifespanResultT]):
1450
  route_map_fn: OpenAPIRouteMapFn | None = None,
1451
  mcp_component_fn: OpenAPIComponentFn | None = None,
1452
  mcp_names: dict[str, str] | None = None,
1453
- all_routes_as_tools: bool = False,
1454
  httpx_client_kwargs: dict[str, Any] | None = None,
1455
  **settings: Any,
1456
  ) -> FastMCPOpenAPI:
@@ -1458,22 +1600,7 @@ class FastMCP(Generic[LifespanResultT]):
1458
  Create a FastMCP server from a FastAPI application.
1459
  """
1460
 
1461
- from .openapi import FastMCPOpenAPI, MCPType, RouteMap
1462
-
1463
- # Deprecated since 2.5.0
1464
- if all_routes_as_tools:
1465
- warnings.warn(
1466
- "The 'all_routes_as_tools' parameter is deprecated and will be removed in a future version. "
1467
- 'Use \'route_maps=[RouteMap(methods="*", pattern=r".*", mcp_type=MCPType.TOOL)]\' instead.',
1468
- DeprecationWarning,
1469
- stacklevel=2,
1470
- )
1471
-
1472
- if all_routes_as_tools and route_maps:
1473
- raise ValueError("Cannot specify both all_routes_as_tools and route_maps")
1474
-
1475
- elif all_routes_as_tools:
1476
- route_maps = [RouteMap(methods="*", pattern=r".*", mcp_type=MCPType.TOOL)]
1477
 
1478
  if httpx_client_kwargs is None:
1479
  httpx_client_kwargs = {}
@@ -1543,14 +1670,15 @@ class FastMCP(Generic[LifespanResultT]):
1543
 
1544
  return cls.as_proxy(client, **settings)
1545
 
1546
- def should_include_component(
1547
  self,
1548
- component: Tool | Resource | ResourceTemplate | Prompt,
1549
  ) -> bool:
1550
  """
1551
- Given a set of tags, determine if the tags match the include and exclude tags. Returns True if it should be included; False if it should not.
1552
 
1553
  Rules:
 
1554
  • If both include_tags and exclude_tags are None, return True.
1555
  • If exclude_tags is provided, check each exclude tag:
1556
  - If the exclude tag is a tuple, all tags in the tuple must be present in the input tags to exclude.
@@ -1561,11 +1689,14 @@ class FastMCP(Generic[LifespanResultT]):
1561
  • If include_tags is provided and none of the include tags match, return False.
1562
  • If include_tags is not provided, return True.
1563
  """
1564
- if self.settings.include_tags is None and self.settings.exclude_tags is None:
 
 
 
1565
  return True
1566
 
1567
- if self.settings.exclude_tags is not None:
1568
- for etag in self.settings.exclude_tags:
1569
  if isinstance(etag, tuple):
1570
  if all(et in component.tags for et in etag):
1571
  return False
@@ -1573,8 +1704,8 @@ class FastMCP(Generic[LifespanResultT]):
1573
  if etag in component.tags:
1574
  return False
1575
 
1576
- if self.settings.include_tags is not None:
1577
- for itag in self.settings.include_tags:
1578
  if isinstance(itag, tuple):
1579
  if all(it in component.tags for it in itag):
1580
  return True
@@ -1672,7 +1803,7 @@ def add_resource_prefix(
1672
  # Get the server settings to check for legacy format preference
1673
 
1674
  if prefix_format is None:
1675
- prefix_format = fastmcp.settings.settings.resource_prefix_format
1676
 
1677
  if prefix_format == "protocol":
1678
  # Legacy style: prefix+protocol://path
@@ -1721,7 +1852,7 @@ def remove_resource_prefix(
1721
  return uri
1722
 
1723
  if prefix_format is None:
1724
- prefix_format = fastmcp.settings.settings.resource_prefix_format
1725
 
1726
  if prefix_format == "protocol":
1727
  # Legacy style: prefix+protocol://path
@@ -1781,7 +1912,7 @@ def has_resource_prefix(
1781
  # Get the server settings to check for legacy format preference
1782
 
1783
  if prefix_format is None:
1784
- prefix_format = fastmcp.settings.settings.resource_prefix_format
1785
 
1786
  if prefix_format == "protocol":
1787
  # Legacy style: prefix+protocol://path
 
43
 
44
  import fastmcp
45
  import fastmcp.server
46
+ from fastmcp.exceptions import DisabledError, NotFoundError
 
47
  from fastmcp.prompts import Prompt, PromptManager
48
  from fastmcp.prompts.prompt import FunctionPrompt
49
  from fastmcp.resources import Resource, ResourceManager
 
55
  create_sse_app,
56
  create_streamable_http_app,
57
  )
58
+ from fastmcp.settings import Settings
59
  from fastmcp.tools import ToolManager
60
  from fastmcp.tools.tool import FunctionTool, Tool
61
  from fastmcp.utilities.cache import TimedCache
62
+ from fastmcp.utilities.components import FastMCPComponent
63
  from fastmcp.utilities.logging import get_logger
64
  from fastmcp.utilities.mcp_config import MCPConfig
65
 
 
122
  | None
123
  ) = None,
124
  tags: set[str] | None = None,
 
125
  tool_serializer: Callable[[Any], str] | None = None,
126
  cache_expiration_seconds: float | None = None,
127
  on_duplicate_tools: DuplicateBehavior | None = None,
 
130
  resource_prefix_format: Literal["protocol", "path"] | None = None,
131
  mask_error_details: bool | None = None,
132
  tools: list[Tool | Callable[..., Any]] | None = None,
133
+ dependencies: list[str] | None = None,
134
  include_tags: set[str]
135
  | set[tuple[str, ...]]
136
  | set[str | tuple[str, ...]]
 
139
  | set[tuple[str, ...]]
140
  | set[str | tuple[str, ...]]
141
  | None = None,
142
+ # ---
143
+ # ---
144
+ # --- The following arguments are DEPRECATED ---
145
+ # ---
146
+ # ---
147
+ log_level: str | None = None,
148
+ debug: bool | None = None,
149
+ host: str | None = None,
150
+ port: int | None = None,
151
+ sse_path: str | None = None,
152
+ message_path: str | None = None,
153
+ streamable_http_path: str | None = None,
154
+ json_response: bool | None = None,
155
+ stateless_http: bool | None = None,
156
  ):
157
+ self.resource_prefix_format: Literal["protocol", "path"] = (
158
+ resource_prefix_format or fastmcp.settings.resource_prefix_format
 
 
159
  )
160
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  self.tags: set[str] = tags or set()
162
 
 
163
  self._cache = TimedCache(
164
+ expiration=datetime.timedelta(seconds=cache_expiration_seconds or 0)
 
 
165
  )
166
  self._mounted_servers: dict[str, MountedServer] = {}
167
  self._additional_http_routes: list[BaseRoute] = []
168
  self._tool_manager = ToolManager(
169
  duplicate_behavior=on_duplicate_tools,
170
+ mask_error_details=mask_error_details,
171
  )
172
  self._resource_manager = ResourceManager(
173
  duplicate_behavior=on_duplicate_resources,
174
+ mask_error_details=mask_error_details,
175
  )
176
  self._prompt_manager = PromptManager(
177
  duplicate_behavior=on_duplicate_prompts,
178
+ mask_error_details=mask_error_details,
179
  )
180
  self._tool_serializer = tool_serializer
181
 
 
190
  lifespan=_lifespan_wrapper(self, lifespan),
191
  )
192
 
193
+ if auth is None and fastmcp.settings.default_auth_provider == "bearer_env":
194
  auth = EnvBearerAuthProvider()
195
  self.auth = auth
196
 
 
200
  tool = Tool.from_function(tool, serializer=self._tool_serializer)
201
  self.add_tool(tool)
202
 
203
+ self.include_tags = include_tags
204
+ self.exclude_tags = exclude_tags
205
+
206
  # Set up MCP protocol handlers
207
  self._setup_handlers()
208
+ self.dependencies = dependencies or fastmcp.settings.server_dependencies
209
+
210
+ # handle deprecated settings
211
+ self._handle_deprecated_settings(
212
+ log_level=log_level,
213
+ debug=debug,
214
+ host=host,
215
+ port=port,
216
+ sse_path=sse_path,
217
+ message_path=message_path,
218
+ streamable_http_path=streamable_http_path,
219
+ json_response=json_response,
220
+ stateless_http=stateless_http,
221
+ )
222
 
223
  def __repr__(self) -> str:
224
  return f"{type(self).__name__}({self.name!r})"
225
 
226
+ def _handle_deprecated_settings(
227
+ self,
228
+ log_level: str | None,
229
+ debug: bool | None,
230
+ host: str | None,
231
+ port: int | None,
232
+ sse_path: str | None,
233
+ message_path: str | None,
234
+ streamable_http_path: str | None,
235
+ json_response: bool | None,
236
+ stateless_http: bool | None,
237
+ ) -> None:
238
+ """Handle deprecated settings. Deprecated in 2.8.0."""
239
+ deprecated_settings: dict[str, Any] = {}
240
+
241
+ for name, arg in [
242
+ ("log_level", log_level),
243
+ ("debug", debug),
244
+ ("host", host),
245
+ ("port", port),
246
+ ("sse_path", sse_path),
247
+ ("message_path", message_path),
248
+ ("streamable_http_path", streamable_http_path),
249
+ ("json_response", json_response),
250
+ ("stateless_http", stateless_http),
251
+ ]:
252
+ if arg is not None:
253
+ # Deprecated in 2.8.0
254
+ warnings.warn(
255
+ f"Providing `{name}` when creating a server is deprecated. Provide it when calling `run` or as a global setting instead.",
256
+ DeprecationWarning,
257
+ stacklevel=2,
258
+ )
259
+ deprecated_settings[name] = arg
260
+
261
+ combined_settings = fastmcp.settings.model_dump() | deprecated_settings
262
+ self._deprecated_settings = Settings(**combined_settings)
263
+
264
  @property
265
  def name(self) -> str:
266
  return self._mcp_server.name
 
331
  self._cache.set("tools", tools)
332
  return tools
333
 
334
+ async def get_tool(self, key: str) -> Tool:
335
+ tools = await self.get_tools()
336
+ if key not in tools:
337
+ raise NotFoundError(f"Unknown tool: {key}")
338
+ return tools[key]
339
+
340
  async def get_resources(self) -> dict[str, Resource]:
341
  """Get all registered resources, indexed by registered key."""
342
  if (resources := self._cache.get("resources")) is self._cache.NOT_FOUND:
 
354
  self._cache.set("resources", resources)
355
  return resources
356
 
357
+ async def get_resource(self, key: str) -> Resource:
358
+ resources = await self.get_resources()
359
+ if key not in resources:
360
+ raise NotFoundError(f"Unknown resource: {key}")
361
+ return resources[key]
362
+
363
  async def get_resource_templates(self) -> dict[str, ResourceTemplate]:
364
  """Get all registered resource templates, indexed by registered key."""
365
  if (
 
380
  self._cache.set("resource_templates", templates)
381
  return templates
382
 
383
+ async def get_resource_template(self, key: str) -> ResourceTemplate:
384
+ templates = await self.get_resource_templates()
385
+ if key not in templates:
386
+ raise NotFoundError(f"Unknown resource template: {key}")
387
+ return templates[key]
388
+
389
  async def get_prompts(self) -> dict[str, Prompt]:
390
  """
391
  List all available prompts.
 
405
  self._cache.set("prompts", prompts)
406
  return prompts
407
 
408
+ async def get_prompt(self, key: str) -> Prompt:
409
+ prompts = await self.get_prompts()
410
+ if key not in prompts:
411
+ raise NotFoundError(f"Unknown prompt: {key}")
412
+ return prompts[key]
413
+
414
  def custom_route(
415
  self,
416
  path: str,
 
465
 
466
  mcp_tools: list[MCPTool] = []
467
  for key, tool in tools.items():
468
+ if self._should_enable_component(tool):
469
  mcp_tools.append(tool.to_mcp_tool(name=key))
470
 
471
  return mcp_tools
 
479
  resources = await self.get_resources()
480
  mcp_resources: list[MCPResource] = []
481
  for key, resource in resources.items():
482
+ if self._should_enable_component(resource):
483
  mcp_resources.append(resource.to_mcp_resource(uri=key))
484
  return mcp_resources
485
 
 
492
  templates = await self.get_resource_templates()
493
  mcp_templates: list[MCPResourceTemplate] = []
494
  for key, template in templates.items():
495
+ if self._should_enable_component(template):
496
  mcp_templates.append(template.to_mcp_template(uriTemplate=key))
497
  return mcp_templates
498
 
 
505
  prompts = await self.get_prompts()
506
  mcp_prompts: list[MCPPrompt] = []
507
  for key, prompt in prompts.items():
508
+ if self._should_enable_component(prompt):
509
  mcp_prompts.append(prompt.to_mcp_prompt(name=key))
510
  return mcp_prompts
511
 
512
  async def _mcp_call_tool(
513
  self, key: str, arguments: dict[str, Any]
514
  ) -> list[TextContent | ImageContent | EmbeddedResource]:
515
+ """
516
+ Handle MCP 'callTool' requests.
517
+
518
+ Delegates to _call_tool, which should be overridden by FastMCP subclasses.
519
 
520
  Args:
521
  key: The name of the tool to call
 
528
 
529
  # Create and use context for the entire call
530
  with fastmcp.server.context.Context(fastmcp=self):
531
+ try:
532
+ return await self._call_tool(key, arguments)
533
+ except DisabledError:
534
+ # convert to NotFoundError to avoid leaking tool presence
535
+ raise NotFoundError(f"Unknown tool: {key}")
536
+ except NotFoundError:
537
+ # standardize NotFound message
538
+ raise NotFoundError(f"Unknown tool: {key}")
539
+
540
+ async def _call_tool(
541
+ self, key: str, arguments: dict[str, Any]
542
+ ) -> list[TextContent | ImageContent | EmbeddedResource]:
543
+ """
544
+ Call a tool with raw MCP arguments. FastMCP subclasses should override
545
+ this method, not _mcp_call_tool.
546
 
547
+ Args:
548
+ key: The name of the tool to call arguments: Arguments to pass to
549
+ the tool
550
+
551
+ Returns:
552
+ List of MCP Content objects containing the tool results
553
+ """
554
+
555
+ # Get tool, checking first from our tools, then from the mounted servers
556
+ if self._tool_manager.has_tool(key):
557
+ tool = self._tool_manager.get_tool(key)
558
+ if not tool.enabled:
559
+ raise DisabledError(f"Tool {key!r} is disabled")
560
+ return await self._tool_manager.call_tool(key, arguments)
561
+
562
+ # Check mounted servers to see if they have the tool
563
+ for server in self._mounted_servers.values():
564
+ if server.match_tool(key):
565
+ tool_key = server.strip_tool_prefix(key)
566
+ return await server.server._call_tool(tool_key, arguments)
567
+
568
+ raise NotFoundError(f"Unknown tool: {key!r}")
569
 
570
  async def _mcp_read_resource(self, uri: AnyUrl | str) -> list[ReadResourceContents]:
571
+ """
572
+ Handle MCP 'readResource' requests.
573
+
574
+ Delegates to _read_resource, which should be overridden by FastMCP subclasses.
575
+ """
576
+ logger.debug("Read resource: %s", uri)
577
+
578
+ with fastmcp.server.context.Context(fastmcp=self):
579
+ try:
580
+ return await self._read_resource(uri)
581
+ except DisabledError:
582
+ # convert to NotFoundError to avoid leaking resource presence
583
+ raise NotFoundError(f"Unknown resource: {str(uri)!r}")
584
+ except NotFoundError:
585
+ # standardize NotFound message
586
+ raise NotFoundError(f"Unknown resource: {str(uri)!r}")
587
+
588
+ async def _read_resource(self, uri: AnyUrl | str) -> list[ReadResourceContents]:
589
  """
590
  Read a resource by URI, in the format expected by the low-level MCP
591
  server.
592
  """
593
+ if self._resource_manager.has_resource(uri):
594
+ resource = await self._resource_manager.get_resource(uri)
595
+ if not resource.enabled:
596
+ raise DisabledError(f"Resource {str(uri)!r} is disabled")
597
+ content = await self._resource_manager.read_resource(uri)
598
+ return [
599
+ ReadResourceContents(
600
+ content=content,
601
+ mime_type=resource.mime_type,
602
+ )
603
+ ]
604
+ else:
605
+ for server in self._mounted_servers.values():
606
+ if server.match_resource(str(uri)):
607
+ new_uri = server.strip_resource_prefix(str(uri))
608
+ return await server.server._mcp_read_resource(new_uri)
609
  else:
610
+ raise NotFoundError(f"Unknown resource: {uri}")
 
 
 
 
 
611
 
612
  async def _mcp_get_prompt(
613
  self, name: str, arguments: dict[str, Any] | None = None
614
+ ) -> GetPromptResult:
615
+ """
616
+ Handle MCP 'getPrompt' requests.
617
+
618
+ Delegates to _get_prompt, which should be overridden by FastMCP subclasses.
619
+ """
620
+ logger.debug("Get prompt: %s with %s", name, arguments)
621
+
622
+ with fastmcp.server.context.Context(fastmcp=self):
623
+ try:
624
+ return await self._get_prompt(name, arguments)
625
+ except DisabledError:
626
+ # convert to NotFoundError to avoid leaking prompt presence
627
+ raise NotFoundError(f"Unknown prompt: {name}")
628
+ except NotFoundError:
629
+ # standardize NotFound message
630
+ raise NotFoundError(f"Unknown prompt: {name}")
631
+
632
+ async def _get_prompt(
633
+ self, name: str, arguments: dict[str, Any] | None = None
634
  ) -> GetPromptResult:
635
  """Handle MCP 'getPrompt' requests.
636
 
 
643
  """
644
  logger.debug("Get prompt: %s with %s", name, arguments)
645
 
646
+ # Get prompt, checking first from our prompts, then from the mounted servers
647
+ if self._prompt_manager.has_prompt(name):
648
+ prompt = self._prompt_manager.get_prompt(name)
649
+ if not prompt.enabled:
650
+ raise DisabledError(f"Prompt {name!r} is disabled")
651
+ return await self._prompt_manager.render_prompt(name, arguments)
652
+
653
+ # Check mounted servers to see if they have the prompt
654
+ for server in self._mounted_servers.values():
655
+ if server.match_prompt(name):
656
+ prompt_name = server.strip_prompt_prefix(name)
657
+ return await server.server._mcp_get_prompt(prompt_name, arguments)
 
 
658
 
659
+ raise NotFoundError(f"Unknown prompt: {name}")
660
 
661
  def add_tool(self, tool: Tool) -> None:
662
  """Add a tool to the server.
 
692
  tags: set[str] | None = None,
693
  annotations: ToolAnnotations | dict[str, Any] | None = None,
694
  exclude_args: list[str] | None = None,
695
+ enabled: bool | None = None,
696
  ) -> FunctionTool: ...
697
 
698
  @overload
 
705
  tags: set[str] | None = None,
706
  annotations: ToolAnnotations | dict[str, Any] | None = None,
707
  exclude_args: list[str] | None = None,
708
+ enabled: bool | None = None,
709
  ) -> Callable[[AnyFunction], FunctionTool]: ...
710
 
711
  def tool(
 
717
  tags: set[str] | None = None,
718
  annotations: ToolAnnotations | dict[str, Any] | None = None,
719
  exclude_args: list[str] | None = None,
720
+ enabled: bool | None = None,
721
  ) -> Callable[[AnyFunction], FunctionTool] | FunctionTool:
722
  """Decorator to register a tool.
723
 
 
734
 
735
  Args:
736
  name_or_fn: Either a function (when used as @tool), a string name, or None
737
+ name: Optional name for the tool (keyword-only, alternative to name_or_fn)
738
  description: Optional description of what the tool does
739
  tags: Optional set of tags for categorizing the tool
740
+ annotations: Optional annotations about the tool's behavior (e.g. {"is_async": True})
741
  exclude_args: Optional list of argument names to exclude from the tool schema
742
+ enabled: Optional boolean to enable or disable the tool
743
 
744
  Example:
745
  @server.tool
 
792
  annotations=annotations,
793
  exclude_args=exclude_args,
794
  serializer=self._tool_serializer,
795
+ enabled=enabled,
796
  )
797
  self.add_tool(tool)
798
  return tool
 
821
  tags=tags,
822
  annotations=annotations,
823
  exclude_args=exclude_args,
824
+ enabled=enabled,
825
  )
826
 
827
  def add_resource(self, resource: Resource, key: str | None = None) -> None:
 
888
  description: str | None = None,
889
  mime_type: str | None = None,
890
  tags: set[str] | None = None,
891
+ enabled: bool | None = None,
892
  ) -> Callable[[AnyFunction], Resource | ResourceTemplate]:
893
  """Decorator to register a function as a resource.
894
 
 
911
  description: Optional description of the resource
912
  mime_type: Optional MIME type for the resource
913
  tags: Optional set of tags for categorizing the resource
914
+ enabled: Optional boolean to enable or disable the resource
915
 
916
  Example:
917
  @server.resource("resource://my-resource")
 
976
  description=description,
977
  mime_type=mime_type,
978
  tags=tags,
979
+ enabled=enabled,
980
  )
981
  self.add_template(template)
982
  return template
 
988
  description=description,
989
  mime_type=mime_type,
990
  tags=tags,
991
+ enabled=enabled,
992
  )
993
  self.add_resource(resource)
994
  return resource
 
1017
  name: str | None = None,
1018
  description: str | None = None,
1019
  tags: set[str] | None = None,
1020
+ enabled: bool | None = None,
1021
  ) -> FunctionPrompt: ...
1022
 
1023
  @overload
 
1028
  name: str | None = None,
1029
  description: str | None = None,
1030
  tags: set[str] | None = None,
1031
+ enabled: bool | None = None,
1032
  ) -> Callable[[AnyFunction], FunctionPrompt]: ...
1033
 
1034
  def prompt(
 
1038
  name: str | None = None,
1039
  description: str | None = None,
1040
  tags: set[str] | None = None,
1041
+ enabled: bool | None = None,
1042
  ) -> Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt:
1043
  """Decorator to register a prompt.
1044
 
 
1048
 
1049
  This decorator supports multiple calling patterns:
1050
  - @server.prompt (without parentheses)
1051
+ - @server.prompt() (with empty parentheses)
1052
  - @server.prompt("custom_name") (with name as first argument)
1053
  - @server.prompt(name="custom_name") (with name as keyword argument)
1054
  - server.prompt(function, name="custom_name") (direct function call)
1055
 
1056
  Args:
1057
  name_or_fn: Either a function (when used as @prompt), a string name, or None
1058
+ name: Optional name for the prompt (keyword-only, alternative to name_or_fn)
1059
  description: Optional description of what the prompt does
1060
  tags: Optional set of tags for categorizing the prompt
1061
+ enabled: Optional boolean to enable or disable the prompt
1062
 
1063
  Example:
1064
  @server.prompt
 
1071
  }
1072
  ]
1073
 
1074
+ @server.prompt()
1075
  def analyze_with_context(table_name: str, ctx: Context) -> list[Message]:
1076
  ctx.info(f"Analyzing table {table_name}")
1077
  schema = read_table_schema(table_name)
 
1131
  name=prompt_name,
1132
  description=description,
1133
  tags=tags,
1134
+ enabled=enabled,
1135
  )
1136
  self.add_prompt(prompt)
1137
 
 
1159
  name=prompt_name,
1160
  description=description,
1161
  tags=tags,
1162
+ enabled=enabled,
1163
  )
1164
 
1165
  async def run_stdio_async(self) -> None:
 
1194
  path: Path for the endpoint (defaults to settings.streamable_http_path or settings.sse_path)
1195
  uvicorn_config: Additional configuration for the Uvicorn server
1196
  """
1197
+ host = host or self._deprecated_settings.host
1198
+ port = port or self._deprecated_settings.port
1199
+ default_log_level_to_use = (
1200
+ log_level or self._deprecated_settings.log_level
1201
+ ).lower()
1202
 
1203
  app = self.http_app(path=path, transport=transport, middleware=middleware)
1204
 
 
1272
  )
1273
  return create_sse_app(
1274
  server=self,
1275
+ message_path=message_path or self._deprecated_settings.message_path,
1276
+ sse_path=path or self._deprecated_settings.sse_path,
1277
  auth=self.auth,
1278
+ debug=self._deprecated_settings.debug,
1279
  middleware=middleware,
1280
  )
1281
 
 
1303
  self,
1304
  path: str | None = None,
1305
  middleware: list[Middleware] | None = None,
1306
+ json_response: bool | None = None,
1307
+ stateless_http: bool | None = None,
1308
  transport: Literal["streamable-http", "sse"] = "streamable-http",
1309
  ) -> StarletteWithLifespan:
1310
  """Create a Starlette app using the specified HTTP transport.
 
1321
  if transport == "streamable-http":
1322
  return create_streamable_http_app(
1323
  server=self,
1324
+ streamable_http_path=path
1325
+ or self._deprecated_settings.streamable_http_path,
1326
  event_store=None,
1327
  auth=self.auth,
1328
+ json_response=self._deprecated_settings.json_response,
1329
+ stateless_http=self._deprecated_settings.stateless_http,
1330
+ debug=self._deprecated_settings.debug,
1331
  middleware=middleware,
1332
  )
1333
  elif transport == "sse":
1334
  return create_sse_app(
1335
  server=self,
1336
+ message_path=self._deprecated_settings.message_path,
1337
+ sse_path=path or self._deprecated_settings.sse_path,
1338
  auth=self.auth,
1339
+ debug=self._deprecated_settings.debug,
1340
  middleware=middleware,
1341
  )
1342
 
 
1567
  route_map_fn: OpenAPIRouteMapFn | None = None,
1568
  mcp_component_fn: OpenAPIComponentFn | None = None,
1569
  mcp_names: dict[str, str] | None = None,
 
1570
  **settings: Any,
1571
  ) -> FastMCPOpenAPI:
1572
  """
1573
  Create a FastMCP server from an OpenAPI specification.
1574
  """
1575
+ from .openapi import FastMCPOpenAPI
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1576
 
1577
  return FastMCPOpenAPI(
1578
  openapi_spec=openapi_spec,
 
1593
  route_map_fn: OpenAPIRouteMapFn | None = None,
1594
  mcp_component_fn: OpenAPIComponentFn | None = None,
1595
  mcp_names: dict[str, str] | None = None,
 
1596
  httpx_client_kwargs: dict[str, Any] | None = None,
1597
  **settings: Any,
1598
  ) -> FastMCPOpenAPI:
 
1600
  Create a FastMCP server from a FastAPI application.
1601
  """
1602
 
1603
+ from .openapi import FastMCPOpenAPI
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1604
 
1605
  if httpx_client_kwargs is None:
1606
  httpx_client_kwargs = {}
 
1670
 
1671
  return cls.as_proxy(client, **settings)
1672
 
1673
+ def _should_enable_component(
1674
  self,
1675
+ component: FastMCPComponent,
1676
  ) -> bool:
1677
  """
1678
+ Given a component, determine if it should be enabled. Returns True if it should be enabled; False if it should not.
1679
 
1680
  Rules:
1681
+ • If the component's enabled property is False, always return False.
1682
  • If both include_tags and exclude_tags are None, return True.
1683
  • If exclude_tags is provided, check each exclude tag:
1684
  - If the exclude tag is a tuple, all tags in the tuple must be present in the input tags to exclude.
 
1689
  • If include_tags is provided and none of the include tags match, return False.
1690
  • If include_tags is not provided, return True.
1691
  """
1692
+ if not component.enabled:
1693
+ return False
1694
+
1695
+ if self.include_tags is None and self.exclude_tags is None:
1696
  return True
1697
 
1698
+ if self.exclude_tags is not None:
1699
+ for etag in self.exclude_tags:
1700
  if isinstance(etag, tuple):
1701
  if all(et in component.tags for et in etag):
1702
  return False
 
1704
  if etag in component.tags:
1705
  return False
1706
 
1707
+ if self.include_tags is not None:
1708
+ for itag in self.include_tags:
1709
  if isinstance(itag, tuple):
1710
  if all(it in component.tags for it in itag):
1711
  return True
 
1803
  # Get the server settings to check for legacy format preference
1804
 
1805
  if prefix_format is None:
1806
+ prefix_format = fastmcp.settings.resource_prefix_format
1807
 
1808
  if prefix_format == "protocol":
1809
  # Legacy style: prefix+protocol://path
 
1852
  return uri
1853
 
1854
  if prefix_format is None:
1855
+ prefix_format = fastmcp.settings.resource_prefix_format
1856
 
1857
  if prefix_format == "protocol":
1858
  # Legacy style: prefix+protocol://path
 
1912
  # Get the server settings to check for legacy format preference
1913
 
1914
  if prefix_format is None:
1915
+ prefix_format = fastmcp.settings.resource_prefix_format
1916
 
1917
  if prefix_format == "protocol":
1918
  # Legacy style: prefix+protocol://path
src/fastmcp/settings.py CHANGED
@@ -1,12 +1,16 @@
1
  from __future__ import annotations as _annotations
2
 
3
  import inspect
 
4
  from pathlib import Path
5
- from typing import Annotated, Literal
6
 
7
  from pydantic import Field, model_validator
 
8
  from pydantic_settings import (
9
  BaseSettings,
 
 
10
  SettingsConfigDict,
11
  )
12
  from typing_extensions import Self
@@ -16,17 +20,82 @@ LOG_LEVEL = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
16
  DuplicateBehavior = Literal["warn", "error", "replace", "ignore"]
17
 
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  class Settings(BaseSettings):
20
  """FastMCP settings."""
21
 
22
- model_config = SettingsConfigDict(
23
- env_prefix="FASTMCP_",
24
  env_file=".env",
25
  extra="ignore",
26
  env_nested_delimiter="__",
27
  nested_model_default_partial_update=True,
28
  )
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  home: Path = Path.home() / ".fastmcp"
31
 
32
  test_mode: bool = False
@@ -107,27 +176,6 @@ class Settings(BaseSettings):
107
 
108
  return self
109
 
110
-
111
- class ServerSettings(BaseSettings):
112
- """FastMCP server settings.
113
-
114
- All settings can be configured via environment variables with the prefix FASTMCP_.
115
- For example, FASTMCP_DEBUG=true will set debug=True.
116
- """
117
-
118
- model_config = SettingsConfigDict(
119
- env_prefix="FASTMCP_SERVER_",
120
- env_file=".env",
121
- extra="ignore",
122
- env_nested_delimiter="__",
123
- nested_model_default_partial_update=True,
124
- )
125
-
126
- log_level: Annotated[
127
- LOG_LEVEL,
128
- Field(default_factory=lambda: Settings().log_level),
129
- ]
130
-
131
  # HTTP settings
132
  host: str = "127.0.0.1"
133
  port: int = 8000
@@ -136,15 +184,6 @@ class ServerSettings(BaseSettings):
136
  streamable_http_path: str = "/mcp"
137
  debug: bool = False
138
 
139
- # resource settings
140
- on_duplicate_resources: DuplicateBehavior = "warn"
141
-
142
- # tool settings
143
- on_duplicate_tools: DuplicateBehavior = "warn"
144
-
145
- # prompt settings
146
- on_duplicate_prompts: DuplicateBehavior = "warn"
147
-
148
  # error handling
149
  mask_error_details: Annotated[
150
  bool,
@@ -162,7 +201,7 @@ class ServerSettings(BaseSettings):
162
  ),
163
  ] = False
164
 
165
- dependencies: Annotated[
166
  list[str],
167
  Field(
168
  default_factory=list,
@@ -170,9 +209,6 @@ class ServerSettings(BaseSettings):
170
  ),
171
  ] = []
172
 
173
- # cache settings (for getting attributes from servers, used to avoid repeated calls)
174
- cache_expiration_seconds: float = 0
175
-
176
  # StreamableHTTP settings
177
  json_response: bool = False
178
  stateless_http: bool = (
 
1
  from __future__ import annotations as _annotations
2
 
3
  import inspect
4
+ import warnings
5
  from pathlib import Path
6
+ from typing import Annotated, Any, Literal
7
 
8
  from pydantic import Field, model_validator
9
+ from pydantic.fields import FieldInfo
10
  from pydantic_settings import (
11
  BaseSettings,
12
+ EnvSettingsSource,
13
+ PydanticBaseSettingsSource,
14
  SettingsConfigDict,
15
  )
16
  from typing_extensions import Self
 
20
  DuplicateBehavior = Literal["warn", "error", "replace", "ignore"]
21
 
22
 
23
+ class ExtendedEnvSettingsSource(EnvSettingsSource):
24
+ """
25
+ A special EnvSettingsSource that allows for multiple env var prefixes to be used.
26
+
27
+ Raises a deprecation warning if the old `FASTMCP_SERVER_` prefix is used.
28
+ """
29
+
30
+ def get_field_value(
31
+ self, field: FieldInfo, field_name: str
32
+ ) -> tuple[Any, str, bool]:
33
+ if prefixes := self.config.get("env_prefixes"):
34
+ for prefix in prefixes:
35
+ self.env_prefix = prefix
36
+ env_val, field_key, value_is_complex = super().get_field_value(
37
+ field, field_name
38
+ )
39
+ if env_val is not None:
40
+ if prefix == "FASTMCP_SERVER_":
41
+ # Deprecated in 2.8.0
42
+ warnings.warn(
43
+ "Using `FASTMCP_SERVER_` environment variables is deprecated. Use `FASTMCP_` instead.",
44
+ DeprecationWarning,
45
+ stacklevel=2,
46
+ )
47
+ return env_val, field_key, value_is_complex
48
+
49
+ return super().get_field_value(field, field_name)
50
+
51
+
52
+ class ExtendedSettingsConfigDict(SettingsConfigDict, total=False):
53
+ env_prefixes: list[str] | None
54
+
55
+
56
  class Settings(BaseSettings):
57
  """FastMCP settings."""
58
 
59
+ model_config = ExtendedSettingsConfigDict(
60
+ env_prefixes=["FASTMCP_", "FASTMCP_SERVER_"],
61
  env_file=".env",
62
  extra="ignore",
63
  env_nested_delimiter="__",
64
  nested_model_default_partial_update=True,
65
  )
66
 
67
+ @classmethod
68
+ def settings_customise_sources(
69
+ cls,
70
+ settings_cls: type[BaseSettings],
71
+ init_settings: PydanticBaseSettingsSource,
72
+ env_settings: PydanticBaseSettingsSource,
73
+ dotenv_settings: PydanticBaseSettingsSource,
74
+ file_secret_settings: PydanticBaseSettingsSource,
75
+ ) -> tuple[PydanticBaseSettingsSource, ...]:
76
+ # can remove this classmethod after deprecated FASTMCP_SERVER_ prefix is
77
+ # removed
78
+ return (
79
+ init_settings,
80
+ ExtendedEnvSettingsSource(settings_cls),
81
+ dotenv_settings,
82
+ file_secret_settings,
83
+ )
84
+
85
+ @property
86
+ def settings(self) -> Self:
87
+ """
88
+ This property is for backwards compatibility with FastMCP < 2.8.0,
89
+ which accessed fastmcp.settings.settings
90
+ """
91
+ # Deprecated in 2.8.0
92
+ warnings.warn(
93
+ "Using fastmcp.settings.settings is deprecated. Use fastmcp.settings instead.",
94
+ DeprecationWarning,
95
+ stacklevel=2,
96
+ )
97
+ return self
98
+
99
  home: Path = Path.home() / ".fastmcp"
100
 
101
  test_mode: bool = False
 
176
 
177
  return self
178
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  # HTTP settings
180
  host: str = "127.0.0.1"
181
  port: int = 8000
 
184
  streamable_http_path: str = "/mcp"
185
  debug: bool = False
186
 
 
 
 
 
 
 
 
 
 
187
  # error handling
188
  mask_error_details: Annotated[
189
  bool,
 
201
  ),
202
  ] = False
203
 
204
+ server_dependencies: Annotated[
205
  list[str],
206
  Field(
207
  default_factory=list,
 
209
  ),
210
  ] = []
211
 
 
 
 
212
  # StreamableHTTP settings
213
  json_response: bool = False
214
  stateless_http: bool = (
src/fastmcp/tools/__init__.py CHANGED
@@ -1,4 +1,5 @@
1
  from .tool import Tool, FunctionTool
2
  from .tool_manager import ToolManager
 
3
 
4
- __all__ = ["Tool", "ToolManager", "FunctionTool"]
 
1
  from .tool import Tool, FunctionTool
2
  from .tool_manager import ToolManager
3
+ from .tool_transform import forward, forward_raw
4
 
5
+ __all__ = ["Tool", "ToolManager", "FunctionTool", "forward", "forward_raw"]
src/fastmcp/tools/tool.py CHANGED
@@ -2,28 +2,29 @@ from __future__ import annotations
2
 
3
  import inspect
4
  import json
 
5
  from collections.abc import Callable
6
- from typing import TYPE_CHECKING, Annotated, Any
 
7
 
8
  import pydantic_core
9
  from mcp.types import EmbeddedResource, ImageContent, TextContent, ToolAnnotations
10
  from mcp.types import Tool as MCPTool
11
- from pydantic import BeforeValidator, Field
12
 
13
  import fastmcp
14
  from fastmcp.server.dependencies import get_context
 
15
  from fastmcp.utilities.json_schema import compress_schema
16
  from fastmcp.utilities.logging import get_logger
17
  from fastmcp.utilities.types import (
18
- FastMCPBaseModel,
19
  Image,
20
- _convert_set_defaults,
21
  find_kwarg_by_type,
22
  get_cached_typeadapter,
23
  )
24
 
25
  if TYPE_CHECKING:
26
- pass
27
 
28
  logger = get_logger(__name__)
29
 
@@ -32,24 +33,13 @@ def default_serializer(data: Any) -> str:
32
  return pydantic_core.to_json(data, fallback=str, indent=2).decode()
33
 
34
 
35
- class Tool(FastMCPBaseModel):
36
  """Internal tool registration info."""
37
 
38
- name: str = Field(description="Name of the tool")
39
- description: str | None = Field(
40
- default=None, description="Description of what the tool does"
41
- )
42
  parameters: dict[str, Any] = Field(description="JSON schema for tool parameters")
43
- tags: Annotated[set[str], BeforeValidator(_convert_set_defaults)] = Field(
44
- default_factory=set, description="Tags for the tool"
45
- )
46
  annotations: ToolAnnotations | None = Field(
47
  default=None, description="Additional annotations about the tool"
48
  )
49
- exclude_args: list[str] | None = Field(
50
- default=None,
51
- description="Arguments to exclude from the tool schema, such as State, Memory, or Credential",
52
- )
53
  serializer: Callable[[Any], str] | None = Field(
54
  default=None, description="Optional custom serializer for tool results"
55
  )
@@ -72,6 +62,7 @@ class Tool(FastMCPBaseModel):
72
  annotations: ToolAnnotations | None = None,
73
  exclude_args: list[str] | None = None,
74
  serializer: Callable[[Any], str] | None = None,
 
75
  ) -> FunctionTool:
76
  """Create a Tool from a function."""
77
  return FunctionTool.from_function(
@@ -82,20 +73,43 @@ class Tool(FastMCPBaseModel):
82
  annotations=annotations,
83
  exclude_args=exclude_args,
84
  serializer=serializer,
 
85
  )
86
 
87
- def __eq__(self, other: object) -> bool:
88
- if type(self) is not type(other):
89
- return False
90
- assert isinstance(other, type(self))
91
- return self.model_dump() == other.model_dump()
92
-
93
  async def run(
94
  self, arguments: dict[str, Any]
95
  ) -> list[TextContent | ImageContent | EmbeddedResource]:
96
  """Run the tool with arguments."""
97
  raise NotImplementedError("Subclasses must implement run()")
98
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
  class FunctionTool(Tool):
101
  fn: Callable[..., Any]
@@ -110,65 +124,24 @@ class FunctionTool(Tool):
110
  annotations: ToolAnnotations | None = None,
111
  exclude_args: list[str] | None = None,
112
  serializer: Callable[[Any], str] | None = None,
 
113
  ) -> FunctionTool:
114
  """Create a Tool from a function."""
115
- from fastmcp.server.context import Context
116
-
117
- # Reject functions with *args or **kwargs
118
- sig = inspect.signature(fn)
119
- for param in sig.parameters.values():
120
- if param.kind == inspect.Parameter.VAR_POSITIONAL:
121
- raise ValueError("Functions with *args are not supported as tools")
122
- if param.kind == inspect.Parameter.VAR_KEYWORD:
123
- raise ValueError("Functions with **kwargs are not supported as tools")
124
-
125
- if exclude_args:
126
- for arg_name in exclude_args:
127
- if arg_name not in sig.parameters:
128
- raise ValueError(
129
- f"Parameter '{arg_name}' in exclude_args does not exist in function."
130
- )
131
- param = sig.parameters[arg_name]
132
- if param.default == inspect.Parameter.empty:
133
- raise ValueError(
134
- f"Parameter '{arg_name}' in exclude_args must have a default value."
135
- )
136
 
137
- func_name = name or getattr(fn, "__name__", None) or fn.__class__.__name__
138
 
139
- if func_name == "<lambda>":
140
  raise ValueError("You must provide a name for lambda functions")
141
 
142
- func_doc = description or fn.__doc__
143
-
144
- # if the fn is a callable class, we need to get the __call__ method from here out
145
- if not inspect.isroutine(fn):
146
- fn = fn.__call__
147
- # if the fn is a staticmethod, we need to work with the underlying function
148
- if isinstance(fn, staticmethod):
149
- fn = fn.__func__
150
-
151
- type_adapter = get_cached_typeadapter(fn)
152
- schema = type_adapter.json_schema()
153
-
154
- prune_params: list[str] = []
155
- context_kwarg = find_kwarg_by_type(fn, kwarg_type=Context)
156
- if context_kwarg:
157
- prune_params.append(context_kwarg)
158
- if exclude_args:
159
- prune_params.extend(exclude_args)
160
-
161
- schema = compress_schema(schema, prune_params=prune_params)
162
-
163
  return cls(
164
- fn=fn,
165
- name=func_name,
166
- description=func_doc,
167
- parameters=schema,
168
  tags=tags or set(),
169
  annotations=annotations,
170
- exclude_args=exclude_args,
171
  serializer=serializer,
 
172
  )
173
 
174
  async def run(
@@ -183,7 +156,7 @@ class FunctionTool(Tool):
183
  if context_kwarg and context_kwarg not in arguments:
184
  arguments[context_kwarg] = get_context()
185
 
186
- if fastmcp.settings.settings.tool_attempt_parse_json_args:
187
  # Pre-parse data from JSON in order to handle cases like `["a", "b", "c"]`
188
  # being passed in as JSON inside a string rather than an actual list.
189
  #
@@ -220,6 +193,76 @@ class FunctionTool(Tool):
220
  return _convert_to_content(result, serializer=self.serializer)
221
 
222
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  def _convert_to_content(
224
  result: Any,
225
  serializer: Callable[[Any], str] | None = None,
 
2
 
3
  import inspect
4
  import json
5
+ from abc import ABC, abstractmethod
6
  from collections.abc import Callable
7
+ from dataclasses import dataclass
8
+ from typing import TYPE_CHECKING, Any
9
 
10
  import pydantic_core
11
  from mcp.types import EmbeddedResource, ImageContent, TextContent, ToolAnnotations
12
  from mcp.types import Tool as MCPTool
13
+ from pydantic import Field
14
 
15
  import fastmcp
16
  from fastmcp.server.dependencies import get_context
17
+ from fastmcp.utilities.components import FastMCPComponent
18
  from fastmcp.utilities.json_schema import compress_schema
19
  from fastmcp.utilities.logging import get_logger
20
  from fastmcp.utilities.types import (
 
21
  Image,
 
22
  find_kwarg_by_type,
23
  get_cached_typeadapter,
24
  )
25
 
26
  if TYPE_CHECKING:
27
+ from fastmcp.tools.tool_transform import ArgTransform, TransformedTool
28
 
29
  logger = get_logger(__name__)
30
 
 
33
  return pydantic_core.to_json(data, fallback=str, indent=2).decode()
34
 
35
 
36
+ class Tool(FastMCPComponent, ABC):
37
  """Internal tool registration info."""
38
 
 
 
 
 
39
  parameters: dict[str, Any] = Field(description="JSON schema for tool parameters")
 
 
 
40
  annotations: ToolAnnotations | None = Field(
41
  default=None, description="Additional annotations about the tool"
42
  )
 
 
 
 
43
  serializer: Callable[[Any], str] | None = Field(
44
  default=None, description="Optional custom serializer for tool results"
45
  )
 
62
  annotations: ToolAnnotations | None = None,
63
  exclude_args: list[str] | None = None,
64
  serializer: Callable[[Any], str] | None = None,
65
+ enabled: bool | None = None,
66
  ) -> FunctionTool:
67
  """Create a Tool from a function."""
68
  return FunctionTool.from_function(
 
73
  annotations=annotations,
74
  exclude_args=exclude_args,
75
  serializer=serializer,
76
+ enabled=enabled,
77
  )
78
 
79
+ @abstractmethod
 
 
 
 
 
80
  async def run(
81
  self, arguments: dict[str, Any]
82
  ) -> list[TextContent | ImageContent | EmbeddedResource]:
83
  """Run the tool with arguments."""
84
  raise NotImplementedError("Subclasses must implement run()")
85
 
86
+ @classmethod
87
+ def from_tool(
88
+ cls,
89
+ tool: Tool,
90
+ transform_fn: Callable[..., Any] | None = None,
91
+ name: str | None = None,
92
+ transform_args: dict[str, ArgTransform] | None = None,
93
+ description: str | None = None,
94
+ tags: set[str] | None = None,
95
+ annotations: ToolAnnotations | None = None,
96
+ serializer: Callable[[Any], str] | None = None,
97
+ enabled: bool | None = None,
98
+ ) -> TransformedTool:
99
+ from fastmcp.tools.tool_transform import TransformedTool
100
+
101
+ return TransformedTool.from_tool(
102
+ tool=tool,
103
+ transform_fn=transform_fn,
104
+ name=name,
105
+ transform_args=transform_args,
106
+ description=description,
107
+ tags=tags,
108
+ annotations=annotations,
109
+ serializer=serializer,
110
+ enabled=enabled,
111
+ )
112
+
113
 
114
  class FunctionTool(Tool):
115
  fn: Callable[..., Any]
 
124
  annotations: ToolAnnotations | None = None,
125
  exclude_args: list[str] | None = None,
126
  serializer: Callable[[Any], str] | None = None,
127
+ enabled: bool | None = None,
128
  ) -> FunctionTool:
129
  """Create a Tool from a function."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
+ parsed_fn = ParsedFunction.from_function(fn, exclude_args=exclude_args)
132
 
133
+ if name is None and parsed_fn.name == "<lambda>":
134
  raise ValueError("You must provide a name for lambda functions")
135
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  return cls(
137
+ fn=parsed_fn.fn,
138
+ name=name or parsed_fn.name,
139
+ description=description or parsed_fn.description,
140
+ parameters=parsed_fn.parameters,
141
  tags=tags or set(),
142
  annotations=annotations,
 
143
  serializer=serializer,
144
+ enabled=enabled if enabled is not None else True,
145
  )
146
 
147
  async def run(
 
156
  if context_kwarg and context_kwarg not in arguments:
157
  arguments[context_kwarg] = get_context()
158
 
159
+ if fastmcp.settings.tool_attempt_parse_json_args:
160
  # Pre-parse data from JSON in order to handle cases like `["a", "b", "c"]`
161
  # being passed in as JSON inside a string rather than an actual list.
162
  #
 
193
  return _convert_to_content(result, serializer=self.serializer)
194
 
195
 
196
+ @dataclass
197
+ class ParsedFunction:
198
+ fn: Callable[..., Any]
199
+ name: str
200
+ description: str | None
201
+ parameters: dict[str, Any]
202
+
203
+ @classmethod
204
+ def from_function(
205
+ cls,
206
+ fn: Callable[..., Any],
207
+ exclude_args: list[str] | None = None,
208
+ validate: bool = True,
209
+ ) -> ParsedFunction:
210
+ from fastmcp.server.context import Context
211
+
212
+ if validate:
213
+ sig = inspect.signature(fn)
214
+ # Reject functions with *args or **kwargs
215
+ for param in sig.parameters.values():
216
+ if param.kind == inspect.Parameter.VAR_POSITIONAL:
217
+ raise ValueError("Functions with *args are not supported as tools")
218
+ if param.kind == inspect.Parameter.VAR_KEYWORD:
219
+ raise ValueError(
220
+ "Functions with **kwargs are not supported as tools"
221
+ )
222
+
223
+ # Reject exclude_args that don't exist in the function or don't have a default value
224
+ if exclude_args:
225
+ for arg_name in exclude_args:
226
+ if arg_name not in sig.parameters:
227
+ raise ValueError(
228
+ f"Parameter '{arg_name}' in exclude_args does not exist in function."
229
+ )
230
+ param = sig.parameters[arg_name]
231
+ if param.default == inspect.Parameter.empty:
232
+ raise ValueError(
233
+ f"Parameter '{arg_name}' in exclude_args must have a default value."
234
+ )
235
+
236
+ # collect name and doc before we potentially modify the function
237
+ fn_name = getattr(fn, "__name__", None) or fn.__class__.__name__
238
+ fn_doc = fn.__doc__
239
+
240
+ # if the fn is a callable class, we need to get the __call__ method from here out
241
+ if not inspect.isroutine(fn):
242
+ fn = fn.__call__
243
+ # if the fn is a staticmethod, we need to work with the underlying function
244
+ if isinstance(fn, staticmethod):
245
+ fn = fn.__func__
246
+
247
+ type_adapter = get_cached_typeadapter(fn)
248
+ schema = type_adapter.json_schema()
249
+
250
+ prune_params: list[str] = []
251
+ context_kwarg = find_kwarg_by_type(fn, kwarg_type=Context)
252
+ if context_kwarg:
253
+ prune_params.append(context_kwarg)
254
+ if exclude_args:
255
+ prune_params.extend(exclude_args)
256
+
257
+ schema = compress_schema(schema, prune_params=prune_params)
258
+ return cls(
259
+ fn=fn,
260
+ name=fn_name,
261
+ description=fn_doc,
262
+ parameters=schema,
263
+ )
264
+
265
+
266
  def _convert_to_content(
267
  result: Any,
268
  serializer: Callable[[Any], str] | None = None,
src/fastmcp/tools/tool_manager.py CHANGED
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Any
6
 
7
  from mcp.types import EmbeddedResource, ImageContent, TextContent, ToolAnnotations
8
 
 
9
  from fastmcp.exceptions import NotFoundError, ToolError
10
  from fastmcp.settings import DuplicateBehavior
11
  from fastmcp.tools.tool import Tool
@@ -23,10 +24,10 @@ class ToolManager:
23
  def __init__(
24
  self,
25
  duplicate_behavior: DuplicateBehavior | None = None,
26
- mask_error_details: bool = False,
27
  ):
28
  self._tools: dict[str, Tool] = {}
29
- self.mask_error_details = mask_error_details
30
 
31
  # Default to "warn" if None is provided
32
  if duplicate_behavior is None:
 
6
 
7
  from mcp.types import EmbeddedResource, ImageContent, TextContent, ToolAnnotations
8
 
9
+ from fastmcp import settings
10
  from fastmcp.exceptions import NotFoundError, ToolError
11
  from fastmcp.settings import DuplicateBehavior
12
  from fastmcp.tools.tool import Tool
 
24
  def __init__(
25
  self,
26
  duplicate_behavior: DuplicateBehavior | None = None,
27
+ mask_error_details: bool | None = None,
28
  ):
29
  self._tools: dict[str, Tool] = {}
30
+ self.mask_error_details = mask_error_details or settings.mask_error_details
31
 
32
  # Default to "warn" if None is provided
33
  if duplicate_behavior is None: