Spaces:
Running
Running
Merge branch 'main' into include-exclude
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .github/dependabot.yml +20 -0
- .github/release.yml +10 -0
- .github/workflows/publish.yml +1 -1
- .github/workflows/run-static.yml +1 -1
- .github/workflows/run-tests.yml +1 -1
- README.md +2 -0
- docs/assets/images/tutorial-rest-api-result.png +3 -0
- docs/assets/updates/release-2-7.png +3 -0
- docs/changelog.mdx +1062 -0
- docs/clients/auth/oauth.mdx +3 -3
- docs/docs.json +77 -62
- docs/getting-started/installation.mdx +1 -1
- docs/getting-started/welcome.mdx +10 -4
- docs/integrations/anthropic.mdx +8 -11
- docs/integrations/claude-desktop.mdx +1 -1
- docs/integrations/contrib.mdx +1 -1
- docs/integrations/gemini.mdx +2 -2
- docs/integrations/openai.mdx +3 -4
- docs/patterns/tool-transformation.mdx +454 -0
- docs/servers/composition.mdx +1 -1
- docs/servers/openapi.mdx +18 -37
- docs/servers/prompts.mdx +27 -0
- docs/servers/resources.mdx +27 -0
- docs/servers/tools.mdx +47 -16
- docs/tutorials/create-mcp-server.mdx +198 -0
- docs/tutorials/mcp.mdx +120 -0
- docs/tutorials/rest-api.mdx +203 -0
- docs/updates.mdx +126 -4
- pyproject.toml +2 -0
- src/fastmcp/__init__.py +4 -1
- src/fastmcp/cli/cli.py +3 -2
- src/fastmcp/client/auth/oauth.py +1 -1
- src/fastmcp/client/client.py +3 -1
- src/fastmcp/client/transports.py +43 -37
- src/fastmcp/exceptions.py +4 -0
- src/fastmcp/prompts/prompt.py +8 -18
- src/fastmcp/prompts/prompt_manager.py +3 -2
- src/fastmcp/resources/resource.py +21 -26
- src/fastmcp/resources/resource_manager.py +3 -2
- src/fastmcp/resources/template.py +8 -16
- src/fastmcp/server/auth/providers/bearer_env.py +8 -11
- src/fastmcp/server/dependencies.py +1 -0
- src/fastmcp/server/http.py +2 -1
- src/fastmcp/server/openapi.py +3 -11
- src/fastmcp/server/proxy.py +27 -14
- src/fastmcp/server/server.py +273 -142
- src/fastmcp/settings.py +73 -37
- src/fastmcp/tools/__init__.py +2 -1
- src/fastmcp/tools/tool.py +116 -73
- 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@
|
| 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@
|
| 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@
|
| 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 |
[](https://gofastmcp.com)
|
| 8 |
[](https://pypi.org/project/fastmcp)
|
| 9 |
[](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 |
[](https://gofastmcp.com)
|
| 10 |
[](https://pypi.org/project/fastmcp)
|
| 11 |
[](https://github.com/jlowin/fastmcp/actions/workflows/run-tests.yml)
|
docs/assets/images/tutorial-rest-api-result.png
ADDED
|
Git LFS Details
|
docs/assets/updates/release-2-7.png
ADDED
|
Git LFS Details
|
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 |
-
"
|
| 56 |
{
|
| 57 |
-
"
|
| 58 |
-
"
|
| 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": "
|
| 71 |
-
"icon": "toolbox",
|
| 72 |
"pages": [
|
| 73 |
-
"
|
| 74 |
-
"
|
| 75 |
-
"
|
| 76 |
-
"
|
| 77 |
]
|
| 78 |
},
|
| 79 |
{
|
| 80 |
-
"group": "
|
| 81 |
-
"icon": "shield-check",
|
| 82 |
"pages": [
|
| 83 |
-
"servers/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
]
|
| 85 |
},
|
| 86 |
-
"servers/openapi",
|
| 87 |
-
"servers/proxy",
|
| 88 |
-
"servers/composition",
|
| 89 |
{
|
| 90 |
-
"group": "
|
| 91 |
-
"icon": "upload",
|
| 92 |
"pages": [
|
| 93 |
-
"
|
| 94 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
]
|
| 96 |
-
}
|
| 97 |
-
]
|
| 98 |
-
},
|
| 99 |
-
{
|
| 100 |
-
"group": "Clients",
|
| 101 |
-
"pages": [
|
| 102 |
-
"clients/client",
|
| 103 |
-
"clients/transports",
|
| 104 |
{
|
| 105 |
-
"group": "
|
| 106 |
-
"icon": "user-shield",
|
| 107 |
"pages": [
|
| 108 |
-
"
|
| 109 |
-
"
|
|
|
|
|
|
|
|
|
|
| 110 |
]
|
| 111 |
},
|
| 112 |
-
|
| 113 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
},
|
| 115 |
{
|
| 116 |
-
"
|
|
|
|
| 117 |
"pages": [
|
| 118 |
-
"
|
| 119 |
-
"
|
| 120 |
-
"
|
| 121 |
-
"integrations/gemini",
|
| 122 |
-
"integrations/contrib"
|
| 123 |
]
|
| 124 |
},
|
| 125 |
{
|
| 126 |
-
"
|
|
|
|
| 127 |
"pages": [
|
| 128 |
-
"
|
| 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
|
| 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 |
-
##
|
| 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 |
-
- [
|
| 70 |
-
- [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
##
|
| 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 |
-
##
|
| 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 |
-
##
|
| 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 |
-
##
|
| 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 |
-
###
|
| 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 |
-
###
|
| 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 |
-
|
| 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
|
| 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.
|
| 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 |
-
|
| 64 |
|
| 65 |
```python
|
| 66 |
from fastmcp.server.openapi import RouteMap, MCPType
|
| 67 |
|
| 68 |
DEFAULT_ROUTE_MAPPINGS = [
|
| 69 |
-
|
| 70 |
-
|
| 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,
|
| 98 |
|
| 99 |
-
```python {
|
| 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
|
|
|
|
|
|
|
| 173 |
|
|
|
|
| 174 |
|
| 175 |
-
|
| 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 |
-
|
| 181 |
|
| 182 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+

|
| 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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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
|
| 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
|
| 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.
|
| 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
|
| 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 |
-
|
| 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"<
|
| 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 |
-
|
| 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"<
|
| 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 |
-
|
| 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"<
|
| 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 |
-
|
| 679 |
-
|
| 680 |
-
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 684 |
|
| 685 |
def __repr__(self) -> str:
|
| 686 |
-
return f"<
|
| 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"<
|
| 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,
|
| 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
|
| 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(
|
| 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 =
|
| 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(
|
| 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
|
| 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 |
-
@
|
| 81 |
-
|
| 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 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
|
|
|
|
|
|
| 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 =
|
| 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
|
| 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(
|
| 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 |
|
| 37 |
-
jwks_uri: str | None |
|
| 38 |
-
issuer: str | None |
|
| 39 |
-
audience: str | None |
|
| 40 |
-
required_scopes: list[str] | None |
|
| 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
|
| 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
|
|
|
|
| 159 |
DEFAULT_ROUTE_MAPPINGS = [
|
| 160 |
-
|
| 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 |
-
|
| 190 |
-
|
|
|
|
|
|
|
| 191 |
|
| 192 |
return tools
|
| 193 |
|
|
@@ -203,8 +205,12 @@ class FastMCPProxy(FastMCP):
|
|
| 203 |
else:
|
| 204 |
raise e
|
| 205 |
for resource in client_resources:
|
| 206 |
-
|
| 207 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
|
| 209 |
return resources
|
| 210 |
|
|
@@ -220,8 +226,12 @@ class FastMCPProxy(FastMCP):
|
|
| 220 |
else:
|
| 221 |
raise e
|
| 222 |
for template in client_templates:
|
| 223 |
-
|
| 224 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
|
| 226 |
return templates
|
| 227 |
|
|
@@ -237,24 +247,27 @@ class FastMCPProxy(FastMCP):
|
|
| 237 |
else:
|
| 238 |
raise e
|
| 239 |
for prompt in client_prompts:
|
| 240 |
-
|
| 241 |
-
|
|
|
|
|
|
|
|
|
|
| 242 |
return prompts
|
| 243 |
|
| 244 |
-
async def
|
| 245 |
self, key: str, arguments: dict[str, Any]
|
| 246 |
) -> list[TextContent | ImageContent | EmbeddedResource]:
|
| 247 |
try:
|
| 248 |
-
result = await super().
|
| 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
|
| 256 |
try:
|
| 257 |
-
result = await super().
|
| 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
|
| 274 |
self, name: str, arguments: dict[str, Any] | None = None
|
| 275 |
) -> GetPromptResult:
|
| 276 |
try:
|
| 277 |
-
result = await super().
|
| 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 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
):
|
| 143 |
-
|
| 144 |
-
|
| 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=
|
| 174 |
)
|
| 175 |
self._resource_manager = ResourceManager(
|
| 176 |
duplicate_behavior=on_duplicate_resources,
|
| 177 |
-
mask_error_details=
|
| 178 |
)
|
| 179 |
self._prompt_manager = PromptManager(
|
| 180 |
duplicate_behavior=on_duplicate_prompts,
|
| 181 |
-
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
|
| 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.
|
| 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.
|
| 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.
|
| 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.
|
| 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 |
-
"""
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
|
|
|
|
|
|
|
|
|
| 464 |
|
| 465 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 484 |
else:
|
| 485 |
-
|
| 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 |
-
#
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
if
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
prompt_name = server.strip_prompt_prefix(name)
|
| 519 |
-
return await server.server._mcp_get_prompt(prompt_name, arguments)
|
| 520 |
|
| 521 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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.
|
| 1044 |
-
port = port or self.
|
| 1045 |
-
default_log_level_to_use = (
|
|
|
|
|
|
|
| 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.
|
| 1120 |
-
sse_path=path or self.
|
| 1121 |
auth=self.auth,
|
| 1122 |
-
debug=self.
|
| 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
|
|
|
|
| 1167 |
event_store=None,
|
| 1168 |
auth=self.auth,
|
| 1169 |
-
json_response=self.
|
| 1170 |
-
stateless_http=self.
|
| 1171 |
-
debug=self.
|
| 1172 |
middleware=middleware,
|
| 1173 |
)
|
| 1174 |
elif transport == "sse":
|
| 1175 |
return create_sse_app(
|
| 1176 |
server=self,
|
| 1177 |
-
message_path=self.
|
| 1178 |
-
sse_path=path or self.
|
| 1179 |
auth=self.auth,
|
| 1180 |
-
debug=self.
|
| 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
|
| 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
|
| 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
|
| 1547 |
self,
|
| 1548 |
-
component:
|
| 1549 |
) -> bool:
|
| 1550 |
"""
|
| 1551 |
-
Given a
|
| 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
|
|
|
|
|
|
|
|
|
|
| 1565 |
return True
|
| 1566 |
|
| 1567 |
-
if self.
|
| 1568 |
-
for etag in self.
|
| 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.
|
| 1577 |
-
for itag in self.
|
| 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.
|
| 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.
|
| 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.
|
| 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 =
|
| 23 |
-
|
| 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 |
-
|
| 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
|
|
|
|
| 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
|
| 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 |
-
|
| 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(
|
| 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 |
-
|
| 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 |
-
|
| 138 |
|
| 139 |
-
if
|
| 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=
|
| 166 |
-
description=
|
| 167 |
-
parameters=
|
| 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.
|
| 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 =
|
| 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:
|