mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-24 06:24:18 +02:00
Merge branch 'main' into switch-kvstore
This commit is contained in:
commit
55ac070a59
52 changed files with 2231 additions and 574 deletions
|
|
@ -39,3 +39,10 @@ repos:
|
|||
- id: no-commit-to-branch
|
||||
name: prevent commits to main
|
||||
args: [--branch, main]
|
||||
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.4.1
|
||||
hooks:
|
||||
- id: codespell # See pyproject.toml for args
|
||||
additional_dependencies:
|
||||
- tomli
|
||||
|
|
|
|||
|
|
@ -4,6 +4,124 @@ icon: "list-check"
|
|||
rss: true
|
||||
---
|
||||
|
||||
<Update label="v2.12.4" description="2025-09-26">
|
||||
|
||||
**[v2.12.4: OIDC What You Did There](https://github.com/jlowin/fastmcp/releases/tag/v2.12.4)**
|
||||
|
||||
FastMCP 2.12.4 adds comprehensive OIDC support and expands authentication options with AWS Cognito and Descope providers. The release also includes improvements to logging middleware, URL handling for nested resources, persistent OAuth client registration storage, and various fixes to the experimental OpenAPI parser.
|
||||
|
||||
## What's Changed
|
||||
### New Features 🎉
|
||||
* feat: Add support for OIDC configuration by [@ruhulio](https://github.com/ruhulio) in [#1817](https://github.com/jlowin/fastmcp/pull/1817)
|
||||
### Enhancements 🔧
|
||||
* feat: Move the Starlette context middleware to the front by [@akkuman](https://github.com/akkuman) in [#1812](https://github.com/jlowin/fastmcp/pull/1812)
|
||||
* Refactor Logging and Structured Logging Middleware by [@strawgate](https://github.com/strawgate) in [#1805](https://github.com/jlowin/fastmcp/pull/1805)
|
||||
* Update pull_request_template.md by [@jlowin](https://github.com/jlowin) in [#1824](https://github.com/jlowin/fastmcp/pull/1824)
|
||||
* chore: Set redirect_path default in function by [@ruhulio](https://github.com/ruhulio) in [#1833](https://github.com/jlowin/fastmcp/pull/1833)
|
||||
* feat: Set instructions in code by [@attiks](https://github.com/attiks) in [#1838](https://github.com/jlowin/fastmcp/pull/1838)
|
||||
* Automatically Create inline Snapshots by [@strawgate](https://github.com/strawgate) in [#1779](https://github.com/jlowin/fastmcp/pull/1779)
|
||||
* chore: Cleanup Auth0 redirect_path initialization by [@ruhulio](https://github.com/ruhulio) in [#1842](https://github.com/jlowin/fastmcp/pull/1842)
|
||||
* feat: Add support for Descope Authentication by [@anvibanga](https://github.com/anvibanga) in [#1853](https://github.com/jlowin/fastmcp/pull/1853)
|
||||
* Update descope version badges by [@jlowin](https://github.com/jlowin) in [#1870](https://github.com/jlowin/fastmcp/pull/1870)
|
||||
* Update welcome images by [@jlowin](https://github.com/jlowin) in [#1884](https://github.com/jlowin/fastmcp/pull/1884)
|
||||
* Fix rounded edges of image by [@jlowin](https://github.com/jlowin) in [#1886](https://github.com/jlowin/fastmcp/pull/1886)
|
||||
* optimize test suite by [@zzstoatzz](https://github.com/zzstoatzz) in [#1893](https://github.com/jlowin/fastmcp/pull/1893)
|
||||
* Enhancement: client completions support context_arguments by [@isijoe](https://github.com/isijoe) in [#1906](https://github.com/jlowin/fastmcp/pull/1906)
|
||||
* Update Descope icon by [@anvibanga](https://github.com/anvibanga) in [#1912](https://github.com/jlowin/fastmcp/pull/1912)
|
||||
* Add AWS Cognito OAuth Provider for Enterprise Authentication by [@stephaneberle9](https://github.com/stephaneberle9) in [#1873](https://github.com/jlowin/fastmcp/pull/1873)
|
||||
* Fix typos discovered by codespell by [@cclauss](https://github.com/cclauss) in [#1922](https://github.com/jlowin/fastmcp/pull/1922)
|
||||
* Use lowercase namespace for fastmcp logger by [@jlowin](https://github.com/jlowin) in [#1791](https://github.com/jlowin/fastmcp/pull/1791)
|
||||
### Fixes 🐞
|
||||
* Update quickstart.mdx by [@radi-dev](https://github.com/radi-dev) in [#1821](https://github.com/jlowin/fastmcp/pull/1821)
|
||||
* Remove extraneous union import by [@jlowin](https://github.com/jlowin) in [#1823](https://github.com/jlowin/fastmcp/pull/1823)
|
||||
* Delay import of Provider classes until FastMCP Server Creation by [@strawgate](https://github.com/strawgate) in [#1820](https://github.com/jlowin/fastmcp/pull/1820)
|
||||
* fix: correct documentation link in deprecation warning by [@strawgate](https://github.com/strawgate) in [#1828](https://github.com/jlowin/fastmcp/pull/1828)
|
||||
* fix: Increase default 3s timeout on Pytest by [@dacamposol](https://github.com/dacamposol) in [#1866](https://github.com/jlowin/fastmcp/pull/1866)
|
||||
* fix: Improve URL handling in OIDCConfiguration by [@ruhulio](https://github.com/ruhulio) in [#1850](https://github.com/jlowin/fastmcp/pull/1850)
|
||||
* fix: correct typing for on_read_resource middleware method by [@strawgate](https://github.com/strawgate) in [#1858](https://github.com/jlowin/fastmcp/pull/1858)
|
||||
* feat(experimental/openapi): replace $ref in additionalProperties; add tests by [@jlowin](https://github.com/jlowin) in [#1735](https://github.com/jlowin/fastmcp/pull/1735)
|
||||
* Honor client supplied scopes during registration by [@dmikusa](https://github.com/dmikusa) in [#1860](https://github.com/jlowin/fastmcp/pull/1860)
|
||||
* Fix: FastAPI list parameter parsing in experimental OpenAPI parser by [@jlowin](https://github.com/jlowin) in [#1834](https://github.com/jlowin/fastmcp/pull/1834)
|
||||
* Add log level support for stdio and HTTP transports by [@jlowin](https://github.com/jlowin) in [#1840](https://github.com/jlowin/fastmcp/pull/1840)
|
||||
* Fix OAuth pre-flight check to accept HTTP 200 responses by [@jlowin](https://github.com/jlowin) in [#1874](https://github.com/jlowin/fastmcp/pull/1874)
|
||||
* Fix: Preserve OpenAPI parameter descriptions in experimental parser by [@shlomo666](https://github.com/shlomo666) in [#1877](https://github.com/jlowin/fastmcp/pull/1877)
|
||||
* Add persistent storage for OAuth client registrations by [@jlowin](https://github.com/jlowin) in [#1879](https://github.com/jlowin/fastmcp/pull/1879)
|
||||
* docs: update release dates based on github releases by [@lodu](https://github.com/lodu) in [#1890](https://github.com/jlowin/fastmcp/pull/1890)
|
||||
* Small updates to Sampling types by [@strawgate](https://github.com/strawgate) in [#1882](https://github.com/jlowin/fastmcp/pull/1882)
|
||||
* remove lockfile smart_home example by [@zzstoatzz](https://github.com/zzstoatzz) in [#1892](https://github.com/jlowin/fastmcp/pull/1892)
|
||||
* Fix: Remove JSON schema title metadata while preserving parameters named 'title' by [@jlowin](https://github.com/jlowin) in [#1872](https://github.com/jlowin/fastmcp/pull/1872)
|
||||
* Fix: get_resource_url nested URL handling by [@raphael-linx](https://github.com/raphael-linx) in [#1914](https://github.com/jlowin/fastmcp/pull/1914)
|
||||
* Clean up code for creating the resource url by [@jlowin](https://github.com/jlowin) in [#1916](https://github.com/jlowin/fastmcp/pull/1916)
|
||||
* Fix route count logging in OpenAPI server by [@zzstoatzz](https://github.com/zzstoatzz) in [#1928](https://github.com/jlowin/fastmcp/pull/1928)
|
||||
### Docs 📚
|
||||
* docs: make Gemini CLI integration discoverable by [@jackwotherspoon](https://github.com/jackwotherspoon) in [#1827](https://github.com/jlowin/fastmcp/pull/1827)
|
||||
* docs: update NEW tags for AI assistant integrations by [@jackwotherspoon](https://github.com/jackwotherspoon) in [#1829](https://github.com/jlowin/fastmcp/pull/1829)
|
||||
* Update wordmark by [@jlowin](https://github.com/jlowin) in [#1832](https://github.com/jlowin/fastmcp/pull/1832)
|
||||
* docs: improve OAuth and OIDC Proxy documentation by [@jlowin](https://github.com/jlowin) in [#1880](https://github.com/jlowin/fastmcp/pull/1880)
|
||||
* Update readme + welcome docs by [@jlowin](https://github.com/jlowin) in [#1883](https://github.com/jlowin/fastmcp/pull/1883)
|
||||
* Update dark mode image in README by [@jlowin](https://github.com/jlowin) in [#1885](https://github.com/jlowin/fastmcp/pull/1885)
|
||||
|
||||
## New Contributors
|
||||
* [@radi-dev](https://github.com/radi-dev) made their first contribution in [#1821](https://github.com/jlowin/fastmcp/pull/1821)
|
||||
* [@akkuman](https://github.com/akkuman) made their first contribution in [#1812](https://github.com/jlowin/fastmcp/pull/1812)
|
||||
* [@ruhulio](https://github.com/ruhulio) made their first contribution in [#1817](https://github.com/jlowin/fastmcp/pull/1817)
|
||||
* [@attiks](https://github.com/attiks) made their first contribution in [#1838](https://github.com/jlowin/fastmcp/pull/1838)
|
||||
* [@anvibanga](https://github.com/anvibanga) made their first contribution in [#1853](https://github.com/jlowin/fastmcp/pull/1853)
|
||||
* [@shlomo666](https://github.com/shlomo666) made their first contribution in [#1877](https://github.com/jlowin/fastmcp/pull/1877)
|
||||
* [@lodu](https://github.com/lodu) made their first contribution in [#1890](https://github.com/jlowin/fastmcp/pull/1890)
|
||||
* [@isijoe](https://github.com/isijoe) made their first contribution in [#1906](https://github.com/jlowin/fastmcp/pull/1906)
|
||||
* [@raphael-linx](https://github.com/raphael-linx) made their first contribution in [#1914](https://github.com/jlowin/fastmcp/pull/1914)
|
||||
* [@stephaneberle9](https://github.com/stephaneberle9) made their first contribution in [#1873](https://github.com/jlowin/fastmcp/pull/1873)
|
||||
* [@cclauss](https://github.com/cclauss) made their first contribution in [#1922](https://github.com/jlowin/fastmcp/pull/1922)
|
||||
|
||||
**Full Changelog**: [v2.12.3...v2.12.4](https://github.com/jlowin/fastmcp/compare/v2.12.3...v2.12.4)
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="v2.12.3" description="2025-09-17">
|
||||
|
||||
**[v2.12.3: Double Time](https://github.com/jlowin/fastmcp/releases/tag/v2.12.3)**
|
||||
|
||||
FastMCP 2.12.3 focuses on performance and developer experience improvements based on community feedback. This release includes optimized auth provider imports that reduce server startup time, enhanced OIDC authentication flows with proper token management, and several reliability fixes for OAuth proxy configurations. The addition of automatic inline snapshot creation significantly improves the testing experience for contributors.
|
||||
|
||||
## What's Changed
|
||||
### New Features 🎉
|
||||
* feat: Support setting MCP log level via transport configuration by [@jlowin](https://github.com/jlowin) in [#1756](https://github.com/jlowin/fastmcp/pull/1756)
|
||||
### Enhancements 🔧
|
||||
* Add client-side auth support for mcp install cursor command by [@jlowin](https://github.com/jlowin) in [#1747](https://github.com/jlowin/fastmcp/pull/1747)
|
||||
* Automatically Create inline Snapshots by [@strawgate](https://github.com/strawgate) in [#1779](https://github.com/jlowin/fastmcp/pull/1779)
|
||||
* Use lowercase namespace for fastmcp logger by [@jlowin](https://github.com/jlowin) in [#1791](https://github.com/jlowin/fastmcp/pull/1791)
|
||||
### Fixes 🐞
|
||||
* fix: correct merge mistake during auth0 refactor by [@strawgate](https://github.com/strawgate) in [#1742](https://github.com/jlowin/fastmcp/pull/1742)
|
||||
* Remove extraneous union import by [@jlowin](https://github.com/jlowin) in [#1823](https://github.com/jlowin/fastmcp/pull/1823)
|
||||
* Delay import of Provider classes until FastMCP Server Creation by [@strawgate](https://github.com/strawgate) in [#1820](https://github.com/jlowin/fastmcp/pull/1820)
|
||||
* fix: refactor OIDC configuration provider for proper token management by [@strawgate](https://github.com/strawgate) in [#1751](https://github.com/jlowin/fastmcp/pull/1751)
|
||||
* Fix smart_home example imports by [@strawgate](https://github.com/strawgate) in [#1753](https://github.com/jlowin/fastmcp/pull/1753)
|
||||
* fix: correct oauth proxy initialization of client by [@strawgate](https://github.com/strawgate) in [#1759](https://github.com/jlowin/fastmcp/pull/1759)
|
||||
* Fix: return empty string when prompts have no arguments by [@jlowin](https://github.com/jlowin) in [#1766](https://github.com/jlowin/fastmcp/pull/1766)
|
||||
* Fix async server callbacks by [@strawgate](https://github.com/strawgate) in [#1774](https://github.com/jlowin/fastmcp/pull/1774)
|
||||
* Fix error when retrieving Completion API errors by [@strawgate](https://github.com/strawgate) in [#1785](https://github.com/jlowin/fastmcp/pull/1785)
|
||||
* fix: correct documentation link in deprecation warning by [@strawgate](https://github.com/strawgate) in [#1828](https://github.com/jlowin/fastmcp/pull/1828)
|
||||
### Docs 📚
|
||||
* Add migration docs for 2.12 by [@jlowin](https://github.com/jlowin) in [#1745](https://github.com/jlowin/fastmcp/pull/1745)
|
||||
* Update docs for default sampling implementation to mention OpenAI API Key by [@strawgate](https://github.com/strawgate) in [#1763](https://github.com/jlowin/fastmcp/pull/1763)
|
||||
* Add tip about sampling prompts and user_context to sampling documentation by [@jlowin](https://github.com/jlowin) in [#1764](https://github.com/jlowin/fastmcp/pull/1764)
|
||||
* Update quickstart.mdx by [@radi-dev](https://github.com/radi-dev) in [#1821](https://github.com/jlowin/fastmcp/pull/1821)
|
||||
### Other Changes 🦾
|
||||
* Replace Marvin with Claude Code in CI by [@jlowin](https://github.com/jlowin) in [#1800](https://github.com/jlowin/fastmcp/pull/1800)
|
||||
* Refactor logging and structured logging middleware by [@strawgate](https://github.com/strawgate) in [#1805](https://github.com/jlowin/fastmcp/pull/1805)
|
||||
* feat: Move the Starlette context middleware to the front by [@akkuman](https://github.com/akkuman) in [#1812](https://github.com/jlowin/fastmcp/pull/1812)
|
||||
* feat: Add support for OIDC configuration by [@ruhulio](https://github.com/ruhulio) in [#1817](https://github.com/jlowin/fastmcp/pull/1817)
|
||||
|
||||
## New Contributors
|
||||
* [@radi-dev](https://github.com/radi-dev) made their first contribution in [#1821](https://github.com/jlowin/fastmcp/pull/1821)
|
||||
* [@akkuman](https://github.com/akkuman) made their first contribution in [#1812](https://github.com/jlowin/fastmcp/pull/1812)
|
||||
* [@ruhulio](https://github.com/ruhulio) made their first contribution in [#1817](https://github.com/jlowin/fastmcp/pull/1817)
|
||||
|
||||
**Full Changelog**: [v2.12.2...v2.12.3](https://github.com/jlowin/fastmcp/compare/v2.12.2...v2.12.3)
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="v2.12.2" description="2025-09-03">
|
||||
|
||||
**[v2.12.2: Perchance to Stream](https://github.com/jlowin/fastmcp/releases/tag/v2.12.2)**
|
||||
|
|
@ -1757,7 +1875,7 @@ This release is highlighted by the ability to handle complex JSON objects as MCP
|
|||
### New Features 🎉
|
||||
|
||||
* Set up multiple os tests by [@jlowin](https://github.com/jlowin) in [#44](https://github.com/jlowin/fastmcp/pull/44)
|
||||
* Changes to accomodate windows users. by [@justjoehere](https://github.com/justjoehere) in [#42](https://github.com/jlowin/fastmcp/pull/42)
|
||||
* Changes to accommodate windows users. by [@justjoehere](https://github.com/justjoehere) in [#42](https://github.com/jlowin/fastmcp/pull/42)
|
||||
* Handle complex inputs by [@jurasofish](https://github.com/jurasofish) in [#31](https://github.com/jlowin/fastmcp/pull/31)
|
||||
|
||||
### Docs 📚
|
||||
|
|
|
|||
|
|
@ -11,9 +11,6 @@
|
|||
},
|
||||
"decoration": "windows"
|
||||
},
|
||||
"contextual": {
|
||||
"options": ["copy", "view"]
|
||||
},
|
||||
"banner": {
|
||||
"content": "Host your server on [FastMCP Cloud](https://fastmcp.cloud) for free!"
|
||||
},
|
||||
|
|
@ -22,6 +19,12 @@
|
|||
"light": "#4cc9f0",
|
||||
"primary": "#2d00f7"
|
||||
},
|
||||
"contextual": {
|
||||
"options": [
|
||||
"copy",
|
||||
"view"
|
||||
]
|
||||
},
|
||||
"description": "The fast, Pythonic way to build MCP servers and clients.",
|
||||
"favicon": {
|
||||
"dark": "/assets/brand/favicon.svg",
|
||||
|
|
@ -51,8 +54,8 @@
|
|||
"links": [
|
||||
{
|
||||
"href": "https://fastmcp.cloud",
|
||||
"label": "FastMCP Cloud",
|
||||
"icon": "cloud"
|
||||
"icon": "cloud",
|
||||
"label": "FastMCP Cloud"
|
||||
}
|
||||
],
|
||||
"primary": {
|
||||
|
|
@ -129,7 +132,10 @@
|
|||
{
|
||||
"group": "Essentials",
|
||||
"icon": "cube",
|
||||
"pages": ["clients/client", "clients/transports"]
|
||||
"pages": [
|
||||
"clients/client",
|
||||
"clients/transports"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Core Operations",
|
||||
|
|
@ -155,7 +161,10 @@
|
|||
{
|
||||
"group": "Authentication",
|
||||
"icon": "user-shield",
|
||||
"pages": ["clients/auth/oauth", "clients/auth/bearer"]
|
||||
"pages": [
|
||||
"clients/auth/oauth",
|
||||
"clients/auth/bearer"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -172,6 +181,7 @@
|
|||
"integrations/azure",
|
||||
"integrations/descope",
|
||||
"integrations/github",
|
||||
"integrations/scalekit",
|
||||
"integrations/google",
|
||||
"integrations/workos"
|
||||
]
|
||||
|
|
@ -237,7 +247,10 @@
|
|||
"tab": "Documentation"
|
||||
},
|
||||
{
|
||||
"pages": ["updates", "changelog"],
|
||||
"pages": [
|
||||
"updates",
|
||||
"changelog"
|
||||
],
|
||||
"tab": "What's New"
|
||||
},
|
||||
{
|
||||
|
|
@ -321,16 +334,21 @@
|
|||
"python-sdk/fastmcp-server-auth-__init__",
|
||||
"python-sdk/fastmcp-server-auth-auth",
|
||||
"python-sdk/fastmcp-server-auth-oauth_proxy",
|
||||
"python-sdk/fastmcp-server-auth-oidc_proxy",
|
||||
{
|
||||
"group": "providers",
|
||||
"pages": [
|
||||
"python-sdk/fastmcp-server-auth-providers-__init__",
|
||||
"python-sdk/fastmcp-server-auth-providers-auth0",
|
||||
"python-sdk/fastmcp-server-auth-providers-aws",
|
||||
"python-sdk/fastmcp-server-auth-providers-azure",
|
||||
"python-sdk/fastmcp-server-auth-providers-bearer",
|
||||
"python-sdk/fastmcp-server-auth-providers-descope",
|
||||
"python-sdk/fastmcp-server-auth-providers-github",
|
||||
"python-sdk/fastmcp-server-auth-providers-google",
|
||||
"python-sdk/fastmcp-server-auth-providers-in_memory",
|
||||
"python-sdk/fastmcp-server-auth-providers-jwt",
|
||||
"python-sdk/fastmcp-server-auth-providers-scalekit",
|
||||
"python-sdk/fastmcp-server-auth-providers-workos"
|
||||
]
|
||||
},
|
||||
|
|
@ -411,6 +429,7 @@
|
|||
]
|
||||
},
|
||||
"python-sdk/fastmcp-utilities-openapi",
|
||||
"python-sdk/fastmcp-utilities-storage",
|
||||
"python-sdk/fastmcp-utilities-tests",
|
||||
"python-sdk/fastmcp-utilities-types"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,159 +1,157 @@
|
|||
---
|
||||
title: ChatGPT 🤝 FastMCP
|
||||
sidebarTitle: ChatGPT
|
||||
description: Connect FastMCP servers to ChatGPT Deep Research
|
||||
description: Connect FastMCP servers to ChatGPT in Chat and Deep Research modes
|
||||
icon: message-smile
|
||||
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
ChatGPT supports MCP servers through remote HTTP connections, allowing you to extend ChatGPT's capabilities with custom tools and knowledge from your FastMCP servers.
|
||||
|
||||
<Note>
|
||||
MCP integration with ChatGPT is currently limited to **Deep Research** functionality and is not available for general chat. This feature is available for ChatGPT Pro, Team, Enterprise, and Edu users.
|
||||
</Note>
|
||||
ChatGPT supports MCP servers through remote HTTP connections in two modes: **Chat mode** for interactive conversations and **Deep Research mode** for comprehensive information retrieval.
|
||||
|
||||
<Tip>
|
||||
OpenAI's official MCP documentation and examples are built with **FastMCP v2**! Check out their [simple Deep Research-style MCP server example](https://github.com/openai/sample-deep-research-mcp) for a quick reference similar to the one in this document, or their [more complete Deep Research example](https://github.com/openai/openai-cookbook/tree/main/examples/deep_research_api/how_to_build_a_deep_research_mcp_server) from the OpenAI Cookbook, which includes vector search and more.
|
||||
**Developer Mode Required for Chat Mode**: To use MCP servers in regular ChatGPT conversations, you must first enable Developer Mode in your ChatGPT settings. This feature is available for ChatGPT Pro, Team, Enterprise, and Edu users.
|
||||
</Tip>
|
||||
|
||||
## Deep Research
|
||||
<Note>
|
||||
OpenAI's official MCP documentation and examples are built with **FastMCP v2**! Learn more from their [MCP documentation](https://platform.openai.com/docs/mcp) and [Developer Mode guide](https://platform.openai.com/docs/guides/developer-mode).
|
||||
</Note>
|
||||
|
||||
ChatGPT's Deep Research feature requires MCP servers to be internet-accessible HTTP endpoints with **exactly two specific tools**:
|
||||
## Build a Server
|
||||
|
||||
- **`search`**: For searching through your resources and returning matching IDs
|
||||
- **`fetch`**: For retrieving the full content of specific resources by ID
|
||||
First, let's create a simple FastMCP server:
|
||||
|
||||
<Warning>
|
||||
If your server doesn't implement both `search` and `fetch` tools with the correct signatures, ChatGPT will show the error: "This MCP server doesn't implement our specification". Both tools are required.
|
||||
</Warning>
|
||||
|
||||
### Tool Descriptions Matter
|
||||
|
||||
Since ChatGPT needs to understand how to use your tools effectively, **write detailed tool descriptions**. The description teaches ChatGPT how to form queries, what parameters to use, and what to expect from your data. Poor descriptions lead to poor search results.
|
||||
|
||||
### Create a Server
|
||||
|
||||
A Deep Research-compatible server must implement these two required tools:
|
||||
|
||||
- **`search(query: str)`** - Takes a query of any kind and returns matching record IDs
|
||||
- **`fetch(id: str)`** - Takes an ID and returns the record
|
||||
|
||||
**Critical**: Write detailed docstrings for both tools. These descriptions teach ChatGPT how to use your tools effectively. Poor descriptions lead to poor search results.
|
||||
|
||||
The `search` tool should take a query (of any kind!) and return IDs. The `fetch` tool should take an ID and return the record.
|
||||
|
||||
Here's a reference server implementation you can adapt (see also [OpenAI's sample server](https://github.com/openai/sample-deep-research-mcp) for comparison):
|
||||
|
||||
```python server.py [expandable]
|
||||
import json
|
||||
from pathlib import Path
|
||||
from dataclasses import dataclass
|
||||
```python server.py
|
||||
from fastmcp import FastMCP
|
||||
import random
|
||||
|
||||
@dataclass
|
||||
class Record:
|
||||
id: str
|
||||
title: str
|
||||
text: str
|
||||
metadata: dict
|
||||
mcp = FastMCP("Demo Server")
|
||||
|
||||
def create_server(
|
||||
records_path: Path | str,
|
||||
name: str | None = None,
|
||||
instructions: str | None = None,
|
||||
) -> FastMCP:
|
||||
"""Create a FastMCP server that can search and fetch records from a JSON file."""
|
||||
records = json.loads(Path(records_path).read_text())
|
||||
|
||||
RECORDS = [Record(**r) for r in records]
|
||||
LOOKUP = {r.id: r for r in RECORDS}
|
||||
|
||||
mcp = FastMCP(name=name or "Deep Research MCP", instructions=instructions)
|
||||
|
||||
@mcp.tool()
|
||||
async def search(query: str):
|
||||
"""
|
||||
Simple unranked keyword search across title, text, and metadata.
|
||||
Searches for any of the query terms in the record content.
|
||||
Returns a list of matching record IDs for ChatGPT to fetch.
|
||||
"""
|
||||
toks = query.lower().split()
|
||||
ids = []
|
||||
for r in RECORDS:
|
||||
record_txt = " ".join(
|
||||
[r.title, r.text, " ".join(r.metadata.values())]
|
||||
).lower()
|
||||
if any(t in record_txt for t in toks):
|
||||
ids.append(r.id)
|
||||
|
||||
return {"ids": ids}
|
||||
|
||||
@mcp.tool()
|
||||
async def fetch(id: str):
|
||||
"""
|
||||
Fetch a record by ID.
|
||||
Returns the complete record data for ChatGPT to analyze and cite.
|
||||
"""
|
||||
if id not in LOOKUP:
|
||||
raise ValueError(f"Unknown record ID: {id}")
|
||||
return LOOKUP[id]
|
||||
|
||||
return mcp
|
||||
@mcp.tool
|
||||
def roll_dice(sides: int = 6) -> int:
|
||||
"""Roll a dice with the specified number of sides."""
|
||||
return random.randint(1, sides)
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp = create_server("path/to/records.json")
|
||||
mcp.run(transport="http", port=8000)
|
||||
```
|
||||
|
||||
### Deploy the Server
|
||||
### Deploy Your Server
|
||||
|
||||
Your server must be deployed to a public URL in order for ChatGPT to access it.
|
||||
|
||||
For development, you can use tools like `ngrok` to temporarily expose a locally-running server to the internet. We'll do that for this example (you may need to install `ngrok` and create a free account), but you can use any other method to deploy your server.
|
||||
|
||||
Assuming you saved the above code as `server.py`, you can run the following two commands in two separate terminals to deploy your server and expose it to the internet:
|
||||
Your server must be accessible from the internet. For development, use `ngrok`:
|
||||
|
||||
<CodeGroup>
|
||||
```bash FastMCP server
|
||||
```bash Terminal 1
|
||||
python server.py
|
||||
```
|
||||
|
||||
```bash ngrok
|
||||
```bash Terminal 2
|
||||
ngrok http 8000
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<Warning>
|
||||
This exposes your unauthenticated server to the internet. Only run this command in a safe environment if you understand the risks.
|
||||
</Warning>
|
||||
Note your public URL (e.g., `https://abc123.ngrok.io`) for the next steps.
|
||||
|
||||
### Connect to ChatGPT
|
||||
## Chat Mode
|
||||
|
||||
Replace `https://your-server-url.com` with the actual URL of your server (such as your ngrok URL).
|
||||
Chat mode lets you use MCP tools directly in ChatGPT conversations. See [OpenAI's Developer Mode guide](https://platform.openai.com/docs/guides/developer-mode) for the latest requirements.
|
||||
|
||||
### Add to ChatGPT
|
||||
|
||||
#### 1. Enable Developer Mode
|
||||
|
||||
1. Open ChatGPT and go to **Settings** → **Connectors**
|
||||
2. Click **Add custom connector**
|
||||
3. Enter your server details:
|
||||
- **Name**: Library Catalog
|
||||
- **URL**: Your server URL, including the path.
|
||||
- **Note**: Ensure your URL includes the correct path for the transport you’re using. The defaults are /sse/ for SSE (e.g., https://abc123.ngrok.io/sse/) and /mcp/ for HTTP (e.g., https://abc123.ngrok.io/mcp/).
|
||||
- **Description**: A library catalog for searching and retrieving books
|
||||
2. Under **Advanced**, toggle **Developer Mode** to enabled
|
||||
|
||||
#### Test the Connection
|
||||
#### 2. Create Connector
|
||||
|
||||
1. Start a new chat in ChatGPT
|
||||
2. Click **Tools** → **Run deep research**
|
||||
3. Select your **Library Catalog** connector as a source
|
||||
4. Ask questions like:
|
||||
- "Search for Python programming books"
|
||||
- "Find books about AI and machine learning"
|
||||
- "Show me books by the Python Software Foundation"
|
||||
1. In **Settings** → **Connectors**, click **Create**
|
||||
2. Enter:
|
||||
- **Name**: Your server name
|
||||
- **Server URL**: `https://your-server.ngrok.io/mcp/`
|
||||
3. Check **I trust this provider**
|
||||
4. Add authentication if needed
|
||||
5. Click **Create**
|
||||
|
||||
ChatGPT will use your server's search and fetch tools to find relevant information and cite the sources in its response.
|
||||
<Note>
|
||||
**Without Developer Mode**: If you don't have search/fetch tools, ChatGPT will reject the server. With Developer Mode enabled, you don't need search/fetch tools for Chat mode.
|
||||
</Note>
|
||||
|
||||
### Troubleshooting
|
||||
#### 3. Use in Chat
|
||||
|
||||
#### "This MCP server doesn't implement our specification"
|
||||
1. Start a new chat
|
||||
2. Click the **+** button → **More** → **Developer Mode**
|
||||
3. **Enable your MCP server connector** (required - the connector must be explicitly added to each chat)
|
||||
4. Now you can use your tools:
|
||||
|
||||
Example usage:
|
||||
- "Roll a 20-sided dice"
|
||||
- "Roll dice" (uses default 6 sides)
|
||||
|
||||
<Tip>
|
||||
The connector must be explicitly enabled in each chat session through Developer Mode. Once added, it remains active for the entire conversation.
|
||||
</Tip>
|
||||
|
||||
### Skip Confirmations
|
||||
|
||||
Use `annotations={"readOnlyHint": True}` to skip confirmation prompts for read-only tools:
|
||||
|
||||
```python
|
||||
@mcp.tool(annotations={"readOnlyHint": True})
|
||||
def get_status() -> str:
|
||||
"""Check system status."""
|
||||
return "All systems operational"
|
||||
|
||||
@mcp.tool() # No annotation - ChatGPT may ask for confirmation
|
||||
def delete_item(id: str) -> str:
|
||||
"""Delete an item."""
|
||||
return f"Deleted {id}"
|
||||
```
|
||||
|
||||
## Deep Research Mode
|
||||
|
||||
Deep Research mode provides systematic information retrieval with citations. See [OpenAI's MCP documentation](https://platform.openai.com/docs/mcp) for the latest Deep Research specifications.
|
||||
|
||||
<Warning>
|
||||
**Search and Fetch Required**: Without Developer Mode, ChatGPT will reject any server that doesn't have both `search` and `fetch` tools. Even in Developer Mode, Deep Research only uses these two tools.
|
||||
</Warning>
|
||||
|
||||
### Tool Implementation
|
||||
|
||||
Deep Research tools must follow this pattern:
|
||||
|
||||
```python
|
||||
@mcp.tool()
|
||||
def search(query: str) -> dict:
|
||||
"""
|
||||
Search for records matching the query.
|
||||
Must return {"ids": [list of string IDs]}
|
||||
"""
|
||||
# Your search logic
|
||||
matching_ids = ["id1", "id2", "id3"]
|
||||
return {"ids": matching_ids}
|
||||
|
||||
@mcp.tool()
|
||||
def fetch(id: str) -> dict:
|
||||
"""
|
||||
Fetch a complete record by ID.
|
||||
Return the full record data for ChatGPT to analyze.
|
||||
"""
|
||||
# Your fetch logic
|
||||
return {
|
||||
"id": id,
|
||||
"title": "Record Title",
|
||||
"content": "Full record content...",
|
||||
"metadata": {"author": "Jane Doe", "date": "2024"}
|
||||
}
|
||||
```
|
||||
|
||||
### Using Deep Research
|
||||
|
||||
1. Ensure your server is added to ChatGPT's connectors (same as Chat mode)
|
||||
2. Start a new chat
|
||||
3. Click **+** → **Deep Research**
|
||||
4. Select your MCP server as a source
|
||||
5. Ask research questions
|
||||
|
||||
ChatGPT will use your `search` and `fetch` tools to find and cite relevant information.
|
||||
|
||||
If you get this error, it most likely means that your server doesn't implement the required tools (`search` and `fetch`). To correct it, ensure that your server meets the service requirements.
|
||||
|
|
@ -341,7 +341,7 @@ app.mount("/analytics", mcp_app)
|
|||
|
||||
## Offering an LLM-Friendly API
|
||||
|
||||
A common pattern is to generate an MCP server from your FastAPI app and mount it back into the same application. This provides an LLM-optimized interface alongside your regular API:
|
||||
A common pattern is to generate an MCP server from your FastAPI app and serve both interfaces from the same application. This provides an LLM-optimized interface alongside your regular API:
|
||||
|
||||
```python
|
||||
# Assumes the FastAPI app from above is already defined
|
||||
|
|
@ -354,13 +354,19 @@ mcp = FastMCP.from_fastapi(app=app, name="E-commerce MCP")
|
|||
# 2. Create the MCP's ASGI app
|
||||
mcp_app = mcp.http_app(path='/mcp')
|
||||
|
||||
# 3. Mount it back into your FastAPI app
|
||||
app = FastAPI(title="E-commerce API", lifespan=mcp_app.lifespan)
|
||||
app.mount("/llm", mcp_app)
|
||||
# 3. Create a new FastAPI app that combines both sets of routes
|
||||
combined_app = FastAPI(
|
||||
title="E-commerce API with MCP",
|
||||
routes=[
|
||||
*mcp_app.routes, # MCP routes
|
||||
*app.routes, # Original API routes
|
||||
],
|
||||
lifespan=mcp_app.lifespan,
|
||||
)
|
||||
|
||||
# Now you have:
|
||||
# - Regular API: http://localhost:8000/products
|
||||
# - LLM-friendly MCP: http://localhost:8000/llm/mcp/
|
||||
# - LLM-friendly MCP: http://localhost:8000/mcp/
|
||||
# Both served from the same FastAPI application!
|
||||
```
|
||||
|
||||
|
|
|
|||
187
docs/integrations/scalekit.mdx
Normal file
187
docs/integrations/scalekit.mdx
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
---
|
||||
title: Scalekit 🤝 FastMCP
|
||||
sidebarTitle: Scalekit
|
||||
description: Secure your FastMCP server with Scalekit
|
||||
icon: shield-check
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
||||
<VersionBadge version="2.12.5" />
|
||||
|
||||
|
||||
Install auth stack to your FastMCP server with [Scalekit](https://scalekit.com) using the [Remote OAuth](/servers/auth/remote-oauth) pattern: Scalekit handles user authentication, and the MCP server validates issued tokens.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before you begin
|
||||
|
||||
1. Get a [Scalekit account](https://app.scalekit.com/) and grab API credentials such as **Client ID**, **Client Secret** and **Environment URL** from _Dashboard > Developers > Settings_.
|
||||
2. Have your FastMCP server's endpoint ready (can be localhost for development, e.g., `http://localhost:8000/mcp`)
|
||||
|
||||
### Step 1: Configure MCP server in Scalekit environment
|
||||
|
||||
<Steps>
|
||||
<Step title="Register MCP server and set environment">
|
||||
|
||||
In your Scalekit dashboard:
|
||||
1. Open the **MCP Servers** section, then select **Create new server**
|
||||
2. Enter server details: a name, a resource identifier, and the desired MCP client authentication settings
|
||||
3. Save, then copy the **Resource ID** (for example, res_92015146095)
|
||||
|
||||
In your FastMCP project's `.env`:
|
||||
|
||||
```sh
|
||||
SCALEKIT_ENVIRONMENT_URL=<YOUR_APP_ENVIRONMENT_URL>
|
||||
SCALEKIT_CLIENT_ID=<YOUR_APP_CLIENT_ID> # skc_7008EXAMPLE46
|
||||
SCALEKIT_RESOURCE_ID=<YOUR_APP_RESOURCE_ID> # res_926EXAMPLE5878
|
||||
MCP_URL=http://localhost:8000/mcp
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### Step 2: Add auth to FastMCP server
|
||||
|
||||
Create your FastMCP server file and use the ScalekitProvider to handle all the OAuth integration automatically:
|
||||
|
||||
```python server.py
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.scalekit import ScalekitProvider
|
||||
|
||||
# Discovers Scalekit endpoints and set up JWT token validation
|
||||
auth_provider = ScalekitProvider(
|
||||
environment_url=SCALEKIT_ENVIRONMENT_URL, # Scalekit environment URL
|
||||
client_id=SCALEKIT_CLIENT_ID, # OAuth client ID
|
||||
resource_id=SCALEKIT_RESOURCE_ID, # Resource server ID
|
||||
mcp_url=SERVER_URL, # Is also aud claim
|
||||
)
|
||||
|
||||
# Create FastMCP server with auth
|
||||
mcp = FastMCP(name="My Scalekit Protected Server", auth=auth_provider)
|
||||
|
||||
@mcp.tool
|
||||
def auth_status() -> dict:
|
||||
"""Show Scalekit authentication status."""
|
||||
# Extract user claims from the JWT
|
||||
return {
|
||||
"message": "This tool requires authentication via Scalekit",
|
||||
"authenticated": True,
|
||||
"provider": "Scalekit"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Start the MCP server
|
||||
|
||||
```sh
|
||||
uv run python server.py
|
||||
```
|
||||
|
||||
Use any MCP client (for example, mcp-inspector, Claude, VS Code, or Windsurf) to connect to the running serve. Verify that authentication succeeds and requests are authorized as expected.
|
||||
|
||||
### Provider selection
|
||||
|
||||
Setting this environment variable allows the Scalekit provider to be used automatically without explicitly instantiating it in code.
|
||||
|
||||
<Card>
|
||||
<ParamField path="FASTMCP_SERVER_AUTH" default="Not set">
|
||||
Set to `fastmcp.server.auth.providers.scalekit.ScalekitProvider` to use Scalekit authentication.
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
### Scalekit-specific configuration
|
||||
|
||||
These environment variables provide default values for the Scalekit provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
|
||||
|
||||
<Card>
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_ENVIRONMENT_URL" required>
|
||||
Your Scalekit environment URL from the Admin Portal (e.g., `https://your-env.scalekit.com`)
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_CLIENT_ID" required>
|
||||
Your Scalekit OAuth application client ID from the Applications section
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_RESOURCE_ID" required>
|
||||
Your Scalekit resource server ID from the Resources section
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_MCP_URL" required>
|
||||
Public URL of your FastMCP server (e.g., `https://your-server.com` or `http://localhost:8000/mcp` for development)
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
Example `.env`:
|
||||
|
||||
```bash
|
||||
# Use the Scalekit provider
|
||||
FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.scalekit.ScalekitProvider
|
||||
|
||||
# Scalekit configuration
|
||||
FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_ENVIRONMENT_URL=https://your-env.scalekit.com
|
||||
FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_CLIENT_ID=skc_123
|
||||
FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_RESOURCE_ID=res_456
|
||||
FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_MCP_URL=https://your-server.com/mcp
|
||||
```
|
||||
|
||||
With environment variables set, your server code simplifies to:
|
||||
|
||||
```python server.py
|
||||
from fastmcp import FastMCP
|
||||
|
||||
# Authentication is automatically configured from environment
|
||||
mcp = FastMCP(name="My Scalekit Protected Server")
|
||||
|
||||
@mcp.tool
|
||||
def protected_action() -> str:
|
||||
"""A tool that requires authentication."""
|
||||
return "Access granted via Scalekit!"
|
||||
```
|
||||
|
||||
## Capabilities
|
||||
|
||||
Scalekit supports OAuth 2.1 with Dynamic Client Registration for MCP clients and enterprise SSO, and provides built‑in JWT validation and security controls.
|
||||
|
||||
**OAuth 2.1/DCR**: clients self‑register, use PKCE, and work with the Remote OAuth pattern without pre‑provisioned credentials.
|
||||
|
||||
**Validation and SSO**: tokens are verified (keys, RS256, issuer, audience, expiry), and SAML, OIDC, OAuth 2.0, ADFS, Azure AD, and Google Workspace are supported; use HTTPS in production and review auth logs as needed.
|
||||
|
||||
## Debugging
|
||||
|
||||
Enable detailed logging to troubleshoot authentication issues:
|
||||
|
||||
```python
|
||||
import logging
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
```
|
||||
|
||||
### Token inspection
|
||||
|
||||
You can inspect JWT tokens in your tools to understand the user context:
|
||||
|
||||
```python
|
||||
from fastmcp.server.context import request_ctx
|
||||
import jwt
|
||||
|
||||
@mcp.tool
|
||||
def inspect_token() -> dict:
|
||||
"""Inspect the current JWT token claims."""
|
||||
context = request_ctx.get()
|
||||
|
||||
# Extract token from Authorization header
|
||||
if hasattr(context, 'request') and hasattr(context.request, 'headers'):
|
||||
auth_header = context.request.headers.get('authorization', '')
|
||||
if auth_header.startswith('Bearer '):
|
||||
token = auth_header[7:]
|
||||
# Decode without verification (already verified by provider)
|
||||
claims = jwt.decode(token, options={"verify_signature": False})
|
||||
return claims
|
||||
|
||||
return {"error": "No token found"}
|
||||
```
|
||||
|
|
@ -74,7 +74,7 @@ fastmcp run server.py -- --config config.json --debug
|
|||
- `server_spec`: Python file, object specification (file\:obj), config file, URL, or None to auto-detect
|
||||
|
||||
|
||||
### `inspect` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L541" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `inspect` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L544" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
inspect(server_spec: str | None = None) -> None
|
||||
|
|
@ -105,7 +105,7 @@ fastmcp inspect # auto-detect fastmcp.json
|
|||
- `server_spec`: Python file to inspect, optionally with \:object suffix, or fastmcp.json
|
||||
|
||||
|
||||
### `prepare` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L782" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `prepare` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L785" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
prepare(config_path: Annotated[str | None, cyclopts.Parameter(help='Path to fastmcp.json configuration file')] = None, output_dir: Annotated[str | None, cyclopts.Parameter(help='Directory to create the persistent environment in')] = None, skip_source: Annotated[bool, cyclopts.Parameter(help='Skip source preparation (e.g., git clone)')] = False) -> None
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ Install FastMCP server in Gemini CLI.
|
|||
- True if installation was successful, False otherwise
|
||||
|
||||
|
||||
### `gemini_cli_command` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/gemini_cli.py#L151" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `gemini_cli_command` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/gemini_cli.py#L150" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
gemini_cli_command(server_spec: str) -> None
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ sidebarTitle: oauth
|
|||
default_cache_dir() -> Path
|
||||
```
|
||||
|
||||
### `check_if_auth_required` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L199" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `check_if_auth_required` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L212" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
check_if_auth_required(mcp_url: str, httpx_kwargs: dict[str, Any] | None = None) -> bool
|
||||
|
|
@ -47,11 +47,12 @@ File-based token storage implementation for OAuth credentials and tokens.
|
|||
Implements the mcp.client.auth.TokenStorage protocol.
|
||||
|
||||
Each instance is tied to a specific server URL for proper token isolation.
|
||||
Uses JSONFileStorage internally for consistent file handling.
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `get_base_url` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L74" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_base_url` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L75" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_base_url(url: str) -> str
|
||||
|
|
@ -60,16 +61,7 @@ get_base_url(url: str) -> str
|
|||
Extract the base URL (scheme + host) from a URL.
|
||||
|
||||
|
||||
#### `get_cache_key` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L79" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_cache_key(self) -> str
|
||||
```
|
||||
|
||||
Generate a safe filesystem key from the server's base URL.
|
||||
|
||||
|
||||
#### `get_tokens` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L94" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_tokens` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L96" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_tokens(self) -> OAuthToken | None
|
||||
|
|
@ -78,7 +70,7 @@ get_tokens(self) -> OAuthToken | None
|
|||
Load tokens from file storage.
|
||||
|
||||
|
||||
#### `set_tokens` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L126" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_tokens` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L132" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_tokens(self, tokens: OAuthToken) -> None
|
||||
|
|
@ -87,7 +79,7 @@ set_tokens(self, tokens: OAuthToken) -> None
|
|||
Save tokens to file storage.
|
||||
|
||||
|
||||
#### `get_client_info` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L143" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_client_info` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L149" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_client_info(self) -> OAuthClientInformationFull | None
|
||||
|
|
@ -96,7 +88,7 @@ get_client_info(self) -> OAuthClientInformationFull | None
|
|||
Load client information from file storage.
|
||||
|
||||
|
||||
#### `set_client_info` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L171" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_client_info` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L179" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_client_info(self, client_info: OAuthClientInformationFull) -> None
|
||||
|
|
@ -105,7 +97,7 @@ set_client_info(self, client_info: OAuthClientInformationFull) -> None
|
|||
Save client information to file storage.
|
||||
|
||||
|
||||
#### `clear` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L177" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `clear` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L185" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
clear(self) -> None
|
||||
|
|
@ -113,8 +105,11 @@ clear(self) -> None
|
|||
|
||||
Clear all cached data for this server.
|
||||
|
||||
Note: This is a synchronous method for backward compatibility.
|
||||
Uses direct file operations instead of async storage methods.
|
||||
|
||||
#### `clear_all` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L186" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
#### `clear_all` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L199" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
clear_all(cls, cache_dir: Path | None = None) -> None
|
||||
|
|
@ -123,7 +118,7 @@ clear_all(cls, cache_dir: Path | None = None) -> None
|
|||
Clear all cached data for all servers.
|
||||
|
||||
|
||||
### `OAuth` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L229" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `OAuth` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L242" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
OAuth client provider for MCP servers with browser-based authentication.
|
||||
|
|
@ -134,7 +129,7 @@ a browser for user authorization and running a local callback server.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `redirect_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L309" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `redirect_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L322" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
redirect_handler(self, authorization_url: str) -> None
|
||||
|
|
@ -143,7 +138,7 @@ redirect_handler(self, authorization_url: str) -> None
|
|||
Open browser for authorization, with pre-flight check for invalid client.
|
||||
|
||||
|
||||
#### `callback_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L330" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `callback_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L343" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
callback_handler(self) -> tuple[str, str | None]
|
||||
|
|
@ -152,7 +147,7 @@ callback_handler(self) -> tuple[str, str | None]
|
|||
Handle OAuth callback and return (auth_code, state).
|
||||
|
||||
|
||||
#### `async_auth_flow` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L363" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `async_auth_flow` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L376" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
async_auth_flow(self, request: httpx.Request) -> AsyncGenerator[httpx.Request, httpx.Response]
|
||||
|
|
|
|||
|
|
@ -382,7 +382,8 @@ Send a completion request and return the complete MCP protocol result.
|
|||
**Args:**
|
||||
- `ref`: The reference to complete.
|
||||
- `argument`: Arguments to pass to the completion request.
|
||||
- `context_arguments`: Optional context arguments to include with the completion request. Defaults to None.
|
||||
- `context_arguments`: Optional context arguments to
|
||||
include with the completion request. Defaults to None.
|
||||
|
||||
**Returns:**
|
||||
- mcp.types.CompleteResult: The complete response object from the protocol,
|
||||
|
|
@ -403,7 +404,8 @@ Send a completion request to the server.
|
|||
**Args:**
|
||||
- `ref`: The reference to complete.
|
||||
- `argument`: Arguments to pass to the completion request.
|
||||
- `context_arguments`: Optional context arguments to include with the completion request. Defaults to None.
|
||||
- `context_arguments`: Optional context arguments to
|
||||
include with the completion request. Defaults to None.
|
||||
|
||||
**Returns:**
|
||||
- mcp.types.Completion: The completion object.
|
||||
|
|
@ -412,7 +414,7 @@ Send a completion request to the server.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `list_tools_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L789" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_tools_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L799" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_tools_mcp(self) -> mcp.types.ListToolsResult
|
||||
|
|
@ -428,7 +430,7 @@ containing the list of tools and any additional metadata.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `list_tools` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L804" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_tools` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L814" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_tools(self) -> list[mcp.types.Tool]
|
||||
|
|
@ -443,7 +445,7 @@ Retrieve a list of tools available on the server.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `call_tool_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L818" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `call_tool_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L828" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
call_tool_mcp(self, name: str, arguments: dict[str, Any], progress_handler: ProgressHandler | None = None, timeout: datetime.timedelta | float | int | None = None) -> mcp.types.CallToolResult
|
||||
|
|
@ -468,7 +470,7 @@ containing the tool result and any additional metadata.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `call_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L855" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `call_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L865" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
call_tool(self, name: str, arguments: dict[str, Any] | None = None, timeout: datetime.timedelta | float | int | None = None, progress_handler: ProgressHandler | None = None, raise_on_error: bool = True) -> CallToolResult
|
||||
|
|
@ -498,10 +500,10 @@ raw result object.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `generate_name` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L926" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `generate_name` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L936" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
generate_name(cls, name: str | None = None) -> str
|
||||
```
|
||||
|
||||
### `CallToolResult` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L935" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `CallToolResult` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L945" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@ sidebarTitle: auth
|
|||
|
||||
## Classes
|
||||
|
||||
### `AccessToken` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L36" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `AccessToken` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L35" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
AccessToken that includes all JWT claims.
|
||||
|
||||
|
||||
### `AuthProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L42" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `AuthProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L41" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Base class for all FastMCP authentication providers.
|
||||
|
|
@ -26,7 +26,7 @@ custom authentication routes.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L69" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L68" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
verify_token(self, token: str) -> AccessToken | None
|
||||
|
|
@ -43,7 +43,7 @@ All auth providers must implement token verification.
|
|||
- AccessToken object if valid, None if invalid or expired
|
||||
|
||||
|
||||
#### `get_routes` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L82" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_routes` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L81" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_routes(self, mcp_path: str | None = None, mcp_endpoint: Any | None = None) -> list[Route]
|
||||
|
|
@ -65,7 +65,7 @@ Each provider is responsible for creating whatever routes it needs:
|
|||
- List of routes for this provider, including protected MCP endpoints if provided
|
||||
|
||||
|
||||
#### `get_middleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L122" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_middleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L121" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_middleware(self) -> list
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ production use with enterprise identity providers.
|
|||
|
||||
## Classes
|
||||
|
||||
### `ProxyDCRClient` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L58" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ProxyDCRClient` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L60" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Client for DCR proxy with configurable redirect URI validation.
|
||||
|
|
@ -56,7 +56,7 @@ arise from accepting arbitrary redirect URIs.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `validate_redirect_uri` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L97" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `validate_redirect_uri` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L99" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
validate_redirect_uri(self, redirect_uri: AnyUrl | None) -> AnyUrl
|
||||
|
|
@ -70,7 +70,7 @@ This is essential for cached token scenarios where the client may
|
|||
reconnect with a different port.
|
||||
|
||||
|
||||
### `OAuthProxy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L123" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `OAuthProxy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L125" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
OAuth provider that presents a DCR-compliant interface while proxying to non-DCR IDPs.
|
||||
|
|
@ -181,7 +181,7 @@ Handles provider-specific requirements:
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `get_client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L381" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L396" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_client(self, client_id: str) -> OAuthClientInformationFull | None
|
||||
|
|
@ -193,7 +193,7 @@ provided to the DCR client during registration, not the upstream client ID.
|
|||
For unregistered clients, returns None (which will raise an error in the SDK).
|
||||
|
||||
|
||||
#### `register_client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L391" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `register_client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L417" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
register_client(self, client_info: OAuthClientInformationFull) -> None
|
||||
|
|
@ -207,7 +207,7 @@ redirect URI will likely be localhost or unknown to the proxied IDP. The
|
|||
proxied IDP only knows about this server's fixed redirect URI.
|
||||
|
||||
|
||||
#### `authorize` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L434" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `authorize` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L464" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str
|
||||
|
|
@ -222,7 +222,7 @@ This implements the DCR-compliant proxy pattern:
|
|||
4. Redirect to IdP with our fixed callback URL and proxy's PKCE
|
||||
|
||||
|
||||
#### `load_authorization_code` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L535" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `load_authorization_code` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L565" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
load_authorization_code(self, client: OAuthClientInformationFull, authorization_code: str) -> AuthorizationCode | None
|
||||
|
|
@ -234,7 +234,7 @@ Look up our client code and return authorization code object
|
|||
with PKCE challenge for validation.
|
||||
|
||||
|
||||
#### `exchange_authorization_code` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L577" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `exchange_authorization_code` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L607" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
exchange_authorization_code(self, client: OAuthClientInformationFull, authorization_code: AuthorizationCode) -> OAuthToken
|
||||
|
|
@ -246,7 +246,7 @@ For the DCR-compliant proxy flow, we return the IdP tokens that were obtained
|
|||
during the IdP callback exchange. PKCE validation is handled by the MCP framework.
|
||||
|
||||
|
||||
#### `load_refresh_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L644" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `load_refresh_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L674" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
load_refresh_token(self, client: OAuthClientInformationFull, refresh_token: str) -> RefreshToken | None
|
||||
|
|
@ -255,7 +255,7 @@ load_refresh_token(self, client: OAuthClientInformationFull, refresh_token: str)
|
|||
Load refresh token from local storage.
|
||||
|
||||
|
||||
#### `exchange_refresh_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L652" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `exchange_refresh_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L682" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
exchange_refresh_token(self, client: OAuthClientInformationFull, refresh_token: RefreshToken, scopes: list[str]) -> OAuthToken
|
||||
|
|
@ -264,7 +264,7 @@ exchange_refresh_token(self, client: OAuthClientInformationFull, refresh_token:
|
|||
Exchange refresh token for new access token using authlib.
|
||||
|
||||
|
||||
#### `load_access_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L728" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `load_access_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L758" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
load_access_token(self, token: str) -> AccessToken | None
|
||||
|
|
@ -276,7 +276,7 @@ Delegates to the JWT verifier which handles signature validation,
|
|||
expiration checking, and claims validation using the upstream JWKS.
|
||||
|
||||
|
||||
#### `revoke_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L745" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `revoke_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L775" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
revoke_token(self, token: AccessToken | RefreshToken) -> None
|
||||
|
|
@ -288,7 +288,7 @@ Removes tokens from local storage and attempts to revoke them with
|
|||
the upstream server if a revocation endpoint is configured.
|
||||
|
||||
|
||||
#### `get_routes` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L789" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_routes` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L819" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_routes(self, mcp_path: str | None = None, mcp_endpoint: Any | None = None) -> list[Route]
|
||||
|
|
|
|||
82
docs/python-sdk/fastmcp-server-auth-oidc_proxy.mdx
Normal file
82
docs/python-sdk/fastmcp-server-auth-oidc_proxy.mdx
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
---
|
||||
title: oidc_proxy
|
||||
sidebarTitle: oidc_proxy
|
||||
---
|
||||
|
||||
# `fastmcp.server.auth.oidc_proxy`
|
||||
|
||||
|
||||
OIDC Proxy Provider for FastMCP.
|
||||
|
||||
This provider acts as a transparent proxy to an upstream OIDC compliant Authorization
|
||||
Server. It leverages the OAuthProxy class to handle Dynamic Client Registration and
|
||||
forwarding of all OAuth flows.
|
||||
|
||||
This implementation is based on:
|
||||
OpenID Connect Discovery 1.0 - https://openid.net/specs/openid-connect-discovery-1_0.html
|
||||
OAuth 2.0 Authorization Server Metadata - https://datatracker.ietf.org/doc/html/rfc8414
|
||||
|
||||
|
||||
## Classes
|
||||
|
||||
### `OIDCConfiguration` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oidc_proxy.py#L27" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
OIDC Configuration.
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `get_oidc_configuration` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oidc_proxy.py#L142" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_oidc_configuration(cls, config_url: AnyHttpUrl) -> Self
|
||||
```
|
||||
|
||||
Get the OIDC configuration for the specified config URL.
|
||||
|
||||
**Args:**
|
||||
- `config_url`: The OIDC config URL
|
||||
- `strict`: The strict flag for the configuration
|
||||
- `timeout_seconds`: HTTP request timeout in seconds
|
||||
|
||||
|
||||
### `OIDCProxy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oidc_proxy.py#L172" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
OAuth provider that wraps OAuthProxy to provide configuration via an OIDC configuration URL.
|
||||
|
||||
This provider makes it easier to add OAuth protection for any upstream provider
|
||||
that is OIDC compliant.
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `get_oidc_configuration` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oidc_proxy.py#L309" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_oidc_configuration(self, config_url: AnyHttpUrl, strict: bool | None, timeout_seconds: int | None) -> OIDCConfiguration
|
||||
```
|
||||
|
||||
Gets the OIDC configuration for the specified configuration URL.
|
||||
|
||||
**Args:**
|
||||
- `config_url`: The OIDC configuration URL
|
||||
- `strict`: The strict flag for the configuration
|
||||
- `timeout_seconds`: HTTP request timeout in seconds
|
||||
|
||||
|
||||
#### `get_token_verifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oidc_proxy.py#L326" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_token_verifier(self) -> TokenVerifier
|
||||
```
|
||||
|
||||
Creates the token verifier for the specified OIDC configuration and arguments.
|
||||
|
||||
**Args:**
|
||||
- `algorithm`: Optional token verifier algorithm
|
||||
- `audience`: Optional token verifier audience
|
||||
- `required_scopes`: Optional token verifier required_scopes
|
||||
- `timeout_seconds`: HTTP request timeout in seconds
|
||||
|
||||
47
docs/python-sdk/fastmcp-server-auth-providers-auth0.mdx
Normal file
47
docs/python-sdk/fastmcp-server-auth-providers-auth0.mdx
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
title: auth0
|
||||
sidebarTitle: auth0
|
||||
---
|
||||
|
||||
# `fastmcp.server.auth.providers.auth0`
|
||||
|
||||
|
||||
Auth0 OAuth provider for FastMCP.
|
||||
|
||||
This module provides a complete Auth0 integration that's ready to use with
|
||||
just the configuration URL, client ID, client secret, audience, and base URL.
|
||||
|
||||
Example:
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.auth0 import Auth0Provider
|
||||
|
||||
# Simple Auth0 OAuth protection
|
||||
auth = Auth0Provider(
|
||||
config_url="https://auth0.config.url",
|
||||
client_id="your-auth0-client-id",
|
||||
client_secret="your-auth0-client-secret",
|
||||
audience="your-auth0-api-audience",
|
||||
base_url="http://localhost:8000",
|
||||
)
|
||||
|
||||
mcp = FastMCP("My Protected Server", auth=auth)
|
||||
```
|
||||
|
||||
|
||||
## Classes
|
||||
|
||||
### `Auth0ProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/auth0.py#L36" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Settings for Auth0 OIDC provider.
|
||||
|
||||
|
||||
### `Auth0Provider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/auth0.py#L60" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
An Auth0 provider implementation for FastMCP.
|
||||
|
||||
This provider is a complete Auth0 integration that's ready to use with
|
||||
just the configuration URL, client ID, client secret, audience, and base URL.
|
||||
|
||||
88
docs/python-sdk/fastmcp-server-auth-providers-aws.mdx
Normal file
88
docs/python-sdk/fastmcp-server-auth-providers-aws.mdx
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
---
|
||||
title: aws
|
||||
sidebarTitle: aws
|
||||
---
|
||||
|
||||
# `fastmcp.server.auth.providers.aws`
|
||||
|
||||
|
||||
AWS Cognito OAuth provider for FastMCP.
|
||||
|
||||
This module provides a complete AWS Cognito OAuth integration that's ready to use
|
||||
with a user pool ID, domain prefix, client ID and client secret. It handles all
|
||||
the complexity of AWS Cognito's OAuth flow, token validation, and user management.
|
||||
|
||||
Example:
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.aws_cognito import AWSCognitoProvider
|
||||
|
||||
# Simple AWS Cognito OAuth protection
|
||||
auth = AWSCognitoProvider(
|
||||
user_pool_id="your-user-pool-id",
|
||||
aws_region="eu-central-1",
|
||||
client_id="your-cognito-client-id",
|
||||
client_secret="your-cognito-client-secret"
|
||||
)
|
||||
|
||||
mcp = FastMCP("My Protected Server", auth=auth)
|
||||
```
|
||||
|
||||
|
||||
## Classes
|
||||
|
||||
### `AWSCognitoProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/aws.py#L40" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Settings for AWS Cognito OAuth provider.
|
||||
|
||||
|
||||
### `AWSCognitoTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/aws.py#L64" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Token verifier that filters claims to Cognito-specific subset.
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/aws.py#L67" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
verify_token(self, token: str) -> AccessToken | None
|
||||
```
|
||||
|
||||
Verify token and filter claims to Cognito-specific subset.
|
||||
|
||||
|
||||
### `AWSCognitoProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/aws.py#L91" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Complete AWS Cognito OAuth provider for FastMCP.
|
||||
|
||||
This provider makes it trivial to add AWS Cognito OAuth protection to any
|
||||
FastMCP server using OIDC Discovery. Just provide your Cognito User Pool details,
|
||||
client credentials, and a base URL, and you're ready to go.
|
||||
|
||||
Features:
|
||||
- Automatic OIDC Discovery from AWS Cognito User Pool
|
||||
- Automatic JWT token validation via Cognito's public keys
|
||||
- Cognito-specific claim filtering (sub, username, cognito:groups)
|
||||
- Support for Cognito User Pools
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `get_token_verifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/aws.py#L215" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_token_verifier(self) -> TokenVerifier
|
||||
```
|
||||
|
||||
Creates a Cognito-specific token verifier with claim filtering.
|
||||
|
||||
**Args:**
|
||||
- `algorithm`: Optional token verifier algorithm
|
||||
- `audience`: Optional token verifier audience
|
||||
- `required_scopes`: Optional token verifier required_scopes
|
||||
- `timeout_seconds`: HTTP request timeout in seconds
|
||||
|
||||
|
|
@ -14,13 +14,13 @@ using the OAuth Proxy pattern for non-DCR OAuth flows.
|
|||
|
||||
## Classes
|
||||
|
||||
### `AzureProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L22" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `AzureProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L23" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Settings for Azure OAuth provider.
|
||||
|
||||
|
||||
### `AzureTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L46" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `AzureTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L47" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Token verifier for Azure OAuth tokens.
|
||||
|
|
@ -31,7 +31,7 @@ to get user information and validate the token.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L68" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L69" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
verify_token(self, token: str) -> AccessToken | None
|
||||
|
|
@ -40,7 +40,7 @@ verify_token(self, token: str) -> AccessToken | None
|
|||
Verify Azure OAuth token by calling Microsoft Graph API.
|
||||
|
||||
|
||||
### `AzureProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L117" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `AzureProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L118" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Azure (Microsoft Entra) OAuth provider for FastMCP.
|
||||
|
|
|
|||
62
docs/python-sdk/fastmcp-server-auth-providers-descope.mdx
Normal file
62
docs/python-sdk/fastmcp-server-auth-providers-descope.mdx
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
---
|
||||
title: descope
|
||||
sidebarTitle: descope
|
||||
---
|
||||
|
||||
# `fastmcp.server.auth.providers.descope`
|
||||
|
||||
|
||||
Descope authentication provider for FastMCP.
|
||||
|
||||
This module provides DescopeProvider - a complete authentication solution that integrates
|
||||
with Descope's OAuth 2.1 and OpenID Connect services, supporting Dynamic Client Registration (DCR)
|
||||
for seamless MCP client authentication.
|
||||
|
||||
|
||||
## Classes
|
||||
|
||||
### `DescopeProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/descope.py#L26" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
### `DescopeProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/descope.py#L38" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Descope metadata provider for DCR (Dynamic Client Registration).
|
||||
|
||||
This provider implements Descope integration using metadata forwarding.
|
||||
This is the recommended approach for Descope DCR
|
||||
as it allows Descope to handle the OAuth flow directly while FastMCP acts
|
||||
as a resource server.
|
||||
|
||||
IMPORTANT SETUP REQUIREMENTS:
|
||||
|
||||
1. Enable Dynamic Client Registration in Descope Console:
|
||||
- Go to the [Inbound Apps page](https://app.descope.com/apps/inbound) of the Descope Console
|
||||
- Click **DCR Settings**
|
||||
- Enable **Dynamic Client Registration (DCR)**
|
||||
- Define allowed scopes
|
||||
|
||||
2. Note your Project ID:
|
||||
- Save your Project ID from [Project Settings](https://app.descope.com/settings/project)
|
||||
- Example: P2abc...123
|
||||
|
||||
For detailed setup instructions, see:
|
||||
https://docs.descope.com/identity-federation/inbound-apps/creating-inbound-apps#method-2-dynamic-client-registration-dcr
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `get_routes` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/descope.py#L127" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_routes(self, mcp_path: str | None = None, mcp_endpoint: Any | None = None) -> list[Route]
|
||||
```
|
||||
|
||||
Get OAuth routes including Descope authorization server metadata forwarding.
|
||||
|
||||
This returns the standard protected resource routes plus an authorization server
|
||||
metadata endpoint that forwards Descope's OAuth metadata to clients.
|
||||
|
||||
**Args:**
|
||||
- `mcp_path`: The path where the MCP endpoint is mounted (e.g., "/mcp")
|
||||
- `mcp_endpoint`: The MCP endpoint handler to protect with auth
|
||||
|
||||
|
|
@ -29,13 +29,13 @@ Example:
|
|||
|
||||
## Classes
|
||||
|
||||
### `GitHubProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/github.py#L38" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `GitHubProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/github.py#L39" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Settings for GitHub OAuth provider.
|
||||
|
||||
|
||||
### `GitHubTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/github.py#L61" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `GitHubTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/github.py#L62" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Token verifier for GitHub OAuth tokens.
|
||||
|
|
@ -46,7 +46,7 @@ by calling GitHub's API to check if they're valid and get user info.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/github.py#L83" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/github.py#L84" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
verify_token(self, token: str) -> AccessToken | None
|
||||
|
|
@ -55,7 +55,7 @@ verify_token(self, token: str) -> AccessToken | None
|
|||
Verify GitHub OAuth token by calling GitHub API.
|
||||
|
||||
|
||||
### `GitHubProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/github.py#L166" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `GitHubProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/github.py#L167" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Complete GitHub OAuth provider for FastMCP.
|
||||
|
|
|
|||
|
|
@ -29,13 +29,13 @@ Example:
|
|||
|
||||
## Classes
|
||||
|
||||
### `GoogleProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/google.py#L40" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `GoogleProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/google.py#L41" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Settings for Google OAuth provider.
|
||||
|
||||
|
||||
### `GoogleTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/google.py#L63" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `GoogleTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/google.py#L64" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Token verifier for Google OAuth tokens.
|
||||
|
|
@ -46,7 +46,7 @@ by calling Google's tokeninfo API to check if they're valid and get user info.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/google.py#L85" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/google.py#L86" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
verify_token(self, token: str) -> AccessToken | None
|
||||
|
|
@ -55,7 +55,7 @@ verify_token(self, token: str) -> AccessToken | None
|
|||
Verify Google OAuth token by calling Google's tokeninfo API.
|
||||
|
||||
|
||||
### `GoogleProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/google.py#L182" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `GoogleProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/google.py#L183" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Complete Google OAuth provider for FastMCP.
|
||||
|
|
|
|||
64
docs/python-sdk/fastmcp-server-auth-providers-scalekit.mdx
Normal file
64
docs/python-sdk/fastmcp-server-auth-providers-scalekit.mdx
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
---
|
||||
title: scalekit
|
||||
sidebarTitle: scalekit
|
||||
---
|
||||
|
||||
# `fastmcp.server.auth.providers.scalekit`
|
||||
|
||||
|
||||
Scalekit authentication provider for FastMCP.
|
||||
|
||||
This module provides ScalekitProvider - a complete authentication solution that integrates
|
||||
with Scalekit's OAuth 2.1 and OpenID Connect services, supporting Resource Server
|
||||
authentication for seamless MCP client authentication.
|
||||
|
||||
|
||||
## Classes
|
||||
|
||||
### `ScalekitProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/scalekit.py#L26" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
### `ScalekitProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/scalekit.py#L39" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Scalekit resource server provider for OAuth 2.1 authentication.
|
||||
|
||||
This provider implements Scalekit integration using resource server pattern.
|
||||
FastMCP acts as a protected resource server that validates access tokens issued
|
||||
by Scalekit's authorization server.
|
||||
|
||||
IMPORTANT SETUP REQUIREMENTS:
|
||||
|
||||
1. Create an MCP Server in Scalekit Dashboard:
|
||||
- Go to your [Scalekit Dashboard](https://app.scalekit.com/)
|
||||
- Navigate to MCP Servers section
|
||||
- Register a new MCP Server with appropriate scopes
|
||||
- Ensure the Resource Identifier matches exactly what you configure as MCP URL
|
||||
- Note the Resource ID
|
||||
|
||||
2. Environment Configuration:
|
||||
- Set SCALEKIT_ENVIRONMENT_URL (e.g., https://your-env.scalekit.com)
|
||||
- Set SCALEKIT_CLIENT_ID from your OAuth application
|
||||
- Set SCALEKIT_RESOURCE_ID from your created resource
|
||||
- Set MCP_URL to your FastMCP server's public URL
|
||||
|
||||
For detailed setup instructions, see:
|
||||
https://docs.scalekit.com/mcp/overview/
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `get_routes` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/scalekit.py#L135" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_routes(self, mcp_path: str | None = None, mcp_endpoint: Any | None = None) -> list[Route]
|
||||
```
|
||||
|
||||
Get OAuth routes including Scalekit authorization server metadata forwarding.
|
||||
|
||||
This returns the standard protected resource routes plus an authorization server
|
||||
metadata endpoint that forwards Scalekit's OAuth metadata to clients.
|
||||
|
||||
**Args:**
|
||||
- `mcp_path`: The path where the MCP endpoint is mounted (e.g., "/mcp")
|
||||
- `mcp_endpoint`: The MCP endpoint handler to protect with auth
|
||||
|
||||
|
|
@ -18,13 +18,13 @@ Choose based on your WorkOS setup and authentication requirements.
|
|||
|
||||
## Classes
|
||||
|
||||
### `WorkOSProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L31" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `WorkOSProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L32" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Settings for WorkOS OAuth provider.
|
||||
|
||||
|
||||
### `WorkOSTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L55" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `WorkOSTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L56" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Token verifier for WorkOS OAuth tokens.
|
||||
|
|
@ -35,7 +35,7 @@ the /oauth2/userinfo endpoint to check validity and get user info.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L80" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L81" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
verify_token(self, token: str) -> AccessToken | None
|
||||
|
|
@ -44,7 +44,7 @@ verify_token(self, token: str) -> AccessToken | None
|
|||
Verify WorkOS OAuth token by calling userinfo endpoint.
|
||||
|
||||
|
||||
### `WorkOSProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L127" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `WorkOSProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L128" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Complete WorkOS OAuth provider for FastMCP.
|
||||
|
|
@ -65,9 +65,9 @@ Setup Requirements:
|
|||
4. Note your Client ID and Client Secret
|
||||
|
||||
|
||||
### `AuthKitProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L260" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `AuthKitProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L264" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
### `AuthKitProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L277" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `AuthKitProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L281" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
AuthKit metadata provider for DCR (Dynamic Client Registration).
|
||||
|
|
@ -93,7 +93,7 @@ https://workos.com/docs/authkit/mcp/integrating/token-verification
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `get_routes` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L360" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_routes` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L364" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_routes(self, mcp_path: str | None = None, mcp_endpoint: Any | None = None) -> list[Route]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ sidebarTitle: context
|
|||
|
||||
## Functions
|
||||
|
||||
### `set_context` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L69" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `set_context` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L70" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_context(context: Context) -> Generator[Context, None, None]
|
||||
|
|
@ -15,7 +15,7 @@ set_context(context: Context) -> Generator[Context, None, None]
|
|||
|
||||
## Classes
|
||||
|
||||
### `LogData` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L57" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `LogData` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L58" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Data object for passing log arguments to client-side handlers.
|
||||
|
|
@ -24,7 +24,7 @@ This provides an interface to match the Python standard library logging,
|
|||
for compatibility with structured logging.
|
||||
|
||||
|
||||
### `Context` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L78" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `Context` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L79" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Context object providing access to MCP capabilities.
|
||||
|
|
@ -36,18 +36,18 @@ To use context in a tool function, add a parameter with the Context type annotat
|
|||
|
||||
```python
|
||||
@server.tool
|
||||
def my_tool(x: int, ctx: Context) -> str:
|
||||
async def my_tool(x: int, ctx: Context) -> str:
|
||||
# Log messages to the client
|
||||
ctx.info(f"Processing {x}")
|
||||
ctx.debug("Debug info")
|
||||
ctx.warning("Warning message")
|
||||
ctx.error("Error message")
|
||||
await ctx.info(f"Processing {x}")
|
||||
await ctx.debug("Debug info")
|
||||
await ctx.warning("Warning message")
|
||||
await ctx.error("Error message")
|
||||
|
||||
# Report progress
|
||||
ctx.report_progress(50, 100, "Processing")
|
||||
await ctx.report_progress(50, 100, "Processing")
|
||||
|
||||
# Access resources
|
||||
data = ctx.read_resource("resource://data")
|
||||
data = await ctx.read_resource("resource://data")
|
||||
|
||||
# Get request info
|
||||
request_id = ctx.request_id
|
||||
|
|
@ -72,7 +72,7 @@ The context is optional - tools that don't need it can omit the parameter.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `fastmcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L130" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `fastmcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L131" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
fastmcp(self) -> FastMCP
|
||||
|
|
@ -81,7 +81,7 @@ fastmcp(self) -> FastMCP
|
|||
Get the FastMCP instance.
|
||||
|
||||
|
||||
#### `request_context` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L159" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `request_context` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L160" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
request_context(self) -> RequestContext[ServerSession, Any, Request]
|
||||
|
|
@ -92,7 +92,7 @@ Access to the underlying request context.
|
|||
If called outside of a request context, this will raise a ValueError.
|
||||
|
||||
|
||||
#### `report_progress` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L169" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `report_progress` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L170" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
report_progress(self, progress: float, total: float | None = None, message: str | None = None) -> None
|
||||
|
|
@ -105,7 +105,7 @@ Report progress for the current operation.
|
|||
- `total`: Optional total value e.g. 100
|
||||
|
||||
|
||||
#### `read_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L196" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `read_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L197" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
read_resource(self, uri: str | AnyUrl) -> list[ReadResourceContents]
|
||||
|
|
@ -120,7 +120,7 @@ Read a resource by URI.
|
|||
- The resource content as either text or bytes
|
||||
|
||||
|
||||
#### `log` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L209" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `log` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L210" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
log(self, message: str, level: LoggingLevel | None = None, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
|
||||
|
|
@ -136,7 +136,7 @@ Send a log message to the client.
|
|||
- `extra`: Optional mapping for additional arguments
|
||||
|
||||
|
||||
#### `client_id` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L236" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `client_id` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L237" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
client_id(self) -> str | None
|
||||
|
|
@ -145,7 +145,7 @@ client_id(self) -> str | None
|
|||
Get the client ID if available.
|
||||
|
||||
|
||||
#### `request_id` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L245" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `request_id` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L246" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
request_id(self) -> str
|
||||
|
|
@ -154,7 +154,7 @@ request_id(self) -> str
|
|||
Get the unique ID for this request.
|
||||
|
||||
|
||||
#### `session_id` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L250" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `session_id` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L251" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
session_id(self) -> str
|
||||
|
|
@ -171,7 +171,7 @@ the same client session.
|
|||
- for other transports.
|
||||
|
||||
|
||||
#### `session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L294" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L295" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
session(self) -> ServerSession
|
||||
|
|
@ -180,7 +180,7 @@ session(self) -> ServerSession
|
|||
Access to the underlying session for advanced usage.
|
||||
|
||||
|
||||
#### `debug` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L299" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `debug` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L300" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
debug(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
|
||||
|
|
@ -189,7 +189,7 @@ debug(self, message: str, logger_name: str | None = None, extra: Mapping[str, An
|
|||
Send a debug log message.
|
||||
|
||||
|
||||
#### `info` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L310" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `info` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L311" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
info(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
|
||||
|
|
@ -198,7 +198,7 @@ info(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any
|
|||
Send an info log message.
|
||||
|
||||
|
||||
#### `warning` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L321" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `warning` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L322" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
warning(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
|
||||
|
|
@ -207,7 +207,7 @@ warning(self, message: str, logger_name: str | None = None, extra: Mapping[str,
|
|||
Send a warning log message.
|
||||
|
||||
|
||||
#### `error` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L332" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `error` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L333" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
error(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
|
||||
|
|
@ -216,7 +216,7 @@ error(self, message: str, logger_name: str | None = None, extra: Mapping[str, An
|
|||
Send an error log message.
|
||||
|
||||
|
||||
#### `list_roots` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L343" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_roots` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L344" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_roots(self) -> list[Root]
|
||||
|
|
@ -225,7 +225,7 @@ list_roots(self) -> list[Root]
|
|||
List the roots available to the server, as indicated by the client.
|
||||
|
||||
|
||||
#### `send_tool_list_changed` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L348" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `send_tool_list_changed` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L349" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
send_tool_list_changed(self) -> None
|
||||
|
|
@ -234,7 +234,7 @@ send_tool_list_changed(self) -> None
|
|||
Send a tool list changed notification to the client.
|
||||
|
||||
|
||||
#### `send_resource_list_changed` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L352" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `send_resource_list_changed` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L353" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
send_resource_list_changed(self) -> None
|
||||
|
|
@ -243,7 +243,7 @@ send_resource_list_changed(self) -> None
|
|||
Send a resource list changed notification to the client.
|
||||
|
||||
|
||||
#### `send_prompt_list_changed` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L356" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `send_prompt_list_changed` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L357" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
send_prompt_list_changed(self) -> None
|
||||
|
|
@ -252,10 +252,10 @@ send_prompt_list_changed(self) -> None
|
|||
Send a prompt list changed notification to the client.
|
||||
|
||||
|
||||
#### `sample` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L360" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `sample` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L361" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
sample(self, messages: str | list[str | SamplingMessage], system_prompt: str | None = None, include_context: IncludeContext | None = None, temperature: float | None = None, max_tokens: int | None = None, model_preferences: ModelPreferences | str | list[str] | None = None) -> ContentBlock
|
||||
sample(self, messages: str | Sequence[str | SamplingMessage], system_prompt: str | None = None, include_context: IncludeContext | None = None, temperature: float | None = None, max_tokens: int | None = None, model_preferences: ModelPreferences | str | list[str] | None = None) -> TextContent | ImageContent | AudioContent
|
||||
```
|
||||
|
||||
Send a sampling request to the client and await the response.
|
||||
|
|
@ -265,25 +265,25 @@ completion from the client. The client must be appropriately configured,
|
|||
or the request will error.
|
||||
|
||||
|
||||
#### `elicit` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L444" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `elicit` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L445" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
elicit(self, message: str, response_type: None) -> AcceptedElicitation[dict[str, Any]] | DeclinedElicitation | CancelledElicitation
|
||||
```
|
||||
|
||||
#### `elicit` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L456" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `elicit` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L457" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
elicit(self, message: str, response_type: type[T]) -> AcceptedElicitation[T] | DeclinedElicitation | CancelledElicitation
|
||||
```
|
||||
|
||||
#### `elicit` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L466" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `elicit` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L467" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
elicit(self, message: str, response_type: list[str]) -> AcceptedElicitation[str] | DeclinedElicitation | CancelledElicitation
|
||||
```
|
||||
|
||||
#### `elicit` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L475" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `elicit` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L476" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
elicit(self, message: str, response_type: type[T] | list[str] | None = None) -> AcceptedElicitation[T] | AcceptedElicitation[dict[str, Any]] | AcceptedElicitation[str] | DeclinedElicitation | CancelledElicitation
|
||||
|
|
@ -312,7 +312,7 @@ type or dataclass or BaseModel. If it is a primitive type, an
|
|||
object schema with a single "value" field will be generated.
|
||||
|
||||
|
||||
#### `get_http_request` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L568" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_http_request` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L569" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_http_request(self) -> Request
|
||||
|
|
@ -321,7 +321,7 @@ get_http_request(self) -> Request
|
|||
Get the active starlette request.
|
||||
|
||||
|
||||
#### `set_state` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L583" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_state` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L584" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_state(self, key: str, value: Any) -> None
|
||||
|
|
@ -330,7 +330,7 @@ set_state(self, key: str, value: Any) -> None
|
|||
Set a value in the context state.
|
||||
|
||||
|
||||
#### `get_state` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L587" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_state` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L588" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_state(self, key: str) -> Any
|
||||
|
|
|
|||
|
|
@ -22,18 +22,15 @@ The default serializer for Payloads in the logging middleware.
|
|||
|
||||
## Classes
|
||||
|
||||
### `LoggingMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/logging.py#L19" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `BaseLoggingMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/logging.py#L19" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Middleware that provides comprehensive request and response logging.
|
||||
|
||||
Logs all MCP messages with configurable detail levels. Useful for debugging,
|
||||
monitoring, and understanding server usage patterns.
|
||||
Base class for logging middleware.
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `on_message` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/logging.py#L91" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `on_message` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/logging.py#L110" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
on_message(self, context: MiddlewareContext[Any], call_next: CallNext[Any, Any]) -> Any
|
||||
|
|
@ -42,7 +39,16 @@ on_message(self, context: MiddlewareContext[Any], call_next: CallNext[Any, Any])
|
|||
Log all messages.
|
||||
|
||||
|
||||
### `StructuredLoggingMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/logging.py#L114" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `LoggingMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/logging.py#L143" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Middleware that provides comprehensive request and response logging.
|
||||
|
||||
Logs all MCP messages with configurable detail levels. Useful for debugging,
|
||||
monitoring, and understanding server usage patterns.
|
||||
|
||||
|
||||
### `StructuredLoggingMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/logging.py#L198" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Middleware that provides structured JSON logging for better log analysis.
|
||||
|
|
@ -50,14 +56,3 @@ Middleware that provides structured JSON logging for better log analysis.
|
|||
Outputs structured logs that are easier to parse and analyze with log
|
||||
aggregation tools like ELK stack, Splunk, or cloud logging services.
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `on_message` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/logging.py#L185" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
on_message(self, context: MiddlewareContext[Any], call_next: CallNext[Any, Any]) -> Any
|
||||
```
|
||||
|
||||
Log structured message information.
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ sidebarTitle: middleware
|
|||
|
||||
## Functions
|
||||
|
||||
### `make_middleware_wrapper` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L66" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `make_middleware_wrapper` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L67" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
make_middleware_wrapper(middleware: Middleware, call_next: CallNext[T, R]) -> CallNext[T, R]
|
||||
|
|
@ -21,9 +21,9 @@ passed to other functions that expect a call_next function.
|
|||
|
||||
## Classes
|
||||
|
||||
### `CallNext` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L42" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `CallNext` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L43" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
### `MiddlewareContext` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L47" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `MiddlewareContext` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L48" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Unified context for all middleware operations.
|
||||
|
|
@ -31,13 +31,13 @@ Unified context for all middleware operations.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `copy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L62" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `copy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L63" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
copy(self, **kwargs: Any) -> MiddlewareContext[T]
|
||||
```
|
||||
|
||||
### `Middleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L79" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `Middleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L80" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Base class for FastMCP middleware with dispatching hooks.
|
||||
|
|
@ -45,61 +45,61 @@ Base class for FastMCP middleware with dispatching hooks.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `on_message` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L126" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `on_message` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L127" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
on_message(self, context: MiddlewareContext[Any], call_next: CallNext[Any, Any]) -> Any
|
||||
```
|
||||
|
||||
#### `on_request` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L133" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `on_request` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L134" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
on_request(self, context: MiddlewareContext[mt.Request], call_next: CallNext[mt.Request, Any]) -> Any
|
||||
```
|
||||
|
||||
#### `on_notification` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L140" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `on_notification` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L141" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
on_notification(self, context: MiddlewareContext[mt.Notification], call_next: CallNext[mt.Notification, Any]) -> Any
|
||||
```
|
||||
|
||||
#### `on_call_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L147" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `on_call_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L148" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
on_call_tool(self, context: MiddlewareContext[mt.CallToolRequestParams], call_next: CallNext[mt.CallToolRequestParams, ToolResult]) -> ToolResult
|
||||
```
|
||||
|
||||
#### `on_read_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L154" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `on_read_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L155" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
on_read_resource(self, context: MiddlewareContext[mt.ReadResourceRequestParams], call_next: CallNext[mt.ReadResourceRequestParams, mt.ReadResourceResult]) -> mt.ReadResourceResult
|
||||
on_read_resource(self, context: MiddlewareContext[mt.ReadResourceRequestParams], call_next: CallNext[mt.ReadResourceRequestParams, list[ReadResourceContents]]) -> list[ReadResourceContents]
|
||||
```
|
||||
|
||||
#### `on_get_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L161" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `on_get_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L162" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
on_get_prompt(self, context: MiddlewareContext[mt.GetPromptRequestParams], call_next: CallNext[mt.GetPromptRequestParams, mt.GetPromptResult]) -> mt.GetPromptResult
|
||||
```
|
||||
|
||||
#### `on_list_tools` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L168" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `on_list_tools` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L169" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
on_list_tools(self, context: MiddlewareContext[mt.ListToolsRequest], call_next: CallNext[mt.ListToolsRequest, list[Tool]]) -> list[Tool]
|
||||
```
|
||||
|
||||
#### `on_list_resources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L175" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `on_list_resources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L176" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
on_list_resources(self, context: MiddlewareContext[mt.ListResourcesRequest], call_next: CallNext[mt.ListResourcesRequest, list[Resource]]) -> list[Resource]
|
||||
```
|
||||
|
||||
#### `on_list_resource_templates` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L182" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `on_list_resource_templates` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L183" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
on_list_resource_templates(self, context: MiddlewareContext[mt.ListResourceTemplatesRequest], call_next: CallNext[mt.ListResourceTemplatesRequest, list[ResourceTemplate]]) -> list[ResourceTemplate]
|
||||
```
|
||||
|
||||
#### `on_list_prompts` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L189" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `on_list_prompts` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L190" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
on_list_prompts(self, context: MiddlewareContext[mt.ListPromptsRequest], call_next: CallNext[mt.ListPromptsRequest, list[Prompt]]) -> list[Prompt]
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ FastMCP - A more ergonomic interface for MCP servers.
|
|||
|
||||
## Functions
|
||||
|
||||
### `default_lifespan` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L98" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `default_lifespan` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L94" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
default_lifespan(server: FastMCP[LifespanResultT]) -> AsyncIterator[Any]
|
||||
|
|
@ -26,7 +26,7 @@ Default lifespan context manager that does nothing.
|
|||
- An empty context object
|
||||
|
||||
|
||||
### `add_resource_prefix` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2224" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `add_resource_prefix` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2229" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
add_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'path'] | None = None) -> str
|
||||
|
|
@ -64,7 +64,7 @@ add_resource_prefix("resource:///absolute/path", "prefix")
|
|||
- `ValueError`: If the URI doesn't match the expected protocol\://path format
|
||||
|
||||
|
||||
### `remove_resource_prefix` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2284" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `remove_resource_prefix` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2289" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
remove_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'path'] | None = None) -> str
|
||||
|
|
@ -103,7 +103,7 @@ remove_resource_prefix("resource://prefix//absolute/path", "prefix")
|
|||
- `ValueError`: If the URI doesn't match the expected protocol\://path format
|
||||
|
||||
|
||||
### `has_resource_prefix` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2351" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `has_resource_prefix` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2356" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
has_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'path'] | None = None) -> bool
|
||||
|
|
@ -143,28 +143,34 @@ False
|
|||
|
||||
## Classes
|
||||
|
||||
### `FastMCP` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L129" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `FastMCP` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L125" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `settings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L314" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `settings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L310" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
settings(self) -> Settings
|
||||
```
|
||||
|
||||
#### `name` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L325" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `name` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L321" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
name(self) -> str
|
||||
```
|
||||
|
||||
#### `instructions` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L329" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `instructions` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L325" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
instructions(self) -> str | None
|
||||
```
|
||||
|
||||
#### `instructions` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L329" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
instructions(self, value: str | None) -> None
|
||||
```
|
||||
|
||||
#### `version` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L333" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
|
|
@ -503,8 +509,8 @@ def get_weather(city: str) -> str:
|
|||
return f"Weather for {city}"
|
||||
|
||||
@server.resource("resource://{city}/weather")
|
||||
def get_weather_with_context(city: str, ctx: Context) -> str:
|
||||
ctx.info(f"Fetching weather for {city}")
|
||||
async def get_weather_with_context(city: str, ctx: Context) -> str:
|
||||
await ctx.info(f"Fetching weather for {city}")
|
||||
return f"Weather for {city}"
|
||||
|
||||
@server.resource("resource://{city}/weather")
|
||||
|
|
@ -583,8 +589,8 @@ Decorator to register a prompt.
|
|||
]
|
||||
|
||||
@server.prompt()
|
||||
def analyze_with_context(table_name: str, ctx: Context) -> list[Message]:
|
||||
ctx.info(f"Analyzing table {table_name}")
|
||||
async def analyze_with_context(table_name: str, ctx: Context) -> list[Message]:
|
||||
await ctx.info(f"Analyzing table {table_name}")
|
||||
schema = read_table_schema(table_name)
|
||||
return [
|
||||
{
|
||||
|
|
@ -595,7 +601,7 @@ Decorator to register a prompt.
|
|||
]
|
||||
|
||||
@server.prompt("custom_name")
|
||||
def analyze_file(path: str) -> list[Message]:
|
||||
async def analyze_file(path: str) -> list[Message]:
|
||||
content = await read_file(path)
|
||||
return [
|
||||
{
|
||||
|
|
@ -622,13 +628,17 @@ Decorator to register a prompt.
|
|||
#### `run_stdio_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1484" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
run_stdio_async(self, show_banner: bool = True) -> None
|
||||
run_stdio_async(self, show_banner: bool = True, log_level: str | None = None) -> None
|
||||
```
|
||||
|
||||
Run the server using stdio transport.
|
||||
|
||||
**Args:**
|
||||
- `show_banner`: Whether to display the server banner
|
||||
- `log_level`: Log level for the server
|
||||
|
||||
#### `run_http_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1504" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
#### `run_http_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1511" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
run_http_async(self, show_banner: bool = True, transport: Literal['http', 'streamable-http', 'sse'] = 'http', host: str | None = None, port: int | None = None, log_level: str | None = None, path: str | None = None, uvicorn_config: dict[str, Any] | None = None, middleware: list[ASGIMiddleware] | None = None, stateless_http: bool | None = None) -> None
|
||||
|
|
@ -647,7 +657,7 @@ Run the server using HTTP transport.
|
|||
- `stateless_http`: Whether to use stateless HTTP (defaults to settings.stateless_http)
|
||||
|
||||
|
||||
#### `run_sse_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1578" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `run_sse_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1585" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
run_sse_async(self, host: str | None = None, port: int | None = None, log_level: str | None = None, path: str | None = None, uvicorn_config: dict[str, Any] | None = None) -> None
|
||||
|
|
@ -656,7 +666,7 @@ run_sse_async(self, host: str | None = None, port: int | None = None, log_level:
|
|||
Run the server using SSE transport.
|
||||
|
||||
|
||||
#### `sse_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1606" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `sse_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1613" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
sse_app(self, path: str | None = None, message_path: str | None = None, middleware: list[ASGIMiddleware] | None = None) -> StarletteWithLifespan
|
||||
|
|
@ -670,7 +680,7 @@ Create a Starlette app for the SSE server.
|
|||
- `middleware`: A list of middleware to apply to the app
|
||||
|
||||
|
||||
#### `streamable_http_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1637" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `streamable_http_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1644" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
streamable_http_app(self, path: str | None = None, middleware: list[ASGIMiddleware] | None = None) -> StarletteWithLifespan
|
||||
|
|
@ -683,7 +693,7 @@ Create a Starlette app for the StreamableHTTP server.
|
|||
- `middleware`: A list of middleware to apply to the app
|
||||
|
||||
|
||||
#### `http_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1658" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `http_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1665" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
http_app(self, path: str | None = None, middleware: list[ASGIMiddleware] | None = None, json_response: bool | None = None, stateless_http: bool | None = None, transport: Literal['http', 'streamable-http', 'sse'] = 'http') -> StarletteWithLifespan
|
||||
|
|
@ -700,13 +710,13 @@ Create a Starlette app using the specified HTTP transport.
|
|||
- A Starlette application configured with the specified transport
|
||||
|
||||
|
||||
#### `run_streamable_http_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1707" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `run_streamable_http_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1714" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
run_streamable_http_async(self, host: str | None = None, port: int | None = None, log_level: str | None = None, path: str | None = None, uvicorn_config: dict[str, Any] | None = None) -> None
|
||||
```
|
||||
|
||||
#### `mount` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1732" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `mount` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1739" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
mount(self, server: FastMCP[LifespanResultT], prefix: str | None = None, as_proxy: bool | None = None) -> None
|
||||
|
|
@ -760,7 +770,7 @@ automatically determined based on whether the server has a custom lifespan
|
|||
- `prompt_separator`: Deprecated. Separator character for prompt names.
|
||||
|
||||
|
||||
#### `import_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1854" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `import_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1861" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
import_server(self, server: FastMCP[LifespanResultT], prefix: str | None = None, tool_separator: str | None = None, resource_separator: str | None = None, prompt_separator: str | None = None) -> None
|
||||
|
|
@ -801,7 +811,7 @@ applied using the protocol\://prefix/path format
|
|||
- `prompt_separator`: Deprecated. Separator for prompt names.
|
||||
|
||||
|
||||
#### `from_openapi` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1983" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `from_openapi` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1990" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
from_openapi(cls, openapi_spec: dict[str, Any], client: httpx.AsyncClient, route_maps: list[RouteMap] | list[RouteMapNew] | None = None, route_map_fn: OpenAPIRouteMapFn | OpenAPIRouteMapFnNew | None = None, mcp_component_fn: OpenAPIComponentFn | OpenAPIComponentFnNew | None = None, mcp_names: dict[str, str] | None = None, tags: set[str] | None = None, **settings: Any) -> FastMCPOpenAPI | FastMCPOpenAPINew
|
||||
|
|
@ -810,7 +820,7 @@ from_openapi(cls, openapi_spec: dict[str, Any], client: httpx.AsyncClient, route
|
|||
Create a FastMCP server from an OpenAPI specification.
|
||||
|
||||
|
||||
#### `from_fastapi` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2032" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `from_fastapi` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2039" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
from_fastapi(cls, app: Any, name: str | None = None, route_maps: list[RouteMap] | list[RouteMapNew] | None = None, route_map_fn: OpenAPIRouteMapFn | OpenAPIRouteMapFnNew | None = None, mcp_component_fn: OpenAPIComponentFn | OpenAPIComponentFnNew | None = None, mcp_names: dict[str, str] | None = None, httpx_client_kwargs: dict[str, Any] | None = None, tags: set[str] | None = None, **settings: Any) -> FastMCPOpenAPI | FastMCPOpenAPINew
|
||||
|
|
@ -819,7 +829,7 @@ from_fastapi(cls, app: Any, name: str | None = None, route_maps: list[RouteMap]
|
|||
Create a FastMCP server from a FastAPI application.
|
||||
|
||||
|
||||
#### `as_proxy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2095" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `as_proxy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2102" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
as_proxy(cls, backend: Client[ClientTransportT] | ClientTransport | FastMCP[Any] | AnyUrl | Path | MCPConfig | dict[str, Any] | str, **settings: Any) -> FastMCPProxy
|
||||
|
|
@ -833,7 +843,7 @@ instance or any value accepted as the `transport` argument of
|
|||
`fastmcp.client.Client` constructor.
|
||||
|
||||
|
||||
#### `from_client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2156" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `from_client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2161" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
from_client(cls, client: Client[ClientTransportT], **settings: Any) -> FastMCPProxy
|
||||
|
|
@ -842,10 +852,10 @@ from_client(cls, client: Client[ClientTransportT], **settings: Any) -> FastMCPPr
|
|||
Create a FastMCP proxy server from a FastMCP client.
|
||||
|
||||
|
||||
#### `generate_name` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2208" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `generate_name` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2213" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
generate_name(cls, name: str | None = None) -> str
|
||||
```
|
||||
|
||||
### `MountedServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2218" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `MountedServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2223" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ sidebarTitle: settings
|
|||
|
||||
## Classes
|
||||
|
||||
### `ExtendedEnvSettingsSource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L27" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ExtendedEnvSettingsSource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L30" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
A special EnvSettingsSource that allows for multiple env var prefixes to be used.
|
||||
|
|
@ -17,17 +17,17 @@ Raises a deprecation warning if the old `FASTMCP_SERVER_` prefix is used.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `get_field_value` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L34" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_field_value` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L37" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str, bool]
|
||||
```
|
||||
|
||||
### `ExtendedSettingsConfigDict` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L54" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ExtendedSettingsConfigDict` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L57" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
### `ExperimentalSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L58" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ExperimentalSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L61" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
### `Settings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L77" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `Settings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L80" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
FastMCP settings.
|
||||
|
|
@ -35,7 +35,7 @@ FastMCP settings.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `get_setting` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L89" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_setting` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L92" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_setting(self, attr: str) -> Any
|
||||
|
|
@ -45,7 +45,7 @@ Get a setting. If the setting contains one or more `__`, it will be
|
|||
treated as a nested setting.
|
||||
|
||||
|
||||
#### `set_setting` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L102" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_setting` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L105" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_setting(self, attr: str, value: Any) -> None
|
||||
|
|
@ -55,13 +55,13 @@ Set a setting. If the setting contains one or more `__`, it will be
|
|||
treated as a nested setting.
|
||||
|
||||
|
||||
#### `settings_customise_sources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L116" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `settings_customise_sources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L119" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
settings_customise_sources(cls, settings_cls: type[BaseSettings], init_settings: PydanticBaseSettingsSource, env_settings: PydanticBaseSettingsSource, dotenv_settings: PydanticBaseSettingsSource, file_secret_settings: PydanticBaseSettingsSource) -> tuple[PydanticBaseSettingsSource, ...]
|
||||
```
|
||||
|
||||
#### `settings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L134" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `settings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L137" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
settings(self) -> Self
|
||||
|
|
@ -71,8 +71,14 @@ This property is for backwards compatibility with FastMCP < 2.8.0,
|
|||
which accessed fastmcp.settings.settings
|
||||
|
||||
|
||||
#### `normalize_log_level` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L154" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `normalize_log_level` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L157" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
normalize_log_level(cls, v)
|
||||
```
|
||||
|
||||
#### `server_auth_class` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/settings.py#L364" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
server_auth_class(self) -> AuthProvider | None
|
||||
```
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ sidebarTitle: json_schema
|
|||
|
||||
## Functions
|
||||
|
||||
### `compress_schema` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/json_schema.py#L183" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `compress_schema` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/json_schema.py#L200" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
compress_schema(schema: dict, prune_params: list[str] | None = None, prune_defs: bool = True, prune_additional_properties: bool = True, prune_titles: bool = False) -> dict
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Logging utilities for FastMCP.
|
|||
|
||||
## Functions
|
||||
|
||||
### `get_logger` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/logging.py#L10" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `get_logger` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/logging.py#L13" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_logger(name: str) -> logging.Logger
|
||||
|
|
@ -26,10 +26,10 @@ Get a logger nested under FastMCP namespace.
|
|||
- a configured logger instance
|
||||
|
||||
|
||||
### `configure_logging` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/logging.py#L22" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `configure_logging` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/logging.py#L25" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
configure_logging(level: Literal['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] | int = 'INFO', logger: logging.Logger | None = None, enable_rich_tracebacks: bool = True, **rich_kwargs: Any) -> None
|
||||
configure_logging(level: Literal['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] | int = 'INFO', logger: logging.Logger | None = None, enable_rich_tracebacks: bool | None = None, **rich_kwargs: Any) -> None
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -40,3 +40,19 @@ Configure logging for FastMCP.
|
|||
- `level`: the log level to use
|
||||
- `rich_kwargs`: the parameters to use for creating RichHandler
|
||||
|
||||
|
||||
### `temporary_log_level` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/logging.py#L72" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
temporary_log_level(level: str | None, logger: logging.Logger | None = None, enable_rich_tracebacks: bool | None = None, **rich_kwargs: Any)
|
||||
```
|
||||
|
||||
|
||||
Context manager to temporarily set log level and restore it afterwards.
|
||||
|
||||
**Args:**
|
||||
- `level`: The temporary log level to set (e.g., "DEBUG", "INFO")
|
||||
- `logger`: Optional logger to configure (defaults to FastMCP logger)
|
||||
- `enable_rich_tracebacks`: Whether to enable rich tracebacks
|
||||
- `**rich_kwargs`: Additional parameters for RichHandler
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ command-line arguments.
|
|||
|
||||
## Functions
|
||||
|
||||
### `generate_schema` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py#L415" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `generate_schema` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py#L416" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
generate_schema(output_path: Path | str | None = None) -> dict[str, Any] | None
|
||||
|
|
|
|||
158
docs/python-sdk/fastmcp-utilities-storage.mdx
Normal file
158
docs/python-sdk/fastmcp-utilities-storage.mdx
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
---
|
||||
title: storage
|
||||
sidebarTitle: storage
|
||||
---
|
||||
|
||||
# `fastmcp.utilities.storage`
|
||||
|
||||
|
||||
Key-value storage utilities for persistent data management.
|
||||
|
||||
## Classes
|
||||
|
||||
### `KVStorage` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/storage.py#L16" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Protocol for key-value storage of JSON data.
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `get` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/storage.py#L19" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get(self, key: str) -> dict[str, Any] | None
|
||||
```
|
||||
|
||||
Get a JSON dict by key.
|
||||
|
||||
|
||||
#### `set` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/storage.py#L23" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set(self, key: str, value: dict[str, Any]) -> None
|
||||
```
|
||||
|
||||
Store a JSON dict by key.
|
||||
|
||||
|
||||
#### `delete` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/storage.py#L27" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
delete(self, key: str) -> None
|
||||
```
|
||||
|
||||
Delete a value by key.
|
||||
|
||||
|
||||
### `JSONFileStorage` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/storage.py#L32" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
File-based key-value storage for JSON data with automatic metadata tracking.
|
||||
|
||||
Each key-value pair is stored as a separate JSON file on disk.
|
||||
Keys are sanitized to be filesystem-safe.
|
||||
|
||||
The storage automatically wraps all data with metadata:
|
||||
- timestamp: Timestamp when the entry was last written
|
||||
|
||||
**Args:**
|
||||
- `cache_dir`: Directory for storing JSON files
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `get` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/storage.py#L72" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get(self, key: str) -> dict[str, Any] | None
|
||||
```
|
||||
|
||||
Get a JSON dict from storage by key.
|
||||
|
||||
**Args:**
|
||||
- `key`: The key to retrieve
|
||||
|
||||
**Returns:**
|
||||
- The stored dict or None if not found
|
||||
|
||||
|
||||
#### `set` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/storage.py#L100" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set(self, key: str, value: dict[str, Any]) -> None
|
||||
```
|
||||
|
||||
Store a JSON dict with metadata.
|
||||
|
||||
**Args:**
|
||||
- `key`: The key to store under
|
||||
- `value`: The dict to store
|
||||
|
||||
|
||||
#### `delete` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/storage.py#L123" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
delete(self, key: str) -> None
|
||||
```
|
||||
|
||||
Delete a value from storage.
|
||||
|
||||
**Args:**
|
||||
- `key`: The key to delete
|
||||
|
||||
|
||||
#### `cleanup_old_entries` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/storage.py#L134" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
cleanup_old_entries(self, max_age_seconds: int = 30 * 24 * 60 * 60) -> int
|
||||
```
|
||||
|
||||
Remove entries older than the specified age.
|
||||
|
||||
Uses the timestamp field to determine age.
|
||||
|
||||
**Args:**
|
||||
- `max_age_seconds`: Maximum age in seconds (default 30 days)
|
||||
|
||||
**Returns:**
|
||||
- Number of entries removed
|
||||
|
||||
|
||||
### `InMemoryStorage` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/storage.py#L183" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
In-memory key-value storage for JSON data.
|
||||
|
||||
Simple dict-based storage that doesn't persist across restarts.
|
||||
Useful for testing or environments where file storage isn't available.
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `get` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/storage.py#L194" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get(self, key: str) -> dict[str, Any] | None
|
||||
```
|
||||
|
||||
Get a JSON dict from memory by key.
|
||||
|
||||
|
||||
#### `set` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/storage.py#L198" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set(self, key: str, value: dict[str, Any]) -> None
|
||||
```
|
||||
|
||||
Store a JSON dict in memory.
|
||||
|
||||
|
||||
#### `delete` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/storage.py#L202" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
delete(self, key: str) -> None
|
||||
```
|
||||
|
||||
Delete a value from memory.
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ not pickleable, so we need a function that creates and runs one.
|
|||
- The server URL.
|
||||
|
||||
|
||||
### `caplog_for_fastmcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/tests.py#L141" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `caplog_for_fastmcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/tests.py#L143" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
caplog_for_fastmcp(caplog)
|
||||
|
|
@ -55,7 +55,7 @@ Context manager to capture logs from FastMCP loggers even when propagation is di
|
|||
|
||||
## Classes
|
||||
|
||||
### `HeadlessOAuth` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/tests.py#L152" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `HeadlessOAuth` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/tests.py#L154" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
OAuth provider that bypasses browser interaction for testing.
|
||||
|
|
@ -66,7 +66,7 @@ instead of opening a browser and running a callback server. Useful for automated
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `redirect_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/tests.py#L165" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `redirect_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/tests.py#L167" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
redirect_handler(self, authorization_url: str) -> None
|
||||
|
|
@ -75,7 +75,7 @@ redirect_handler(self, authorization_url: str) -> None
|
|||
Make HTTP request to authorization URL and store response for callback handler.
|
||||
|
||||
|
||||
#### `callback_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/tests.py#L171" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `callback_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/tests.py#L173" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
callback_handler(self) -> tuple[str, str | None]
|
||||
|
|
|
|||
|
|
@ -5,6 +5,52 @@ icon: "sparkles"
|
|||
tag: NEW
|
||||
---
|
||||
|
||||
<Update label="FastMCP 2.12.4" description="September 26, 2025" tags={["Releases"]}>
|
||||
<Card
|
||||
title="FastMCP 2.12.4: OIDC What You Did There"
|
||||
href="https://github.com/jlowin/fastmcp/releases/tag/v2.12.4"
|
||||
cta="Read the release notes"
|
||||
>
|
||||
FastMCP 2.12.4 adds comprehensive OIDC support and expands authentication options with AWS Cognito and Descope providers. The release also includes improvements to logging middleware, URL handling for nested resources, persistent OAuth client registration storage, and various fixes to the experimental OpenAPI parser.
|
||||
|
||||
🔐 **OIDC Configuration** brings native support for OpenID Connect, enabling seamless integration with enterprise identity providers.
|
||||
|
||||
🏢 **Enterprise Authentication** expands with AWS Cognito and Descope providers, broadening the authentication ecosystem.
|
||||
|
||||
🛠️ **Improved Reliability** through enhanced URL handling, persistent OAuth storage, and numerous parser fixes based on community feedback.
|
||||
</Card>
|
||||
</Update>
|
||||
|
||||
<Update label="FastMCP 2.12.3" description="September 17, 2025" tags={["Releases"]}>
|
||||
<Card
|
||||
title="FastMCP 2.12.3: Double Time"
|
||||
href="https://github.com/jlowin/fastmcp/releases/tag/v2.12.3"
|
||||
cta="Read the release notes"
|
||||
>
|
||||
FastMCP 2.12.3 focuses on performance and developer experience improvements. This release includes optimized auth provider imports that reduce server startup time, enhanced OIDC authentication flows, and automatic inline snapshot creation for testing.
|
||||
</Card>
|
||||
</Update>
|
||||
|
||||
<Update label="FastMCP 2.12.2" description="September 3, 2025" tags={["Releases"]}>
|
||||
<Card
|
||||
title="FastMCP 2.12.2: Perchance to Stream"
|
||||
href="https://github.com/jlowin/fastmcp/releases/tag/v2.12.2"
|
||||
cta="Read the release notes"
|
||||
>
|
||||
Hotfix for streamable-http transport validation in fastmcp.json configuration files, resolving a parsing error when CLI arguments were merged against the configuration spec.
|
||||
</Card>
|
||||
</Update>
|
||||
|
||||
<Update label="FastMCP 2.12.1" description="September 3, 2025" tags={["Releases"]}>
|
||||
<Card
|
||||
title="FastMCP 2.12.1: OAuth to Joy"
|
||||
href="https://github.com/jlowin/fastmcp/releases/tag/v2.12.1"
|
||||
cta="Read the release notes"
|
||||
>
|
||||
FastMCP 2.12.1 strengthens OAuth proxy implementation with improved client storage reliability, PKCE forwarding, configurable token endpoint authentication methods, and expanded scope handling based on extensive community testing.
|
||||
</Card>
|
||||
</Update>
|
||||
|
||||
<Update label="FastMCP 2.12" description="August 31, 2025" tags={["Releases"]}>
|
||||
<Card
|
||||
title="FastMCP 2.12: Auth to the Races"
|
||||
|
|
|
|||
25
examples/auth/authkit_dcr/README.md
Normal file
25
examples/auth/authkit_dcr/README.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# AuthKit DCR Example
|
||||
|
||||
Demonstrates FastMCP server protection with AuthKit Dynamic Client Registration.
|
||||
|
||||
## Setup
|
||||
|
||||
1. Set your AuthKit domain:
|
||||
|
||||
```bash
|
||||
export FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_AUTHKIT_DOMAIN="https://your-app.authkit.app"
|
||||
```
|
||||
|
||||
2. Run the server:
|
||||
|
||||
```bash
|
||||
python server.py
|
||||
```
|
||||
|
||||
3. In another terminal, run the client:
|
||||
|
||||
```bash
|
||||
python client.py
|
||||
```
|
||||
|
||||
The client will open your browser for AuthKit authentication.
|
||||
32
examples/auth/authkit_dcr/client.py
Normal file
32
examples/auth/authkit_dcr/client.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
"""OAuth client example for connecting to FastMCP servers.
|
||||
|
||||
This example demonstrates how to connect to an OAuth-protected FastMCP server.
|
||||
|
||||
To run:
|
||||
python client.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from fastmcp.client import Client
|
||||
|
||||
SERVER_URL = "http://127.0.0.1:8000/mcp"
|
||||
|
||||
|
||||
async def main():
|
||||
try:
|
||||
async with Client(SERVER_URL, auth="oauth") as client:
|
||||
assert await client.ping()
|
||||
print("✅ Successfully authenticated!")
|
||||
|
||||
tools = await client.list_tools()
|
||||
print(f"🔧 Available tools ({len(tools)}):")
|
||||
for tool in tools:
|
||||
print(f" - {tool.name}: {tool.description}")
|
||||
except Exception as e:
|
||||
print(f"❌ Authentication failed: {e}")
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
33
examples/auth/authkit_dcr/server.py
Normal file
33
examples/auth/authkit_dcr/server.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
"""AuthKit DCR server example for FastMCP.
|
||||
|
||||
This example demonstrates how to protect a FastMCP server with AuthKit DCR.
|
||||
|
||||
Required environment variables:
|
||||
- FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_AUTHKIT_DOMAIN: Your AuthKit domain (e.g., "https://your-app.authkit.app")
|
||||
|
||||
To run:
|
||||
python server.py
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.workos import AuthKitProvider
|
||||
|
||||
auth = AuthKitProvider(
|
||||
authkit_domain=os.getenv("FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_AUTHKIT_DOMAIN")
|
||||
or "",
|
||||
base_url="http://localhost:8000",
|
||||
)
|
||||
|
||||
mcp = FastMCP("AuthKit DCR Example Server", auth=auth)
|
||||
|
||||
|
||||
@mcp.tool
|
||||
def echo(message: str) -> str:
|
||||
"""Echo the provided message."""
|
||||
return message
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="http", port=8000)
|
||||
54
examples/auth/scalekit_oauth/README.md
Normal file
54
examples/auth/scalekit_oauth/README.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# Scalekit OAuth Example
|
||||
|
||||
Demonstrates FastMCP server protection with Scalekit OAuth.
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Configure MCP server in Scalekit environment
|
||||
|
||||
**Create a Scalekit Account**:
|
||||
|
||||
- Go to [Scalekit Dashboard](https://app.scalekit.com/)
|
||||
- Navigate to **Developers** → **Settings**
|
||||
- Copy your Environment URL, Client ID, and Client Secret
|
||||
|
||||
**Register Your MCP Server**:
|
||||
|
||||
- Go to **MCP Servers** → **Create New Server**
|
||||
- Fill in your MCP server details
|
||||
- Note the **Resource ID** (e.g., `res_123`)
|
||||
|
||||
Create a `.env` file:
|
||||
|
||||
```bash
|
||||
# Required Scalekit credentials
|
||||
SCALEKIT_ENVIRONMENT_URL=<YOUR_APP_ENVIRONMENT_URL>
|
||||
SCALEKIT_CLIENT_ID=<YOUR_APP_CLIENT_ID> # skc_7008EXAMPLE46
|
||||
SCALEKIT_RESOURCE_ID=<YOUR_APP_RESOURCE_ID> # res_926EXAMPLE5878
|
||||
MCP_URL=http://localhost:8000/mcp
|
||||
```
|
||||
|
||||
### 2. Run the Example
|
||||
|
||||
Start the server:
|
||||
|
||||
```bash
|
||||
# From this directory
|
||||
uv run python server.py
|
||||
```
|
||||
|
||||
The server will start on `http://localhost:8000/mcp` with Scalekit OAuth authentication enabled.
|
||||
|
||||
Test with client:
|
||||
|
||||
```bash
|
||||
uv run python client.py
|
||||
```
|
||||
|
||||
The `client.py` will:
|
||||
|
||||
1. Attempt to connect to the server
|
||||
2. Detect that OAuth authentication is required
|
||||
3. Open a browser for Scalekit authentication
|
||||
4. Complete the OAuth flow and connect to the server
|
||||
5. Demonstrate calling authenticated tools
|
||||
41
examples/auth/scalekit_oauth/client.py
Normal file
41
examples/auth/scalekit_oauth/client.py
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
"""OAuth client example for connecting to Scalekit-protected FastMCP servers.
|
||||
|
||||
This example demonstrates how to connect to a Scalekit OAuth-protected FastMCP server.
|
||||
|
||||
To run:
|
||||
python client.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from fastmcp.client import Client
|
||||
|
||||
SERVER_URL = "http://127.0.0.1:8000/mcp"
|
||||
|
||||
|
||||
async def main():
|
||||
try:
|
||||
async with Client(SERVER_URL, auth="oauth") as client:
|
||||
assert await client.ping()
|
||||
print("✅ Successfully authenticated with Scalekit!")
|
||||
|
||||
tools = await client.list_tools()
|
||||
print(f"🔧 Available tools ({len(tools)}):")
|
||||
for tool in tools:
|
||||
print(f" - {tool.name}: {tool.description}")
|
||||
|
||||
# Test calling a tool
|
||||
result = await client.call_tool("echo", {"message": "Hello from Scalekit!"})
|
||||
print(f"🎯 Echo result: {result}")
|
||||
|
||||
# Test calling auth status tool
|
||||
auth_status = await client.call_tool("auth_status", {})
|
||||
print(f"👤 Auth status: {auth_status}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Authentication failed: {e}")
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
48
examples/auth/scalekit_oauth/server.py
Normal file
48
examples/auth/scalekit_oauth/server.py
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
"""Scalekit OAuth server example for FastMCP.
|
||||
|
||||
This example demonstrates how to protect a FastMCP server with Scalekit OAuth.
|
||||
|
||||
Required environment variables:
|
||||
- SCALEKIT_ENVIRONMENT_URL: Your Scalekit environment URL (e.g., "https://your-env.scalekit.com")
|
||||
- SCALEKIT_CLIENT_ID: Your Scalekit OAuth application client ID
|
||||
- SCALEKIT_RESOURCE_ID: Your Scalekit resource ID
|
||||
|
||||
To run:
|
||||
python server.py
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.scalekit import ScalekitProvider
|
||||
|
||||
auth = ScalekitProvider(
|
||||
environment_url=os.getenv("SCALEKIT_ENVIRONMENT_URL")
|
||||
or "https://your-env.scalekit.com",
|
||||
client_id=os.getenv("SCALEKIT_CLIENT_ID") or "",
|
||||
resource_id=os.getenv("SCALEKIT_RESOURCE_ID") or "",
|
||||
mcp_url=os.getenv("MCP_URL", "http://localhost:8000/mcp"),
|
||||
)
|
||||
|
||||
mcp = FastMCP("Scalekit OAuth Example Server", auth=auth)
|
||||
|
||||
|
||||
@mcp.tool
|
||||
def echo(message: str) -> str:
|
||||
"""Echo the provided message."""
|
||||
return message
|
||||
|
||||
|
||||
@mcp.tool
|
||||
def auth_status() -> dict:
|
||||
"""Show Scalekit authentication status."""
|
||||
# In a real implementation, you would extract user info from the JWT token
|
||||
return {
|
||||
"message": "This tool requires authentication via Scalekit",
|
||||
"authenticated": True,
|
||||
"provider": "Scalekit",
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="http", port=8000)
|
||||
|
|
@ -1,159 +1,27 @@
|
|||
# WorkOS OAuth Example
|
||||
|
||||
This example demonstrates how to use the WorkOS OAuth provider with FastMCP servers.
|
||||
|
||||
## Overview
|
||||
|
||||
The WorkOS OAuth provider enables authentication using WorkOS User Management. It provides general OAuth2 authentication similar to GitHub or Google, with optional support for enterprise SSO connections. Unlike the AuthKit provider which uses DCR (Dynamic Client Registration), this provider works with traditional OAuth flows.
|
||||
Demonstrates FastMCP server protection with WorkOS OAuth.
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. WorkOS Configuration
|
||||
1. Create a WorkOS application and copy your credentials:
|
||||
|
||||
1. **Create a WorkOS Application**:
|
||||
- Go to [WorkOS Dashboard → Applications](https://dashboard.workos.com/applications)
|
||||
- Create a new application or use an existing one
|
||||
- Enable **User Management** for OAuth authentication
|
||||
- Copy your `Client ID` and `API Key` (client secret)
|
||||
```bash
|
||||
export WORKOS_CLIENT_ID="your-client-id"
|
||||
export WORKOS_CLIENT_SECRET="your-client-secret"
|
||||
export WORKOS_AUTHKIT_DOMAIN="https://your-app.authkit.app"
|
||||
```
|
||||
|
||||
2. **Configure SSO Connection** (optional for enterprise SSO):
|
||||
- Go to WorkOS Dashboard → Connections
|
||||
- Set up your SSO connection (SAML, OIDC, or OAuth provider like Google/Microsoft)
|
||||
- Note the `Organization ID` or `Connection ID` if using SSO
|
||||
2. Run the server:
|
||||
|
||||
3. **Set Redirect URLs**:
|
||||
- In your WorkOS application settings, add redirect URLs for your OAuth flow
|
||||
- For this example: `http://localhost:8000/auth/callback`
|
||||
```bash
|
||||
python server.py
|
||||
```
|
||||
|
||||
### 2. Environment Variables
|
||||
3. In another terminal, run the client:
|
||||
|
||||
Create a `.env` file in this directory:
|
||||
```bash
|
||||
python client.py
|
||||
```
|
||||
|
||||
```bash
|
||||
# Required WorkOS credentials
|
||||
WORKOS_CLIENT_ID=client_123
|
||||
WORKOS_API_KEY=sk_test_456 # Your WorkOS API key (client secret)
|
||||
|
||||
# Server URL (optional, defaults to http://localhost:8000)
|
||||
# WORKOS_BASE_URL=http://localhost:8000
|
||||
|
||||
# Optional: For enterprise SSO connections
|
||||
# WORKOS_ORGANIZATION_ID=org_123 # Route to specific organization's SSO
|
||||
# WORKOS_CONNECTION_ID=conn_456 # Route to specific SSO connection
|
||||
|
||||
# Optional: Required scopes
|
||||
# FASTMCP_SERVER_AUTH_WORKOS_REQUIRED_SCOPES=["profile", "email"]
|
||||
```
|
||||
|
||||
### 3. Install Dependencies
|
||||
|
||||
```bash
|
||||
cd /Users/jlowin/Developer/fastmcp
|
||||
uv sync
|
||||
```
|
||||
|
||||
## Running the Example
|
||||
|
||||
### Start the Server
|
||||
|
||||
```bash
|
||||
# From this directory
|
||||
uv run python server.py
|
||||
```
|
||||
|
||||
The server will start on `http://localhost:8000` with WorkOS OAuth authentication enabled.
|
||||
|
||||
### Test with Client
|
||||
|
||||
In another terminal:
|
||||
|
||||
```bash
|
||||
# From this directory
|
||||
uv run python client.py
|
||||
```
|
||||
|
||||
The client will:
|
||||
1. Attempt to connect to the server
|
||||
2. Detect that OAuth authentication is required
|
||||
3. Open a browser for WorkOS authentication
|
||||
4. Complete the OAuth flow and connect to the server
|
||||
5. Demonstrate calling authenticated tools
|
||||
|
||||
## How It Works
|
||||
|
||||
### Authentication Flow
|
||||
|
||||
1. **Client Request**: Client attempts to connect to FastMCP server
|
||||
2. **Auth Challenge**: Server responds with `401 Unauthorized` and `WWW-Authenticate` header
|
||||
3. **OAuth Discovery**: Client discovers OAuth endpoints from server metadata
|
||||
4. **Authorization**: Client redirects user to WorkOS for authentication
|
||||
5. **Callback**: WorkOS redirects back with authorization code
|
||||
6. **Token Exchange**: Client exchanges code for access token
|
||||
7. **API Calls**: Client uses access token for authenticated MCP requests
|
||||
|
||||
### Server Components
|
||||
|
||||
- **WorkOSProvider**: Validates tokens using WorkOS User Management API
|
||||
- **Protected Resources**: MCP tools and resources require valid WorkOS tokens
|
||||
- **OAuth Metadata**: Server advertises WorkOS as authorization server
|
||||
|
||||
### Client Components
|
||||
|
||||
- **OAuth Client**: Handles browser-based OAuth flow
|
||||
- **Token Storage**: Caches tokens for future use
|
||||
- **Automatic Auth**: Transparently handles authentication
|
||||
|
||||
## Key Features
|
||||
|
||||
- **SSO Integration**: Works with any WorkOS SSO connection
|
||||
- **User Management**: Validates tokens against WorkOS User Management API
|
||||
- **Token Caching**: Reuses tokens across sessions
|
||||
- **Error Handling**: Graceful handling of auth failures and token expiration
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **"Invalid client" error**: Check CLIENT_ID and CLIENT_SECRET
|
||||
2. **"Token validation failed"**: Check API_KEY and token scope
|
||||
3. **"Redirect URI mismatch"**: Ensure redirect URL matches WorkOS settings
|
||||
4. **Browser doesn't open**: Check firewall settings for localhost
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Enable debug logging:
|
||||
|
||||
```python
|
||||
import logging
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
```
|
||||
|
||||
### Token Inspection
|
||||
|
||||
Check cached tokens:
|
||||
|
||||
```bash
|
||||
ls ~/.fastmcp/oauth-mcp-client-cache/
|
||||
```
|
||||
|
||||
Clear token cache:
|
||||
|
||||
```python
|
||||
from fastmcp.client.auth.oauth import FileTokenStorage
|
||||
FileTokenStorage.clear_all()
|
||||
```
|
||||
|
||||
## Security Notes
|
||||
|
||||
- Never commit `.env` files with real credentials
|
||||
- Use HTTPS in production
|
||||
- Rotate API keys regularly
|
||||
- Monitor WorkOS logs for unusual activity
|
||||
- Set appropriate token expiration times
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Explore WorkOS Directory Sync for user provisioning
|
||||
- Set up multi-organization support
|
||||
- Implement role-based access control
|
||||
- Add custom scopes and claims validation
|
||||
The client will open your browser for WorkOS authentication.
|
||||
|
|
|
|||
|
|
@ -142,3 +142,6 @@ extend-select = ["I", "UP"]
|
|||
"__init__.py" = ["F401", "I001", "RUF013"]
|
||||
# allow imports not at the top of the file
|
||||
"src/fastmcp/__init__.py" = ["E402"]
|
||||
|
||||
[tool.codespell]
|
||||
ignore-words-list = "asend,shttp,te"
|
||||
|
|
|
|||
|
|
@ -104,7 +104,6 @@ def install_gemini_cli(
|
|||
)
|
||||
return False
|
||||
|
||||
# Build uv run command using Environment.build_uv_run_command()
|
||||
env_config = UVEnvironment(
|
||||
python=python_version,
|
||||
dependencies=(with_packages or []) + ["fastmcp"],
|
||||
|
|
|
|||
|
|
@ -91,12 +91,12 @@ class MyComponent(MCPMixin):
|
|||
# prompt
|
||||
@mcp_prompt(name="A prompt")
|
||||
def prompt_method(self, name):
|
||||
return f"Whats up {name}?"
|
||||
return f"What's up {name}?"
|
||||
|
||||
# disabled prompt
|
||||
@mcp_prompt(name="A prompt", enabled=False)
|
||||
def prompt_method(self, name):
|
||||
return f"Whats up {name}?"
|
||||
return f"What's up {name}?"
|
||||
|
||||
mcp_server = FastMCP()
|
||||
component = MyComponent()
|
||||
|
|
|
|||
181
src/fastmcp/server/auth/providers/scalekit.py
Normal file
181
src/fastmcp/server/auth/providers/scalekit.py
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
"""Scalekit authentication provider for FastMCP.
|
||||
|
||||
This module provides ScalekitProvider - a complete authentication solution that integrates
|
||||
with Scalekit's OAuth 2.1 and OpenID Connect services, supporting Resource Server
|
||||
authentication for seamless MCP client authentication.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from pydantic import AnyHttpUrl
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
from starlette.responses import JSONResponse
|
||||
from starlette.routing import Route
|
||||
|
||||
from fastmcp.server.auth import RemoteAuthProvider, TokenVerifier
|
||||
from fastmcp.server.auth.providers.jwt import JWTVerifier
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
from fastmcp.utilities.types import NotSet, NotSetT
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class ScalekitProviderSettings(BaseSettings):
|
||||
model_config = SettingsConfigDict(
|
||||
env_prefix="FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_",
|
||||
env_file=".env",
|
||||
extra="ignore",
|
||||
)
|
||||
|
||||
environment_url: AnyHttpUrl
|
||||
client_id: str
|
||||
resource_id: str
|
||||
mcp_url: AnyHttpUrl
|
||||
|
||||
|
||||
class ScalekitProvider(RemoteAuthProvider):
|
||||
"""Scalekit resource server provider for OAuth 2.1 authentication.
|
||||
|
||||
This provider implements Scalekit integration using resource server pattern.
|
||||
FastMCP acts as a protected resource server that validates access tokens issued
|
||||
by Scalekit's authorization server.
|
||||
|
||||
IMPORTANT SETUP REQUIREMENTS:
|
||||
|
||||
1. Create an MCP Server in Scalekit Dashboard:
|
||||
- Go to your [Scalekit Dashboard](https://app.scalekit.com/)
|
||||
- Navigate to MCP Servers section
|
||||
- Register a new MCP Server with appropriate scopes
|
||||
- Ensure the Resource Identifier matches exactly what you configure as MCP URL
|
||||
- Note the Resource ID
|
||||
|
||||
2. Environment Configuration:
|
||||
- Set SCALEKIT_ENVIRONMENT_URL (e.g., https://your-env.scalekit.com)
|
||||
- Set SCALEKIT_CLIENT_ID from your OAuth application
|
||||
- Set SCALEKIT_RESOURCE_ID from your created resource
|
||||
- Set MCP_URL to your FastMCP server's public URL
|
||||
|
||||
For detailed setup instructions, see:
|
||||
https://docs.scalekit.com/mcp/overview/
|
||||
|
||||
Example:
|
||||
```python
|
||||
from fastmcp.server.auth.providers.scalekit import ScalekitProvider
|
||||
|
||||
# Create Scalekit resource server provider
|
||||
scalekit_auth = ScalekitProvider(
|
||||
environment_url="https://your-env.scalekit.com",
|
||||
client_id="sk_client_...",
|
||||
resource_id="sk_resource_...",
|
||||
mcp_url="https://your-fastmcp-server.com",
|
||||
)
|
||||
|
||||
# Use with FastMCP
|
||||
mcp = FastMCP("My App", auth=scalekit_auth)
|
||||
```
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
environment_url: AnyHttpUrl | str | NotSetT = NotSet,
|
||||
client_id: str | NotSetT = NotSet,
|
||||
resource_id: str | NotSetT = NotSet,
|
||||
mcp_url: AnyHttpUrl | str | NotSetT = NotSet,
|
||||
token_verifier: TokenVerifier | None = None,
|
||||
):
|
||||
"""Initialize Scalekit resource server provider.
|
||||
|
||||
Args:
|
||||
environment_url: Your Scalekit environment URL (e.g., "https://your-env.scalekit.com")
|
||||
client_id: Your Scalekit OAuth client ID
|
||||
resource_id: Your Scalekit resource ID
|
||||
mcp_url: Public URL of this FastMCP server (used as audience)
|
||||
token_verifier: Optional token verifier. If None, creates JWT verifier for Scalekit
|
||||
"""
|
||||
settings = ScalekitProviderSettings.model_validate(
|
||||
{
|
||||
k: v
|
||||
for k, v in {
|
||||
"environment_url": environment_url,
|
||||
"client_id": client_id,
|
||||
"resource_id": resource_id,
|
||||
"mcp_url": mcp_url,
|
||||
}.items()
|
||||
if v is not NotSet
|
||||
}
|
||||
)
|
||||
|
||||
self.environment_url = str(settings.environment_url).rstrip("/")
|
||||
self.client_id = settings.client_id
|
||||
self.resource_id = settings.resource_id
|
||||
self.mcp_url = str(settings.mcp_url)
|
||||
|
||||
# Create default JWT verifier if none provided
|
||||
if token_verifier is None:
|
||||
token_verifier = JWTVerifier(
|
||||
jwks_uri=f"{self.environment_url}/keys",
|
||||
issuer=self.environment_url,
|
||||
algorithm="RS256",
|
||||
audience=self.mcp_url,
|
||||
)
|
||||
|
||||
# Initialize RemoteAuthProvider with Scalekit as the authorization server
|
||||
super().__init__(
|
||||
token_verifier=token_verifier,
|
||||
authorization_servers=[
|
||||
AnyHttpUrl(f"{self.environment_url}/resources/{self.resource_id}")
|
||||
],
|
||||
base_url=self.mcp_url,
|
||||
)
|
||||
|
||||
def get_routes(
|
||||
self,
|
||||
mcp_path: str | None = None,
|
||||
mcp_endpoint: Any | None = None,
|
||||
) -> list[Route]:
|
||||
"""Get OAuth routes including Scalekit authorization server metadata forwarding.
|
||||
|
||||
This returns the standard protected resource routes plus an authorization server
|
||||
metadata endpoint that forwards Scalekit's OAuth metadata to clients.
|
||||
|
||||
Args:
|
||||
mcp_path: The path where the MCP endpoint is mounted (e.g., "/mcp")
|
||||
mcp_endpoint: The MCP endpoint handler to protect with auth
|
||||
"""
|
||||
# Get the standard protected resource routes from RemoteAuthProvider
|
||||
routes = super().get_routes(mcp_path, mcp_endpoint)
|
||||
|
||||
async def oauth_authorization_server_metadata(request):
|
||||
"""Forward Scalekit OAuth authorization server metadata with FastMCP customizations."""
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(
|
||||
f"{self.environment_url}/.well-known/oauth-authorization-server/resources/{self.resource_id}"
|
||||
)
|
||||
response.raise_for_status()
|
||||
metadata = response.json()
|
||||
return JSONResponse(metadata)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to fetch Scalekit metadata: {e}")
|
||||
return JSONResponse(
|
||||
{
|
||||
"error": "server_error",
|
||||
"error_description": f"Failed to fetch Scalekit metadata: {e}",
|
||||
},
|
||||
status_code=500,
|
||||
)
|
||||
|
||||
# Add Scalekit authorization server metadata forwarding
|
||||
routes.append(
|
||||
Route(
|
||||
"/.well-known/oauth-authorization-server",
|
||||
endpoint=oauth_authorization_server_metadata,
|
||||
methods=["GET"],
|
||||
)
|
||||
)
|
||||
|
||||
return routes
|
||||
|
|
@ -785,6 +785,7 @@ class FastMCPOpenAPI(FastMCP):
|
|||
http_routes = openapi.parse_openapi_to_http_routes(openapi_spec)
|
||||
|
||||
# Process routes
|
||||
num_excluded = 0
|
||||
route_maps = (route_maps or []) + DEFAULT_ROUTE_MAPPINGS
|
||||
for route in http_routes:
|
||||
# Determine route type based on mappings or default rules
|
||||
|
|
@ -823,8 +824,11 @@ class FastMCPOpenAPI(FastMCP):
|
|||
self._create_openapi_template(route, component_name, tags=route_tags)
|
||||
elif route_type == MCPType.EXCLUDE:
|
||||
logger.info(f"Excluding route: {route.method} {route.path}")
|
||||
num_excluded += 1
|
||||
|
||||
logger.info(f"Created FastMCP OpenAPI server with {len(http_routes)} routes")
|
||||
logger.info(
|
||||
f"Created FastMCP OpenAPI server with {len(http_routes) - num_excluded} routes"
|
||||
)
|
||||
|
||||
def _generate_default_name(
|
||||
self, route: openapi.HTTPRoute, mcp_names_map: dict[str, str] | None = None
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ def get_logger(name: str) -> logging.Logger:
|
|||
Returns:
|
||||
a configured logger instance
|
||||
"""
|
||||
return logging.getLogger(f"FastMCP.{name}")
|
||||
return logging.getLogger(f"fastmcp.{name}")
|
||||
|
||||
|
||||
def configure_logging(
|
||||
|
|
@ -45,7 +45,7 @@ def configure_logging(
|
|||
enable_rich_tracebacks = fastmcp.settings.enable_rich_tracebacks
|
||||
|
||||
if logger is None:
|
||||
logger = logging.getLogger("FastMCP")
|
||||
logger = logging.getLogger("fastmcp")
|
||||
|
||||
# Only configure the FastMCP logger namespace
|
||||
handler = RichHandler(
|
||||
|
|
|
|||
|
|
@ -143,10 +143,10 @@ def run_server_in_process(
|
|||
def caplog_for_fastmcp(caplog):
|
||||
"""Context manager to capture logs from FastMCP loggers even when propagation is disabled."""
|
||||
caplog.clear()
|
||||
logger = logging.getLogger("FastMCP")
|
||||
logger = logging.getLogger("fastmcp")
|
||||
logger.addHandler(caplog.handler)
|
||||
try:
|
||||
yield
|
||||
yield caplog
|
||||
finally:
|
||||
logger.removeHandler(caplog.handler)
|
||||
|
||||
|
|
|
|||
|
|
@ -457,12 +457,12 @@ async def test_client_nested_context_manager(fastmcp_server):
|
|||
assert client._session_state.session is not None
|
||||
session = client._session_state.session
|
||||
|
||||
# Re-use the same session
|
||||
# Reuse the same session
|
||||
async with client:
|
||||
assert client.is_connected()
|
||||
assert client._session_state.session is session
|
||||
|
||||
# Re-use the same session
|
||||
# Reuse the same session
|
||||
async with client:
|
||||
assert client.is_connected()
|
||||
assert client._session_state.session is session
|
||||
|
|
|
|||
162
tests/server/auth/providers/test_scalekit.py
Normal file
162
tests/server/auth/providers/test_scalekit.py
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
"""Tests for Scalekit OAuth provider."""
|
||||
|
||||
import os
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import patch
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from fastmcp import Client, FastMCP
|
||||
from fastmcp.client.transports import StreamableHttpTransport
|
||||
from fastmcp.server.auth.providers.scalekit import ScalekitProvider
|
||||
from fastmcp.utilities.tests import HeadlessOAuth, run_server_in_process
|
||||
|
||||
|
||||
class TestScalekitProvider:
|
||||
"""Test Scalekit OAuth provider functionality."""
|
||||
|
||||
def test_init_with_explicit_params(self):
|
||||
"""Test ScalekitProvider initialization with explicit parameters."""
|
||||
provider = ScalekitProvider(
|
||||
environment_url="https://my-env.scalekit.com",
|
||||
client_id="sk_client_123",
|
||||
resource_id="sk_resource_456",
|
||||
mcp_url="https://myserver.com/",
|
||||
)
|
||||
|
||||
assert provider.environment_url == "https://my-env.scalekit.com"
|
||||
assert provider.client_id == "sk_client_123"
|
||||
assert provider.resource_id == "sk_resource_456"
|
||||
assert str(provider.mcp_url) == "https://myserver.com/"
|
||||
|
||||
def test_init_with_env_vars(self):
|
||||
"""Test ScalekitProvider initialization from environment variables."""
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_ENVIRONMENT_URL": "https://env-scalekit.com",
|
||||
"FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_CLIENT_ID": "skc_123",
|
||||
"FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_RESOURCE_ID": "res_456",
|
||||
"FASTMCP_SERVER_AUTH_SCALEKITPROVIDER_MCP_URL": "https://envserver.com/mcp",
|
||||
},
|
||||
):
|
||||
provider = ScalekitProvider()
|
||||
|
||||
assert provider.environment_url == "https://env-scalekit.com"
|
||||
assert provider.client_id == "skc_123"
|
||||
assert provider.resource_id == "res_456"
|
||||
assert str(provider.mcp_url) == "https://envserver.com/mcp"
|
||||
|
||||
def test_environment_variable_loading(self):
|
||||
"""Test that environment variables are loaded correctly."""
|
||||
provider = ScalekitProvider(
|
||||
environment_url="https://test-env.scalekit.com",
|
||||
client_id="sk_client_test_123",
|
||||
resource_id="sk_resource_test_456",
|
||||
mcp_url="http://test-server.com",
|
||||
)
|
||||
|
||||
assert provider.environment_url == "https://test-env.scalekit.com"
|
||||
assert provider.client_id == "sk_client_test_123"
|
||||
assert provider.resource_id == "sk_resource_test_456"
|
||||
assert str(provider.mcp_url) == "http://test-server.com/"
|
||||
|
||||
def test_url_trailing_slash_handling(self):
|
||||
"""Test that URLs handle trailing slashes correctly."""
|
||||
provider = ScalekitProvider(
|
||||
environment_url="https://my-env.scalekit.com/",
|
||||
client_id="sk_client_123",
|
||||
resource_id="sk_resource_456",
|
||||
mcp_url="https://myserver.com/",
|
||||
)
|
||||
|
||||
assert provider.environment_url == "https://my-env.scalekit.com"
|
||||
assert str(provider.mcp_url) == "https://myserver.com/"
|
||||
|
||||
def test_jwt_verifier_configured_correctly(self):
|
||||
"""Test that JWT verifier is configured correctly."""
|
||||
provider = ScalekitProvider(
|
||||
environment_url="https://my-env.scalekit.com",
|
||||
client_id="sk_client_123",
|
||||
resource_id="sk_resource_456",
|
||||
mcp_url="https://myserver.com/",
|
||||
)
|
||||
|
||||
# Check that JWT verifier uses the correct endpoints
|
||||
assert (
|
||||
provider.token_verifier.jwks_uri # type: ignore[attr-defined]
|
||||
== "https://my-env.scalekit.com/keys"
|
||||
)
|
||||
assert (
|
||||
provider.token_verifier.issuer == "https://my-env.scalekit.com" # type: ignore[attr-defined]
|
||||
)
|
||||
assert provider.token_verifier.audience == "https://myserver.com/" # type: ignore[attr-defined]
|
||||
|
||||
def test_authorization_servers_configuration(self):
|
||||
"""Test that authorization servers are configured correctly."""
|
||||
provider = ScalekitProvider(
|
||||
environment_url="https://my-env.scalekit.com",
|
||||
client_id="sk_client_123",
|
||||
resource_id="sk_resource_456",
|
||||
mcp_url="https://myserver.com/",
|
||||
)
|
||||
|
||||
assert len(provider.authorization_servers) == 1
|
||||
assert (
|
||||
str(provider.authorization_servers[0])
|
||||
== "https://my-env.scalekit.com/resources/sk_resource_456"
|
||||
)
|
||||
|
||||
|
||||
def run_mcp_server(host: str, port: int) -> None:
|
||||
mcp = FastMCP(
|
||||
auth=ScalekitProvider(
|
||||
environment_url="https://test-env.scalekit.com",
|
||||
client_id="sk_client_test_123",
|
||||
resource_id="sk_resource_test_456",
|
||||
mcp_url="http://localhost:4321",
|
||||
)
|
||||
)
|
||||
|
||||
@mcp.tool
|
||||
def add(a: int, b: int) -> int:
|
||||
return a + b
|
||||
|
||||
mcp.run(host=host, port=port, transport="http")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mcp_server_url() -> Generator[str]:
|
||||
with run_server_in_process(run_mcp_server) as url:
|
||||
yield f"{url}/mcp"
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def client_with_headless_oauth(
|
||||
mcp_server_url: str,
|
||||
) -> Generator[Client, None, None]:
|
||||
"""Client with headless OAuth that bypasses browser interaction."""
|
||||
client = Client(
|
||||
transport=StreamableHttpTransport(mcp_server_url),
|
||||
auth=HeadlessOAuth(mcp_url=mcp_server_url),
|
||||
)
|
||||
yield client
|
||||
|
||||
|
||||
class TestScalekitProviderIntegration:
|
||||
async def test_unauthorized_access(self, mcp_server_url: str):
|
||||
with pytest.raises(httpx.HTTPStatusError) as exc_info:
|
||||
async with Client(mcp_server_url) as client:
|
||||
tools = await client.list_tools() # noqa: F841
|
||||
|
||||
assert isinstance(exc_info.value, httpx.HTTPStatusError)
|
||||
assert exc_info.value.response.status_code == 401
|
||||
assert "tools" not in locals()
|
||||
|
||||
# async def test_authorized_access(self, client_with_headless_oauth: Client):
|
||||
# async with client_with_headless_oauth:
|
||||
# tools = await client_with_headless_oauth.list_tools()
|
||||
# assert tools is not None
|
||||
# assert len(tools) > 0
|
||||
# assert "add" in tools
|
||||
|
|
@ -11,6 +11,7 @@ from fastmcp.server.middleware.error_handling import (
|
|||
RetryMiddleware,
|
||||
)
|
||||
from fastmcp.server.middleware.middleware import MiddlewareContext
|
||||
from fastmcp.utilities.tests import caplog_for_fastmcp
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
@ -60,8 +61,9 @@ class TestErrorHandlingMiddleware:
|
|||
middleware = ErrorHandlingMiddleware()
|
||||
error = ValueError("test error")
|
||||
|
||||
with caplog.at_level(logging.ERROR):
|
||||
middleware._log_error(error, mock_context)
|
||||
with caplog_for_fastmcp(caplog):
|
||||
with caplog.at_level(logging.ERROR):
|
||||
middleware._log_error(error, mock_context)
|
||||
|
||||
assert "Error in test_method: ValueError: test error" in caplog.text
|
||||
assert "ValueError:test_method" in middleware.error_counts
|
||||
|
|
@ -72,8 +74,9 @@ class TestErrorHandlingMiddleware:
|
|||
middleware = ErrorHandlingMiddleware(include_traceback=True)
|
||||
error = ValueError("test error")
|
||||
|
||||
with caplog.at_level(logging.ERROR):
|
||||
middleware._log_error(error, mock_context)
|
||||
with caplog_for_fastmcp(caplog):
|
||||
with caplog.at_level(logging.ERROR):
|
||||
middleware._log_error(error, mock_context)
|
||||
|
||||
assert "Error in test_method: ValueError: test error" in caplog.text
|
||||
# The traceback is added to the log message
|
||||
|
|
@ -95,8 +98,9 @@ class TestErrorHandlingMiddleware:
|
|||
middleware = ErrorHandlingMiddleware(error_callback=callback)
|
||||
error = ValueError("test error")
|
||||
|
||||
with caplog.at_level(logging.ERROR):
|
||||
middleware._log_error(error, mock_context)
|
||||
with caplog_for_fastmcp(caplog):
|
||||
with caplog.at_level(logging.ERROR):
|
||||
middleware._log_error(error, mock_context)
|
||||
|
||||
assert "Error in error callback: callback error" in caplog.text
|
||||
|
||||
|
|
@ -189,9 +193,10 @@ class TestErrorHandlingMiddleware:
|
|||
middleware = ErrorHandlingMiddleware()
|
||||
mock_call_next = AsyncMock(side_effect=ValueError("test error"))
|
||||
|
||||
with caplog.at_level(logging.ERROR):
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
await middleware.on_message(mock_context, mock_call_next)
|
||||
with caplog_for_fastmcp(caplog):
|
||||
with caplog.at_level(logging.ERROR):
|
||||
with pytest.raises(McpError) as exc_info:
|
||||
await middleware.on_message(mock_context, mock_call_next)
|
||||
|
||||
assert isinstance(exc_info.value, McpError)
|
||||
assert exc_info.value.error.code == -32602
|
||||
|
|
@ -293,8 +298,9 @@ class TestRetryMiddleware:
|
|||
]
|
||||
)
|
||||
|
||||
with caplog.at_level(logging.WARNING):
|
||||
result = await middleware.on_request(mock_context, mock_call_next)
|
||||
with caplog_for_fastmcp(caplog):
|
||||
with caplog.at_level(logging.WARNING):
|
||||
result = await middleware.on_request(mock_context, mock_call_next)
|
||||
|
||||
assert result == "test_result"
|
||||
assert mock_call_next.call_count == 3
|
||||
|
|
@ -307,9 +313,10 @@ class TestRetryMiddleware:
|
|||
# Fail all attempts
|
||||
mock_call_next = AsyncMock(side_effect=ConnectionError("connection failed"))
|
||||
|
||||
with caplog.at_level(logging.WARNING):
|
||||
with pytest.raises(ConnectionError):
|
||||
await middleware.on_request(mock_context, mock_call_next)
|
||||
with caplog_for_fastmcp(caplog):
|
||||
with caplog.at_level(logging.WARNING):
|
||||
with pytest.raises(ConnectionError):
|
||||
await middleware.on_request(mock_context, mock_call_next)
|
||||
|
||||
assert mock_call_next.call_count == 3 # initial + 2 retries
|
||||
assert "Retrying in" in caplog.text
|
||||
|
|
@ -385,14 +392,19 @@ class TestErrorHandlingMiddlewareIntegration:
|
|||
|
||||
error_handling_server.add_middleware(ErrorHandlingMiddleware())
|
||||
|
||||
with caplog.at_level(logging.ERROR):
|
||||
async with Client(error_handling_server) as client:
|
||||
# Test different types of errors
|
||||
with pytest.raises(Exception):
|
||||
await client.call_tool("failing_operation", {"error_type": "value"})
|
||||
with caplog_for_fastmcp(caplog):
|
||||
with caplog.at_level(logging.ERROR):
|
||||
async with Client(error_handling_server) as client:
|
||||
# Test different types of errors
|
||||
with pytest.raises(Exception):
|
||||
await client.call_tool(
|
||||
"failing_operation", {"error_type": "value"}
|
||||
)
|
||||
|
||||
with pytest.raises(Exception):
|
||||
await client.call_tool("failing_operation", {"error_type": "file"})
|
||||
with pytest.raises(Exception):
|
||||
await client.call_tool(
|
||||
"failing_operation", {"error_type": "file"}
|
||||
)
|
||||
|
||||
log_text = caplog.text
|
||||
|
||||
|
|
@ -443,17 +455,20 @@ class TestErrorHandlingMiddlewareIntegration:
|
|||
|
||||
error_handling_server.add_middleware(ErrorHandlingMiddleware())
|
||||
|
||||
with caplog.at_level(logging.ERROR):
|
||||
async with Client(error_handling_server) as client:
|
||||
# Successful operation (should not generate error logs)
|
||||
await client.call_tool("reliable_operation", {"data": "test"})
|
||||
with caplog_for_fastmcp(caplog):
|
||||
with caplog.at_level(logging.ERROR):
|
||||
async with Client(error_handling_server) as client:
|
||||
# Successful operation (should not generate error logs)
|
||||
await client.call_tool("reliable_operation", {"data": "test"})
|
||||
|
||||
# Failed operation (should generate error log)
|
||||
with pytest.raises(Exception):
|
||||
await client.call_tool("failing_operation", {"error_type": "value"})
|
||||
# Failed operation (should generate error log)
|
||||
with pytest.raises(Exception):
|
||||
await client.call_tool(
|
||||
"failing_operation", {"error_type": "value"}
|
||||
)
|
||||
|
||||
# Another successful operation
|
||||
await client.call_tool("reliable_operation", {"data": "test2"})
|
||||
# Another successful operation
|
||||
await client.call_tool("reliable_operation", {"data": "test2"})
|
||||
|
||||
log_text = caplog.text
|
||||
|
||||
|
|
@ -533,18 +548,19 @@ class TestRetryMiddlewareIntegration:
|
|||
)
|
||||
)
|
||||
|
||||
with caplog.at_level(logging.WARNING):
|
||||
async with Client(error_handling_server) as client:
|
||||
# This operation fails intermittently - try several times
|
||||
success_count = 0
|
||||
for _ in range(5):
|
||||
try:
|
||||
await client.call_tool(
|
||||
"intermittent_operation", {"fail_rate": 0.7}
|
||||
)
|
||||
success_count += 1
|
||||
except Exception:
|
||||
pass # Some failures expected even with retries
|
||||
with caplog_for_fastmcp(caplog):
|
||||
with caplog.at_level(logging.WARNING):
|
||||
async with Client(error_handling_server) as client:
|
||||
# This operation fails intermittently - try several times
|
||||
success_count = 0
|
||||
for _ in range(5):
|
||||
try:
|
||||
await client.call_tool(
|
||||
"intermittent_operation", {"fail_rate": 0.7}
|
||||
)
|
||||
success_count += 1
|
||||
except Exception:
|
||||
pass # Some failures expected even with retries
|
||||
|
||||
# Should have some retry log messages
|
||||
# Note: Retry logs might not appear if the underlying errors are wrapped by FastMCP
|
||||
|
|
@ -584,17 +600,22 @@ class TestRetryMiddlewareIntegration:
|
|||
)
|
||||
)
|
||||
|
||||
with caplog.at_level(logging.ERROR):
|
||||
async with Client(error_handling_server) as client:
|
||||
# Try intermittent operation
|
||||
try:
|
||||
await client.call_tool("intermittent_operation", {"fail_rate": 0.9})
|
||||
except Exception:
|
||||
pass # May still fail even with retries
|
||||
with caplog_for_fastmcp(caplog):
|
||||
with caplog.at_level(logging.ERROR):
|
||||
async with Client(error_handling_server) as client:
|
||||
# Try intermittent operation
|
||||
try:
|
||||
await client.call_tool(
|
||||
"intermittent_operation", {"fail_rate": 0.9}
|
||||
)
|
||||
except Exception:
|
||||
pass # May still fail even with retries
|
||||
|
||||
# Try permanent failure
|
||||
with pytest.raises(Exception):
|
||||
await client.call_tool("failing_operation", {"error_type": "value"})
|
||||
# Try permanent failure
|
||||
with pytest.raises(Exception):
|
||||
await client.call_tool(
|
||||
"failing_operation", {"error_type": "value"}
|
||||
)
|
||||
|
||||
log_text = caplog.text
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"""Tests for logging middleware."""
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
from typing import Any, Literal, TypeVar
|
||||
|
|
@ -10,14 +11,17 @@ import mcp
|
|||
import mcp.types
|
||||
import pytest
|
||||
from inline_snapshot import snapshot
|
||||
from pydantic import AnyUrl
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.client import Client
|
||||
from fastmcp.resources.template import ResourceTemplate
|
||||
from fastmcp.server.middleware.logging import (
|
||||
LoggingMiddleware,
|
||||
StructuredLoggingMiddleware,
|
||||
)
|
||||
from fastmcp.server.middleware.middleware import MiddlewareContext
|
||||
from fastmcp.server.middleware.middleware import CallNext, MiddlewareContext
|
||||
from fastmcp.utilities.tests import caplog_for_fastmcp
|
||||
|
||||
FIXED_DATE = datetime.datetime(2023, 1, 1, tzinfo=datetime.timezone.utc)
|
||||
|
||||
|
|
@ -186,7 +190,7 @@ class TestStructuredLoggingMiddleware:
|
|||
middleware = StructuredLoggingMiddleware()
|
||||
mock_call_next = AsyncMock(return_value="test_result")
|
||||
|
||||
with caplog.at_level(logging.INFO):
|
||||
with caplog_for_fastmcp(caplog):
|
||||
result = await middleware.on_message(mock_context, mock_call_next)
|
||||
|
||||
assert result == "test_result"
|
||||
|
|
@ -204,7 +208,7 @@ INFO fastmcp.structured:logging.py:LINE_NUMBER Completed message: {"event":
|
|||
middleware = StructuredLoggingMiddleware()
|
||||
mock_call_next = AsyncMock(side_effect=ValueError("test error"))
|
||||
|
||||
with caplog.at_level(logging.INFO):
|
||||
with caplog_for_fastmcp(caplog):
|
||||
with pytest.raises(ValueError):
|
||||
await middleware.on_message(mock_context, mock_call_next)
|
||||
|
||||
|
|
@ -247,6 +251,257 @@ class TestLoggingMiddleware:
|
|||
assert "payload=" in formatted
|
||||
assert "..." in formatted
|
||||
|
||||
async def test_on_message_failure(
|
||||
self, mock_context: MiddlewareContext[Any], caplog: pytest.LogCaptureFixture
|
||||
):
|
||||
"""Test structured logging of failed messages."""
|
||||
middleware = StructuredLoggingMiddleware()
|
||||
mock_call_next = AsyncMock(side_effect=ValueError("test error"))
|
||||
|
||||
with caplog_for_fastmcp(caplog):
|
||||
with pytest.raises(ValueError):
|
||||
await middleware.on_message(mock_context, mock_call_next)
|
||||
|
||||
# Check that we have structured JSON logs
|
||||
log_lines = [record.message for record in caplog.records]
|
||||
assert len(log_lines) == 2 # start and error entries
|
||||
|
||||
# Extract JSON from "Processing message: {JSON}"
|
||||
start_message = log_lines[0]
|
||||
assert start_message.startswith("Processing message: ")
|
||||
start_json = start_message[len("Processing message: ") :]
|
||||
start_entry = json.loads(start_json)
|
||||
assert start_entry["event"] == "request_start"
|
||||
|
||||
# Error messages have different format - check the second log entry
|
||||
assert "Failed message:" in log_lines[1]
|
||||
|
||||
async def test_on_message_with_pydantic_types_in_payload(
|
||||
self,
|
||||
mock_call_next: CallNext[Any, Any],
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
):
|
||||
"""Ensure Pydantic AnyUrl in payload serializes correctly when include_payloads=True."""
|
||||
|
||||
mock_context = new_mock_context(
|
||||
message=mcp.types.ReadResourceRequest(
|
||||
method="resources/read",
|
||||
params=mcp.types.ReadResourceRequestParams(
|
||||
uri=AnyUrl("test://example/1"),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
middleware = StructuredLoggingMiddleware(include_payloads=True)
|
||||
|
||||
with caplog_for_fastmcp(caplog):
|
||||
result = await middleware.on_message(mock_context, mock_call_next)
|
||||
|
||||
assert result == "test_result"
|
||||
|
||||
log_lines = [record.message for record in caplog.records]
|
||||
|
||||
assert len(log_lines) == 2
|
||||
|
||||
# Extract JSON from log messages
|
||||
start_message = log_lines[0]
|
||||
assert start_message.startswith("Processing message: ")
|
||||
start_json = start_message[len("Processing message: ") :]
|
||||
assert json.loads(start_json) == snapshot(
|
||||
{
|
||||
"event": "request_start",
|
||||
"timestamp": "2023-01-01T00:00:00+00:00",
|
||||
"source": "client",
|
||||
"type": "request",
|
||||
"method": "test_method",
|
||||
"payload": '{"method":"resources/read","params":{"_meta":null,"uri":"test://example/1"}}',
|
||||
"payload_type": "ReadResourceRequest",
|
||||
}
|
||||
)
|
||||
|
||||
success_message = log_lines[1]
|
||||
assert success_message.startswith("Completed message: ")
|
||||
success_json = success_message[len("Completed message: ") :]
|
||||
assert json.loads(success_json) == snapshot(
|
||||
{
|
||||
"event": "request_success",
|
||||
"timestamp": "2023-01-01T00:00:00+00:00",
|
||||
"source": "client",
|
||||
"type": "request",
|
||||
"method": "test_method",
|
||||
}
|
||||
)
|
||||
|
||||
async def test_on_message_with_resource_template_in_payload(
|
||||
self,
|
||||
mock_call_next: CallNext[Any, Any],
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
):
|
||||
"""Ensure ResourceTemplate in payload serializes via pydantic conversion without errors."""
|
||||
|
||||
mock_context = new_mock_context(
|
||||
message=ResourceTemplate(
|
||||
name="tmpl",
|
||||
uri_template="tmpl://{id}",
|
||||
parameters={"id": {"type": "string"}},
|
||||
)
|
||||
)
|
||||
|
||||
middleware = StructuredLoggingMiddleware(include_payloads=True)
|
||||
|
||||
with caplog_for_fastmcp(caplog):
|
||||
result = await middleware.on_message(mock_context, mock_call_next)
|
||||
|
||||
assert result == "test_result"
|
||||
|
||||
log_lines = [record.message for record in caplog.records]
|
||||
assert len(log_lines) == 2
|
||||
|
||||
# Extract JSON from log message
|
||||
start_message = log_lines[0]
|
||||
assert start_message.startswith("Processing message: ")
|
||||
start_json = start_message[len("Processing message: ") :]
|
||||
assert json.loads(start_json) == snapshot(
|
||||
{
|
||||
"event": "request_start",
|
||||
"timestamp": "2023-01-01T00:00:00+00:00",
|
||||
"source": "client",
|
||||
"type": "request",
|
||||
"method": "test_method",
|
||||
"payload": '{"name":"tmpl","title":null,"description":null,"tags":[],"meta":null,"enabled":true,"uri_template":"tmpl://{id}","mime_type":"text/plain","parameters":{"id":{"type":"string"}},"annotations":null}',
|
||||
"payload_type": "ResourceTemplate",
|
||||
}
|
||||
)
|
||||
|
||||
async def test_on_message_with_nonserializable_payload_falls_back_to_str(
|
||||
self, mock_call_next: CallNext[Any, Any], caplog: pytest.LogCaptureFixture
|
||||
):
|
||||
"""Ensure non-JSONable objects fall back to string serialization in payload."""
|
||||
|
||||
class NonSerializable:
|
||||
def __str__(self) -> str:
|
||||
return "NON_SERIALIZABLE"
|
||||
|
||||
mock_context = new_mock_context(
|
||||
message=mcp.types.CallToolRequest(
|
||||
method="tools/call",
|
||||
params=mcp.types.CallToolRequestParams(
|
||||
name="test_method",
|
||||
arguments={"obj": NonSerializable()},
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
middleware = StructuredLoggingMiddleware(include_payloads=True)
|
||||
|
||||
with caplog_for_fastmcp(caplog):
|
||||
result = await middleware.on_message(mock_context, mock_call_next)
|
||||
|
||||
assert result == "test_result"
|
||||
|
||||
log_lines = [record.message for record in caplog.records]
|
||||
assert len(log_lines) >= 2
|
||||
|
||||
# Extract JSON from log message
|
||||
start_message = log_lines[0]
|
||||
assert start_message.startswith("Processing message: ")
|
||||
start_json = start_message[len("Processing message: ") :]
|
||||
assert json.loads(start_json) == snapshot(
|
||||
{
|
||||
"event": "request_start",
|
||||
"timestamp": "2023-01-01T00:00:00+00:00",
|
||||
"source": "client",
|
||||
"type": "request",
|
||||
"method": "test_method",
|
||||
"payload": '{"method":"tools/call","params":{"_meta":null,"name":"test_method","arguments":{"obj":"NON_SERIALIZABLE"}}}',
|
||||
"payload_type": "CallToolRequest",
|
||||
}
|
||||
)
|
||||
|
||||
async def test_on_message_with_custom_serializer_applied(
|
||||
self, mock_call_next: CallNext[Any, Any], caplog: pytest.LogCaptureFixture
|
||||
):
|
||||
"""Ensure a custom serializer is used for non-JSONable payloads."""
|
||||
|
||||
# Provide a serializer that replaces entire payload with a fixed string
|
||||
def custom_serializer(_: Any) -> str:
|
||||
return "CUSTOM_PAYLOAD"
|
||||
|
||||
mock_context = new_mock_context(
|
||||
message=mcp.types.CallToolRequest(
|
||||
method="tools/call",
|
||||
params=mcp.types.CallToolRequestParams(
|
||||
name="test_method",
|
||||
arguments={"obj": "OBJECT"},
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
middleware = StructuredLoggingMiddleware(
|
||||
include_payloads=True, payload_serializer=custom_serializer
|
||||
)
|
||||
|
||||
with caplog_for_fastmcp(caplog):
|
||||
result = await middleware.on_message(mock_context, mock_call_next)
|
||||
|
||||
assert result == "test_result"
|
||||
|
||||
log_lines = [record.message for record in caplog.records]
|
||||
assert len(log_lines) >= 2
|
||||
|
||||
# Extract JSON from log message
|
||||
start_message = log_lines[0]
|
||||
assert start_message.startswith("Processing message: ")
|
||||
start_json = start_message[len("Processing message: ") :]
|
||||
assert json.loads(start_json) == snapshot(
|
||||
{
|
||||
"event": "request_start",
|
||||
"timestamp": "2023-01-01T00:00:00+00:00",
|
||||
"source": "client",
|
||||
"type": "request",
|
||||
"method": "test_method",
|
||||
"payload": "CUSTOM_PAYLOAD",
|
||||
"payload_type": "CallToolRequest",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def logging_server():
|
||||
"""Create a FastMCP server specifically for logging middleware tests."""
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("LoggingTestServer")
|
||||
|
||||
@mcp.tool
|
||||
def simple_operation(data: str) -> str:
|
||||
"""A simple operation for testing logging."""
|
||||
return f"Processed: {data}"
|
||||
|
||||
@mcp.tool
|
||||
def complex_operation(items: list[str], mode: str = "default") -> dict:
|
||||
"""A complex operation with structured data."""
|
||||
return {"processed_items": len(items), "mode": mode, "result": "success"}
|
||||
|
||||
@mcp.tool
|
||||
def operation_with_error(should_fail: bool = False) -> str:
|
||||
"""An operation that can be made to fail."""
|
||||
if should_fail:
|
||||
raise ValueError("Operation failed intentionally")
|
||||
return "Operation completed successfully"
|
||||
|
||||
@mcp.resource("log://test")
|
||||
def test_resource() -> str:
|
||||
"""A test resource for logging."""
|
||||
return "Test resource content"
|
||||
|
||||
@mcp.prompt
|
||||
def test_prompt() -> str:
|
||||
"""A test prompt for logging."""
|
||||
return "Test prompt content"
|
||||
|
||||
return mcp
|
||||
|
||||
|
||||
class TestLoggingMiddlewareIntegration:
|
||||
"""Integration tests for logging middleware with real FastMCP server."""
|
||||
|
|
@ -296,15 +551,16 @@ class TestLoggingMiddlewareIntegration:
|
|||
|
||||
logging_server.add_middleware(logging_middleware)
|
||||
|
||||
with caplog.at_level(logging.INFO):
|
||||
async with Client(logging_server) as client:
|
||||
await client.call_tool(
|
||||
name="simple_operation", arguments={"data": "test_data"}
|
||||
)
|
||||
await client.call_tool(
|
||||
name="complex_operation",
|
||||
arguments={"items": ["a", "b", "c"], "mode": "batch"},
|
||||
)
|
||||
with caplog_for_fastmcp(caplog):
|
||||
with caplog.at_level(logging.INFO):
|
||||
async with Client(logging_server) as client:
|
||||
await client.call_tool(
|
||||
name="simple_operation", arguments={"data": "test_data"}
|
||||
)
|
||||
await client.call_tool(
|
||||
name="complex_operation",
|
||||
arguments={"items": ["a", "b", "c"], "mode": "batch"},
|
||||
)
|
||||
|
||||
# Should have processing and completion logs for both operations
|
||||
assert remove_line_numbers(caplog.text) == snapshot("""\
|
||||
|
|
@ -324,7 +580,7 @@ INFO fastmcp.requests:logging.py:LINE_NUMBER Completed message: event=reques
|
|||
"""Test that logging middleware captures failed operations."""
|
||||
logging_server.add_middleware(LoggingMiddleware(methods=["tools/call"]))
|
||||
|
||||
with caplog.at_level(logging.INFO):
|
||||
with caplog_for_fastmcp(caplog):
|
||||
async with Client(logging_server) as client:
|
||||
# This should fail and be logged
|
||||
with pytest.raises(Exception):
|
||||
|
|
@ -351,17 +607,25 @@ INFO fastmcp.requests:logging.py:LINE_NUMBER Completed message: event=reques
|
|||
)
|
||||
logging_server.add_middleware(middleware)
|
||||
|
||||
with caplog.at_level(logging.INFO):
|
||||
with caplog_for_fastmcp(caplog):
|
||||
async with Client(logging_server) as client:
|
||||
await client.call_tool("simple_operation", {"data": "payload_test"})
|
||||
|
||||
log_text = caplog.text
|
||||
|
||||
# Remove client IDs from log text for consistent snapshots
|
||||
import re
|
||||
|
||||
log_text = re.sub(r"\[Client-[^\]]+\]", "[Client-XXXX]", log_text)
|
||||
|
||||
assert remove_line_numbers(log_text) == snapshot("""\
|
||||
INFO mcp.server.lowlevel.server:server.py:LINE_NUMBER Processing request of type CallToolRequest
|
||||
DEBUG fastmcp.fastmcp.client.transports:transports.py:LINE_NUMBER Inferred transport: <FastMCPTransport(server='LoggingTestServer')>
|
||||
DEBUG fastmcp.fastmcp.client.client:client.py:LINE_NUMBER [Client-XXXX] called call_tool: simple_operation
|
||||
DEBUG fastmcp.fastmcp.server.server:server.py:LINE_NUMBER [LoggingTestServer] Handler called: list_tools
|
||||
DEBUG fastmcp.fastmcp.server.server:server.py:LINE_NUMBER [LoggingTestServer] Handler called: call_tool simple_operation with {'data': 'payload_test'}
|
||||
INFO fastmcp.requests:logging.py:LINE_NUMBER Processing message: event=request_start timestamp=2023-01-01T00:00:00+00:00 method=tools/call type=request source=client payload={"_meta":null,"name":"simple_operation","arguments":{"data":"payload_test"}} payload_type=CallToolRequestParams
|
||||
INFO fastmcp.requests:logging.py:LINE_NUMBER Completed message: event=request_success timestamp=2023-01-01T00:00:00+00:00 method=tools/call type=request source=client
|
||||
INFO mcp.server.lowlevel.server:server.py:LINE_NUMBER Processing request of type ListToolsRequest
|
||||
DEBUG fastmcp.fastmcp.server.server:server.py:LINE_NUMBER [LoggingTestServer] Handler called: list_tools
|
||||
|
||||
""")
|
||||
|
||||
|
|
@ -379,7 +643,7 @@ INFO mcp.server.lowlevel.server:server.py:LINE_NUMBER Processing request of
|
|||
|
||||
logging_server.add_middleware(logging_middleware)
|
||||
|
||||
with caplog.at_level(logging.INFO):
|
||||
with caplog_for_fastmcp(caplog):
|
||||
async with Client(logging_server) as client:
|
||||
await client.call_tool(
|
||||
name="simple_operation", arguments={"data": "json_test"}
|
||||
|
|
@ -394,11 +658,19 @@ INFO mcp.server.lowlevel.server:server.py:LINE_NUMBER Processing request of
|
|||
|
||||
assert len(log_lines) >= 2 # Should have start and success entries
|
||||
|
||||
assert remove_line_numbers(caplog.text) == snapshot("""\
|
||||
INFO mcp.server.lowlevel.server:server.py:LINE_NUMBER Processing request of type CallToolRequest
|
||||
# Remove client IDs from log text for consistent snapshots
|
||||
import re
|
||||
|
||||
log_text = re.sub(r"\[Client-[^\]]+\]", "[Client-XXXX]", caplog.text)
|
||||
|
||||
assert remove_line_numbers(log_text) == snapshot("""\
|
||||
DEBUG fastmcp.fastmcp.client.transports:transports.py:LINE_NUMBER Inferred transport: <FastMCPTransport(server='LoggingTestServer')>
|
||||
DEBUG fastmcp.fastmcp.client.client:client.py:LINE_NUMBER [Client-XXXX] called call_tool: simple_operation
|
||||
DEBUG fastmcp.fastmcp.server.server:server.py:LINE_NUMBER [LoggingTestServer] Handler called: list_tools
|
||||
DEBUG fastmcp.fastmcp.server.server:server.py:LINE_NUMBER [LoggingTestServer] Handler called: call_tool simple_operation with {'data': 'json_test'}
|
||||
INFO fastmcp.structured:logging.py:LINE_NUMBER Processing message: {"event": "request_start", "timestamp": "2023-01-01T00:00:00+00:00", "method": "tools/call", "type": "request", "source": "client", "payload": "{\\"_meta\\":null,\\"name\\":\\"simple_operation\\",\\"arguments\\":{\\"data\\":\\"json_test\\"}}", "payload_type": "CallToolRequestParams"}
|
||||
INFO fastmcp.structured:logging.py:LINE_NUMBER Completed message: {"event": "request_success", "timestamp": "2023-01-01T00:00:00+00:00", "method": "tools/call", "type": "request", "source": "client"}
|
||||
INFO mcp.server.lowlevel.server:server.py:LINE_NUMBER Processing request of type ListToolsRequest
|
||||
DEBUG fastmcp.fastmcp.server.server:server.py:LINE_NUMBER [LoggingTestServer] Handler called: list_tools
|
||||
|
||||
""")
|
||||
|
||||
|
|
@ -414,19 +686,26 @@ INFO mcp.server.lowlevel.server:server.py:LINE_NUMBER Processing request of
|
|||
|
||||
logging_server.add_middleware(logging_middleware)
|
||||
|
||||
with caplog.at_level(logging.INFO):
|
||||
async with Client(logging_server) as client:
|
||||
with pytest.raises(Exception):
|
||||
await client.call_tool(
|
||||
"operation_with_error", {"should_fail": True}
|
||||
)
|
||||
with caplog_for_fastmcp(caplog):
|
||||
with caplog.at_level(logging.INFO):
|
||||
async with Client(logging_server) as client:
|
||||
with pytest.raises(Exception):
|
||||
await client.call_tool(
|
||||
"operation_with_error", {"should_fail": True}
|
||||
)
|
||||
|
||||
assert remove_line_numbers(caplog.text) == snapshot("""\
|
||||
INFO mcp.server.lowlevel.server:server.py:LINE_NUMBER Processing request of type CallToolRequest
|
||||
INFO fastmcp.structured:logging.py:LINE_NUMBER Processing message: {"event": "request_start", "timestamp": "2023-01-01T00:00:00+00:00", "method": "tools/call", "type": "request", "source": "client"}
|
||||
ERROR fastmcp.structured:logging.py:LINE_NUMBER Failed message: tools/call - Error calling tool 'operation_with_error': Operation failed intentionally
|
||||
# Verify that the structured logging middleware properly logs errors
|
||||
logs = caplog.text
|
||||
|
||||
""")
|
||||
# The key assertion: structured logging middleware logged the error in JSON format
|
||||
assert re.search(
|
||||
r"fastmcp\.structured.*Failed message: tools/call.*Operation failed intentionally",
|
||||
logs,
|
||||
)
|
||||
|
||||
# Verify the error contains expected error type and message
|
||||
assert "ValueError" in logs
|
||||
assert "Operation failed intentionally" in logs
|
||||
|
||||
async def test_logging_middleware_with_different_operations(
|
||||
self, logging_server: FastMCP, caplog: pytest.LogCaptureFixture
|
||||
|
|
@ -444,7 +723,7 @@ ERROR fastmcp.structured:logging.py:LINE_NUMBER Failed message: tools/call -
|
|||
)
|
||||
)
|
||||
|
||||
with caplog.at_level(logging.INFO):
|
||||
with caplog_for_fastmcp(caplog):
|
||||
async with Client(logging_server) as client:
|
||||
# Test different operation types
|
||||
await client.call_tool("simple_operation", {"data": "test"})
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ from fastmcp import FastMCP
|
|||
from fastmcp.client import Client
|
||||
from fastmcp.server.middleware.middleware import MiddlewareContext
|
||||
from fastmcp.server.middleware.timing import DetailedTimingMiddleware, TimingMiddleware
|
||||
from fastmcp.utilities.tests import caplog_for_fastmcp
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
@ -47,7 +48,7 @@ class TestTimingMiddleware:
|
|||
"""Test timing successful requests."""
|
||||
middleware = TimingMiddleware()
|
||||
|
||||
with caplog.at_level(logging.INFO):
|
||||
with caplog_for_fastmcp(caplog):
|
||||
result = await middleware.on_request(mock_context, mock_call_next)
|
||||
|
||||
assert result == "test_result"
|
||||
|
|
@ -60,7 +61,7 @@ class TestTimingMiddleware:
|
|||
middleware = TimingMiddleware()
|
||||
mock_call_next = AsyncMock(side_effect=ValueError("test error"))
|
||||
|
||||
with caplog.at_level(logging.INFO):
|
||||
with caplog_for_fastmcp(caplog):
|
||||
with pytest.raises(ValueError):
|
||||
await middleware.on_request(mock_context, mock_call_next)
|
||||
|
||||
|
|
@ -84,7 +85,7 @@ class TestDetailedTimingMiddleware:
|
|||
context.message.name = "test_tool"
|
||||
mock_call_next = AsyncMock(return_value="tool_result")
|
||||
|
||||
with caplog.at_level(logging.INFO):
|
||||
with caplog_for_fastmcp(caplog):
|
||||
result = await middleware.on_call_tool(context, mock_call_next)
|
||||
|
||||
assert result == "tool_result"
|
||||
|
|
@ -97,7 +98,7 @@ class TestDetailedTimingMiddleware:
|
|||
context.message.uri = "test://resource"
|
||||
mock_call_next = AsyncMock(return_value="resource_result")
|
||||
|
||||
with caplog.at_level(logging.INFO):
|
||||
with caplog_for_fastmcp(caplog):
|
||||
result = await middleware.on_read_resource(context, mock_call_next)
|
||||
|
||||
assert result == "resource_result"
|
||||
|
|
@ -110,7 +111,7 @@ class TestDetailedTimingMiddleware:
|
|||
context.message.name = "test_prompt"
|
||||
mock_call_next = AsyncMock(return_value="prompt_result")
|
||||
|
||||
with caplog.at_level(logging.INFO):
|
||||
with caplog_for_fastmcp(caplog):
|
||||
result = await middleware.on_get_prompt(context, mock_call_next)
|
||||
|
||||
assert result == "prompt_result"
|
||||
|
|
@ -122,7 +123,7 @@ class TestDetailedTimingMiddleware:
|
|||
context = MagicMock()
|
||||
mock_call_next = AsyncMock(return_value="tools_result")
|
||||
|
||||
with caplog.at_level(logging.INFO):
|
||||
with caplog_for_fastmcp(caplog):
|
||||
result = await middleware.on_list_tools(context, mock_call_next)
|
||||
|
||||
assert result == "tools_result"
|
||||
|
|
@ -135,7 +136,7 @@ class TestDetailedTimingMiddleware:
|
|||
context.message.name = "failing_tool"
|
||||
mock_call_next = AsyncMock(side_effect=RuntimeError("operation failed"))
|
||||
|
||||
with caplog.at_level(logging.INFO):
|
||||
with caplog_for_fastmcp(caplog):
|
||||
with pytest.raises(RuntimeError):
|
||||
await middleware.on_call_tool(context, mock_call_next)
|
||||
|
||||
|
|
@ -194,7 +195,7 @@ class TestTimingMiddlewareIntegration:
|
|||
"""Test that timing middleware accurately measures tool execution times."""
|
||||
timing_server.add_middleware(TimingMiddleware())
|
||||
|
||||
with caplog.at_level(logging.INFO):
|
||||
with caplog_for_fastmcp(caplog):
|
||||
async with Client(timing_server) as client:
|
||||
# Test instant task
|
||||
await client.call_tool("instant_task")
|
||||
|
|
@ -225,7 +226,7 @@ class TestTimingMiddlewareIntegration:
|
|||
"""Test that timing middleware measures time even for failed operations."""
|
||||
timing_server.add_middleware(TimingMiddleware())
|
||||
|
||||
with caplog.at_level(logging.INFO):
|
||||
with caplog_for_fastmcp(caplog):
|
||||
async with Client(timing_server) as client:
|
||||
# This should fail but still be timed
|
||||
with pytest.raises(Exception):
|
||||
|
|
@ -241,7 +242,7 @@ class TestTimingMiddlewareIntegration:
|
|||
"""Test that detailed timing middleware provides operation-specific timing."""
|
||||
timing_server.add_middleware(DetailedTimingMiddleware())
|
||||
|
||||
with caplog.at_level(logging.INFO):
|
||||
with caplog_for_fastmcp(caplog):
|
||||
async with Client(timing_server) as client:
|
||||
# Test tool call
|
||||
await client.call_tool("short_task")
|
||||
|
|
@ -271,7 +272,7 @@ class TestTimingMiddlewareIntegration:
|
|||
"""Test timing middleware with concurrent operations."""
|
||||
timing_server.add_middleware(TimingMiddleware())
|
||||
|
||||
with caplog.at_level(logging.INFO):
|
||||
with caplog_for_fastmcp(caplog):
|
||||
async with Client(timing_server) as client:
|
||||
# Run multiple operations concurrently
|
||||
tasks = [
|
||||
|
|
@ -290,7 +291,7 @@ class TestTimingMiddlewareIntegration:
|
|||
len(timing_logs) >= 3
|
||||
) # At least 3 tool calls, may have additional list_tools calls
|
||||
|
||||
async def test_timing_middleware_custom_logger(self, timing_server):
|
||||
async def test_timing_middleware_custom_logger(self, timing_server, caplog):
|
||||
"""Test timing middleware with custom logger configuration."""
|
||||
import io
|
||||
import logging
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ from fastmcp.utilities.logging import get_logger
|
|||
|
||||
def test_logging_doesnt_affect_other_loggers(caplog):
|
||||
# set FastMCP loggers to CRITICAL and ensure other loggers still emit messages
|
||||
original_level = logging.getLogger("FastMCP").getEffectiveLevel()
|
||||
original_level = logging.getLogger("fastmcp").getEffectiveLevel()
|
||||
|
||||
try:
|
||||
logging.getLogger("FastMCP").setLevel(logging.CRITICAL)
|
||||
logging.getLogger("fastmcp").setLevel(logging.CRITICAL)
|
||||
|
||||
root_logger = logging.getLogger()
|
||||
app_logger = logging.getLogger("app")
|
||||
fastmcp_logger = logging.getLogger("FastMCP")
|
||||
fastmcp_logger = logging.getLogger("fastmcp")
|
||||
fastmcp_server_logger = get_logger("server")
|
||||
|
||||
with caplog.at_level(logging.INFO):
|
||||
|
|
@ -27,4 +27,4 @@ def test_logging_doesnt_affect_other_loggers(caplog):
|
|||
assert "--FASTMCP SERVER--" not in caplog.text
|
||||
|
||||
finally:
|
||||
logging.getLogger("FastMCP").setLevel(original_level)
|
||||
logging.getLogger("fastmcp").setLevel(original_level)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue