Merge branch 'main' into responsecachingmiddleware
172
.github/workflows/martian-issue-triage.yml
vendored
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
name: Martian Issue Triage
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, labeled]
|
||||
|
||||
concurrency:
|
||||
group: triage-martian-${{ github.event.issue.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
martian-issue-triage:
|
||||
if: |
|
||||
(github.event.action == 'opened' && github.actor == 'strawgate') ||
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'triage-martian')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: read
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout base repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
repository: ${{ github.repository }}
|
||||
ref: ${{ github.event.repository.default_branch }}
|
||||
|
||||
# Install UV package manager
|
||||
- name: Install UV
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
enable-cache: true
|
||||
cache-dependency-glob: "uv.lock"
|
||||
|
||||
- name: Generate Marvin App token
|
||||
id: marvin-token
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ secrets.MARVIN_APP_ID }}
|
||||
private-key: ${{ secrets.MARVIN_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Set triage prompt
|
||||
id: triage-prompt
|
||||
run: |
|
||||
cat >> $GITHUB_OUTPUT << 'EOF'
|
||||
PROMPT<<PROMPT_END
|
||||
You're an issue triage assistant for FastMCP, a Python framework for building Model Context Protocol servers and clients.
|
||||
|
||||
# IMPORTANT RULES
|
||||
1. You will not make branches or pull requests. Your ONLY action will be investigating the issue, locating related issues,
|
||||
pull requests, and files in the repository and reporting your findings.
|
||||
2. You will identify the issue type (bug/feature/question) up front and tailor the Recommendation (e.g., for questions: answer directly + links; for bugs: point to failing tests/lines).
|
||||
3. You will avoid speculation and only assert facts that are deeply rooted (traceable) to the codebase, language/framework conventions, related issues, related pull requests, etc.
|
||||
4. The main branch of the repository has been cloned locally, but changes will not be accepted and you are not allowed to make pull requests or other changes. You can search the local repository for relevant code. You will use the available MCP Server tools identify related issues and pull requests (search_issues and search_pull_requests) and you can use search_code to look at the code in relevant dependent packages. For example, you can use search_code to look at the underlying SDK `https://github.com/modelcontextprotocol/python-sdk` to see how it implements a certain class or function relevant to the issue at hand.
|
||||
|
||||
# Getting Started
|
||||
1. Call the generate_agents_md tool to get a high-level summary of the project you're working in
|
||||
2. Get the issue ${{ github.event.issue.number }} in the GitHub repository: ${{ github.repository }}.
|
||||
3. Use the search_issues and search_pull_requests tools to scour the repository for actually related issues and pull requests
|
||||
4. Call the search_code, get_files, etc. tools to search the repository to identify the related classes, methods, docs, tests, etc that are relevant to the issue.
|
||||
|
||||
# Providing a Great Response
|
||||
Your number one priority is to provide a great response to the issue. A great response is a response that is clear, concise, accurate, and actionable. You will avoid long paragraphs, flowery language, and overly verbose responses. Your readers have limited time and attention, so you will be concise and to the point.
|
||||
|
||||
In priority order your goal is to:
|
||||
1. Provide context about the request or issue (related issues, pull requests, files, etc.)
|
||||
2. Layout a single high-quality and actionable recommendation for how to address the issue based on your knowledge of the project, codebase, and issue
|
||||
3. Provide an high quality and detailed plan that a junior developer could follow to implement the recommendation
|
||||
|
||||
Populate the following sections in your response:
|
||||
Recommendation (or “No recommendation” with reason)
|
||||
Findings
|
||||
Detailed Action Plan
|
||||
Related Items
|
||||
Related Files
|
||||
Related Webpages
|
||||
|
||||
You may not be able to do all of these things, sometimes you may find that all you can do is provide in-depth context of the issue and related items. That's perfectly acceptable and expected. Your performance is judged by how accurate your findings are, do the investigation required to have high confidence in your findings and recommendations. "I don't know" or "I'm unable to recommend a course of action" is better than a bad or wrong answer.
|
||||
|
||||
When formulating your response, you will never "bury the lede", you will always provide a clear and concise tl;dr as the first thing in your response. As your response grows in length you can organize the more detailed parts of your response collapsible sections using <details> and <summary> tags. You shouldn't put everything in collapsible sections, especially if the response is short. Use your discretion to determine when to use collapsible sections to avoid overwhelming the reader with too much detail -- think of them like an appendix that can be expanded if the reader is interested.
|
||||
|
||||
# Example output for "Recommendation" part of the response
|
||||
PR #654 already implements the requested feature but is incomplete. The Pull Request is not in a mergeable state yet, the remaining work should be completed: 1) update the Calculator.divide method to utilize the new DivisionByZeroError or the safe_divide function, and 2) update the tests to ensure that the Calculator.divide method raises the new DivisionByZeroError when the divisor is 0.
|
||||
|
||||
<details>
|
||||
<summary>Findings</summary>
|
||||
...details from the code analysis that are relevant to the issue and the recommendation...
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Detailed Action Plan</summary>
|
||||
...a detailed plan that a junior developer could follow to implement the recommendation...
|
||||
</details>
|
||||
|
||||
# Example Output for "Related Items" part of the response
|
||||
|
||||
<details>
|
||||
<summary>Related Issues and Pull Requests</summary>
|
||||
|
||||
| Repository | Issue or PR | Relevance |
|
||||
| --- | --- | --- |
|
||||
| jlowin/fastmcp | [Add matrix operations support](https://github.com/jlowin/fastmcp/pull/680) | This pull request directly addresses the feature request for adding matrix operations to the calculator. |
|
||||
| jlowin/fastmcp | [Add matrix operations support](https://github.com/jlowin/fastmcp/issues/681) | This issue directly addresses the feature request for adding matrix operations to the calculator. |
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Related Files</summary>
|
||||
|
||||
| Repository | File | Relevance | Sections |
|
||||
| --- | --- | --- | --- |
|
||||
| modelcontextprotocol/python-sdk | [test_calculator.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/test_calculator.py) | This file contains the test cases for the Calculator class, including a test that specifically asserts a ValueError is raised for division by zero, confirming the current intended behavior. | [25-27](https://github.com/modelcontextprotocol/python-sdk/blob/main/test_calculator.py#L25-L27) |
|
||||
| modelcontextprotocol/python-sdk | [calculator.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/calculator.py) | This file contains the implementation of the Calculator class, specifically the `divide` method which raises the ValueError when dividing by zero, matching the bug report. | [29-32](https://github.com/modelcontextprotocol/python-sdk/blob/main/calculator.py#L29-L32) |
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Related Webpages</summary>
|
||||
| Name | URL | Relevance |
|
||||
| --- | --- | --- |
|
||||
| Handling Division by Zero Best Practices | https://my-blog-about-division-by-zero.com/handling+division+by+zero+in+calculator | This webpage provides general best practices for handling division by zero in calculator applications and in Python, which is directly relevant to the issue and potential solutions. |
|
||||
</details>
|
||||
|
||||
PROMPT_END
|
||||
EOF
|
||||
|
||||
- name: Setup GitHub MCP Server
|
||||
run: |
|
||||
mkdir -p /tmp/mcp-config
|
||||
cat > /tmp/mcp-config/mcp-servers.json << 'EOF'
|
||||
{
|
||||
"mcpServers": {
|
||||
"repository-summary": {
|
||||
"type": "http",
|
||||
"url": "https://agents-md-generator.fastmcp.app/mcp"
|
||||
},
|
||||
"code-search": {
|
||||
"type": "http",
|
||||
"url": "https://public-code-search.fastmcp.app/mcp"
|
||||
},
|
||||
"github-research": {
|
||||
"type": "stdio",
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"github-research-mcp"
|
||||
],
|
||||
"env": {
|
||||
"DISABLE_SUMMARIES": "true",
|
||||
"GITHUB_PERSONAL_ACCESS_TOKEN": "${{ steps.marvin-token.outputs.token }}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
- name: Run Martian for Issue Triage
|
||||
uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
github_token: ${{ steps.marvin-token.outputs.token }}
|
||||
bot_name: "Marvin Context Protocol"
|
||||
prompt: ${{ steps.triage-prompt.outputs.PROMPT }}
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY_FOR_CI }}
|
||||
track_progress: true
|
||||
claude_args: |
|
||||
--model claude-sonnet-4-5-20250929
|
||||
--allowedTools mcp__repository-summary,mcp__code-search__search_code,mcp__github-research__get_repository,mcp__github-research__get_issue,mcp__github-research__get_pull_request,mcp__github-research__search_issues,mcp__github-research__search_pull_requests,mcp__github-research__get_files
|
||||
--mcp-config /tmp/mcp-config/mcp-servers.json
|
||||
settings: |
|
||||
{
|
||||
"GH_TOKEN": "${{ steps.marvin-token.outputs.token }}"
|
||||
}
|
||||
38
.github/workflows/marvin-dedupe-issues.yml
vendored
|
|
@ -1,5 +1,5 @@
|
|||
name: Marvin Issue Dedupe
|
||||
description: Automatically dedupe GitHub issues using Marvin
|
||||
# description: Automatically dedupe GitHub issues using Marvin
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
|
@ -17,6 +17,7 @@ jobs:
|
|||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
|
|
@ -29,21 +30,22 @@ jobs:
|
|||
app-id: ${{ secrets.MARVIN_APP_ID }}
|
||||
private-key: ${{ secrets.MARVIN_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Create dedupe prompt
|
||||
- name: Set dedupe prompt
|
||||
id: dedupe-prompt
|
||||
run: |
|
||||
mkdir -p /tmp/claude-prompts
|
||||
cat > /tmp/claude-prompts/dedupe-prompt.txt << 'EOF'
|
||||
cat >> $GITHUB_OUTPUT << 'EOF'
|
||||
PROMPT<<PROMPT_END
|
||||
Find up to 3 likely duplicate issues for GitHub issue ${{ github.repository }}/issues/${{ github.event.issue.number || inputs.issue_number }}.
|
||||
|
||||
Follow these steps precisely:
|
||||
|
||||
1. Use the Task tool to check if the GitHub issue (a) is closed, (b) does not need to be deduped (eg. because it is broad product feedback without a specific solution, or positive feedback), or (c) already has a duplicates comment that you made earlier. If so, do not proceed.
|
||||
1. Check if the GitHub issue (a) is closed, (b) does not need to be deduped (eg. because it is broad product feedback without a specific solution, or positive feedback), or (c) already has a duplicates comment that you made earlier. If so, do not proceed.
|
||||
|
||||
2. Use the Task tool to view the GitHub issue, and ask the agent to return a summary of the issue
|
||||
2. View the GitHub issue and produce a summary of the issue
|
||||
|
||||
3. Then, launch 3 parallel agents using the Task tool to search GitHub for duplicates of this issue, using diverse keywords and search approaches, using the summary from step 2
|
||||
|
||||
4. Next, feed the results from steps 2 and 3 into another agent using the Task tool, so that it can filter out false positives that are likely not actually duplicates of the original issue. If there are no duplicates remaining, do not proceed.
|
||||
4. Next, consider the results from steps 2 and 3 and filter out false positives that are likely not actually duplicates of the original issue. If there are no duplicates remaining, do not proceed.
|
||||
|
||||
5. Finally, comment back on the issue with a list of up to three duplicate issues (or zero, if there are no likely duplicates). If there are no duplicates, DO NOT COMMENT. Just exit.
|
||||
|
||||
|
|
@ -51,6 +53,7 @@ jobs:
|
|||
- Use `gh` to interact with GitHub, rather than web fetch
|
||||
- Do not use other tools, beyond `gh` and Task (eg. don't use other MCP servers, file edit, etc.)
|
||||
- Make a todo list first
|
||||
- Never include this issue as a duplicate of itself
|
||||
|
||||
For your comment, follow this format precisely (example with 3 suspected duplicates):
|
||||
|
||||
|
|
@ -67,14 +70,23 @@ jobs:
|
|||
- To prevent auto-closure, add a comment or 👎 this comment
|
||||
|
||||
---
|
||||
PROMPT_END
|
||||
EOF
|
||||
|
||||
- name: Run Marvin dedupe command
|
||||
uses: anthropics/claude-code-base-action@beta
|
||||
uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
model: claude-3-5-haiku-latest
|
||||
prompt_file: /tmp/claude-prompts/dedupe-prompt.txt
|
||||
allowed_tools: "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh api:*),Bash(gh issue comment:*),Task"
|
||||
github_token: ${{ steps.marvin-token.outputs.token }}
|
||||
bot_name: "Marvin Context Protocol"
|
||||
prompt: ${{ steps.dedupe-prompt.outputs.PROMPT }}
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY_FOR_CI }}
|
||||
claude_env: |
|
||||
GH_TOKEN: ${{ steps.marvin-token.outputs.token }}
|
||||
claude_args: |
|
||||
--allowedTools Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh api:*),Bash(gh issue comment:*),Task
|
||||
--mcp-config /tmp/mcp-config/mcp-servers.json
|
||||
settings: |
|
||||
{
|
||||
"model": "claude-sonnet-4-5-20250929",
|
||||
"env": {
|
||||
"GH_TOKEN": "${{ steps.marvin-token.outputs.token }}"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
60
.github/workflows/marvin-label-triage.yml
vendored
|
|
@ -1,5 +1,6 @@
|
|||
name: Marvin Label Triage
|
||||
description: Automatically triage GitHub issues and PRs using Marvin
|
||||
# Automatically triage GitHub issues and PRs using Marvin
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
|
@ -17,7 +18,7 @@ concurrency:
|
|||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
triage-issue:
|
||||
label-issue-or-pr:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
|
|
@ -39,10 +40,11 @@ jobs:
|
|||
app-id: ${{ secrets.MARVIN_APP_ID }}
|
||||
private-key: ${{ secrets.MARVIN_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Create triage prompt
|
||||
- name: Set triage prompt
|
||||
id: triage-prompt
|
||||
run: |
|
||||
mkdir -p /tmp/claude-prompts
|
||||
cat > /tmp/claude-prompts/triage-prompt.txt << 'EOF'
|
||||
cat >> $GITHUB_OUTPUT << 'EOF'
|
||||
PROMPT<<PROMPT_END
|
||||
You're an issue triage assistant for FastMCP, a Python framework for building Model Context Protocol servers and clients. Your task is to analyze issues/PRs and apply appropriate labels.
|
||||
|
||||
IMPORTANT: Your ONLY action should be to apply labels using mcp__github__update_issue. DO NOT post any comments.
|
||||
|
|
@ -119,39 +121,23 @@ jobs:
|
|||
4. Apply selected labels:
|
||||
Use mcp__github__update_issue to apply your selected labels
|
||||
DO NOT post any comments
|
||||
PROMPT_END
|
||||
EOF
|
||||
|
||||
- name: Setup GitHub MCP Server
|
||||
run: |
|
||||
mkdir -p /tmp/mcp-config
|
||||
cat > /tmp/mcp-config/mcp-servers.json << 'EOF'
|
||||
{
|
||||
"mcpServers": {
|
||||
"github": {
|
||||
"command": "docker",
|
||||
"args": [
|
||||
"run",
|
||||
"-i",
|
||||
"--rm",
|
||||
"-e",
|
||||
"GITHUB_PERSONAL_ACCESS_TOKEN",
|
||||
"ghcr.io/github/github-mcp-server:sha-7aced2b"
|
||||
],
|
||||
"env": {
|
||||
"GITHUB_PERSONAL_ACCESS_TOKEN": "${{ steps.marvin-token.outputs.token }}"
|
||||
}
|
||||
- name: Run Marvin for Issue Triage
|
||||
uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
github_token: ${{ steps.marvin-token.outputs.token }}
|
||||
bot_name: "Marvin Context Protocol"
|
||||
prompt: ${{ steps.triage-prompt.outputs.PROMPT }}
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY_FOR_CI }}
|
||||
allowed_non_write_users: "*" # Required for issue triage workflow, if users without repo write access create issues
|
||||
claude_args: |
|
||||
--allowedTools Bash(gh label list),mcp__github__get_issue,mcp__github__get_issue_comments,mcp__github__update_issue,mcp__github__get_pull_request_files
|
||||
settings: |
|
||||
{
|
||||
"model": "claude-sonnet-4-5-20250929",
|
||||
"env": {
|
||||
"GH_TOKEN": "${{ steps.marvin-token.outputs.token }}"
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
- name: Run Marvin for Issue Triage
|
||||
uses: anthropics/claude-code-base-action@beta
|
||||
with:
|
||||
prompt_file: /tmp/claude-prompts/triage-prompt.txt
|
||||
allowed_tools: "Bash(gh label list),mcp__github__get_issue,mcp__github__get_issue_comments,mcp__github__update_issue,mcp__github__get_pull_request_files"
|
||||
timeout_minutes: "5"
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY_FOR_CI }}
|
||||
mcp_config: /tmp/mcp-config/mcp-servers.json
|
||||
claude_env: |
|
||||
GH_TOKEN: ${{ steps.marvin-token.outputs.token }}
|
||||
|
|
|
|||
13
.github/workflows/marvin.yml
vendored
|
|
@ -59,13 +59,20 @@ jobs:
|
|||
|
||||
# Marvin Assistant
|
||||
- name: Run Marvin
|
||||
uses: anthropics/claude-code-action@beta
|
||||
uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
github_token: ${{ steps.marvin-token.outputs.token }}
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
mode: tag
|
||||
trigger_phrase: "/marvin"
|
||||
allowed_bots: "*"
|
||||
allowed_tools: "WebSearch,WebFetch,Bash(uv:*),Bash(pre-commit:*),Bash(pytest:*),Bash(ruff:*),Bash(ty:*),Bash(git:*),Bash(gh:*),mcp__github__add_issue_comment,mcp__github__create_issue,mcp__github__get_issue,mcp__github__list_issues,mcp__github__search_issues,mcp__github__update_issue,mcp__github__update_issue_comment,mcp__github__create_pull_request,mcp__github__get_pull_request,mcp__github__get_pull_request_comments,mcp__github__get_pull_request_files,mcp__github__get_pull_request_reviews,mcp__github__get_pull_request_status,mcp__github__list_pull_requests,mcp__github__update_pull_request,mcp__github__update_pull_request_branch,mcp__github__update_pull_request_comment,mcp__github__merge_pull_request"
|
||||
claude_args: |
|
||||
--allowedTools WebSearch,WebFetch,Bash(uv:*),Bash(pre-commit:*),Bash(pytest:*),Bash(ruff:*),Bash(ty:*),Bash(git:*),Bash(gh:*),mcp__github__add_issue_comment,mcp__github__create_issue,mcp__github__get_issue,mcp__github__list_issues,mcp__github__search_issues,mcp__github__update_issue,mcp__github__update_issue_comment,mcp__github__create_pull_request,mcp__github__get_pull_request,mcp__github__get_pull_request_comments,mcp__github__get_pull_request_files,mcp__github__get_pull_request_reviews,mcp__github__get_pull_request_status,mcp__github__list_pull_requests,mcp__github__update_pull_request,mcp__github__update_pull_request_branch,mcp__github__update_pull_request_comment,mcp__github__merge_pull_request
|
||||
additional_permissions: |
|
||||
actions: read
|
||||
settings: |
|
||||
{
|
||||
"model": "claude-sonnet-4-5-20250929",
|
||||
"env": {
|
||||
"GH_TOKEN": "${{ steps.marvin-token.outputs.token }}"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
6
.github/workflows/run-tests.yml
vendored
|
|
@ -48,10 +48,10 @@ jobs:
|
|||
run: uv sync --frozen
|
||||
|
||||
- name: Run tests (excluding integration and client_process)
|
||||
run: uv run pytest -v tests -m "not integration and not client_process"
|
||||
run: uv run pytest --inline-snapshot=disable tests -m "not integration and not client_process" --numprocesses auto --maxprocesses 4 --dist worksteal
|
||||
|
||||
- name: Run client process tests separately
|
||||
run: uv run pytest -v tests -m "client_process" -x
|
||||
run: uv run pytest --inline-snapshot=disable tests -m "client_process" -x
|
||||
|
||||
run_integration_tests:
|
||||
name: "Run integration tests"
|
||||
|
|
@ -74,7 +74,7 @@ jobs:
|
|||
|
||||
- name: Run integration tests
|
||||
# use longer per-test timeout than the default 3s
|
||||
run: uv run pytest -v tests -m "integration" --timeout=15
|
||||
run: uv run pytest tests -m "integration" --timeout=15 --numprocesses auto --maxprocesses 2 --dist worksteal
|
||||
env:
|
||||
FASTMCP_GITHUB_TOKEN: ${{ secrets.FASTMCP_GITHUB_TOKEN }}
|
||||
FASTMCP_TEST_AUTH_GITHUB_CLIENT_ID: ${{ secrets.FASTMCP_TEST_AUTH_GITHUB_CLIENT_ID }}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
40
AGENTS.md
|
|
@ -20,24 +20,24 @@ uv run pytest # Run full test suite
|
|||
|
||||
## Repository Structure
|
||||
|
||||
| Path | Purpose |
|
||||
| ---------------- | ------------------------------------------------------ |
|
||||
| `src/fastmcp/` | Library source code (Python ≥ 3.10) |
|
||||
| `├─server/` | Server implementation, `FastMCP`, auth, networking |
|
||||
| `│ ├─auth/` | Authentication providers (Bearer, JWT, WorkOS) |
|
||||
| `│ └─middleware/` | Error handling, logging, rate limiting |
|
||||
| `├─client/` | High-level client SDK + transports |
|
||||
| `│ └─auth/` | Client authentication (Bearer, OAuth) |
|
||||
| `├─tools/` | Tool implementations + `ToolManager` |
|
||||
| `├─resources/` | Resources, templates + `ResourceManager` |
|
||||
| `├─prompts/` | Prompt templates + `PromptManager` |
|
||||
| `├─cli/` | FastMCP CLI commands (`run`, `dev`, `install`) |
|
||||
| `├─contrib/` | Community contributions (bulk caller, mixins) |
|
||||
| `├─experimental/` | Experimental features (new OpenAPI parser) |
|
||||
| `└─utilities/` | Shared utilities (logging, JSON schema, HTTP) |
|
||||
| `tests/` | Comprehensive pytest suite with markers |
|
||||
| `docs/` | Mintlify documentation (published to gofastmcp.com) |
|
||||
| `examples/` | Runnable demo servers (echo, smart_home, atproto) |
|
||||
| Path | Purpose |
|
||||
| ------------------ | --------------------------------------------------- |
|
||||
| `src/fastmcp/` | Library source code (Python ≥ 3.10) |
|
||||
| `├─server/` | Server implementation, `FastMCP`, auth, networking |
|
||||
| `│ ├─auth/` | Authentication providers (Bearer, JWT, WorkOS) |
|
||||
| `│ └─middleware/` | Error handling, logging, rate limiting |
|
||||
| `├─client/` | High-level client SDK + transports |
|
||||
| `│ └─auth/` | Client authentication (Bearer, OAuth) |
|
||||
| `├─tools/` | Tool implementations + `ToolManager` |
|
||||
| `├─resources/` | Resources, templates + `ResourceManager` |
|
||||
| `├─prompts/` | Prompt templates + `PromptManager` |
|
||||
| `├─cli/` | FastMCP CLI commands (`run`, `dev`, `install`) |
|
||||
| `├─contrib/` | Community contributions (bulk caller, mixins) |
|
||||
| `├─experimental/` | Experimental features (new OpenAPI parser) |
|
||||
| `└─utilities/` | Shared utilities (logging, JSON schema, HTTP) |
|
||||
| `tests/` | Comprehensive pytest suite with markers |
|
||||
| `docs/` | Mintlify documentation (published to gofastmcp.com) |
|
||||
| `examples/` | Runnable demo servers (echo, smart_home, atproto) |
|
||||
|
||||
## Core MCP Objects
|
||||
|
||||
|
|
@ -64,13 +64,13 @@ When modifying MCP functionality, changes typically need to be applied across al
|
|||
- Every test: atomic, self-contained, single functionality
|
||||
- Use parameterization for multiple examples of same functionality
|
||||
- Use separate tests for different functionality pieces
|
||||
- Put imports at the top of the file, not in the test body
|
||||
- **ALWAYS** Put imports at the top of the file, not in the test body
|
||||
- **NEVER** add `@pytest.mark.asyncio` to tests - `asyncio_mode = "auto"` is set globally
|
||||
- **ALWAYS** run pytest after significant changes
|
||||
|
||||
### Inline Snapshots
|
||||
|
||||
FastMCP uses `inline-snapshot` for testing complex data structures. On first run with empty `snapshot()`, pytest will auto-populate the expected value. To update snapshots after intentional changes, run `pytest --inline-snapshot=fix`. This is particularly useful for testing JSON schemas and API responses.
|
||||
FastMCP uses `inline-snapshot` for testing complex data structures. On first run with empty `snapshot()`, pytest will auto-populate the expected value when running `pytest --inline-snapshot=create`. To update snapshots after intentional changes, run `pytest --inline-snapshot=fix`. This is particularly useful for testing JSON schemas and API responses.
|
||||
|
||||
### Always Use In-Memory Transport
|
||||
|
||||
|
|
|
|||
165
README.md
|
|
@ -1,6 +1,13 @@
|
|||
<div align="center">
|
||||
|
||||
<!-- omit in toc -->
|
||||
|
||||
<picture>
|
||||
<source width="550" media="(prefers-color-scheme: dark)" srcset="docs/assets/brand/wordmark-watercolor-waves-dark.png">
|
||||
<source width="550" media="(prefers-color-scheme: light)" srcset="docs/assets/brand/wordmark-watercolor-waves.png">
|
||||
<img width="550" alt="FastMCP Logo" src="docs/assets/brand/wordmark-watercolor-waves.png">
|
||||
</picture>
|
||||
|
||||
# FastMCP v2 🚀
|
||||
|
||||
<strong>The fast, Pythonic way to build MCP servers and clients.</strong>
|
||||
|
|
@ -17,19 +24,19 @@
|
|||
|
||||
> [!Note]
|
||||
>
|
||||
> #### Beyond the Protocol
|
||||
> #### FastMCP 2.0: The Standard Framework
|
||||
>
|
||||
> FastMCP is the standard framework for working with the Model Context Protocol. FastMCP 1.0 was incorporated into the [official MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk) in 2024.
|
||||
> FastMCP pioneered Python MCP development, and FastMCP 1.0 was incorporated into the [official MCP SDK](https://github.com/modelcontextprotocol/python-sdk) in 2024.
|
||||
>
|
||||
> This is FastMCP 2.0, the **actively maintained version** that provides a complete toolkit for working with the MCP ecosystem.
|
||||
> **This is FastMCP 2.0** — the actively maintained, production-ready framework that extends far beyond basic protocol implementation. While the SDK provides core functionality, FastMCP 2.0 delivers everything needed for production: advanced MCP patterns (server composition, proxying, OpenAPI/FastAPI generation, tool transformation), enterprise auth (Google, GitHub, WorkOS, Azure, Auth0, and more), deployment tools, testing utilities, and comprehensive client libraries.
|
||||
>
|
||||
> FastMCP 2.0 has a comprehensive set of features that go far beyond the core MCP specification, all in service of providing **the simplest path to production**. These include deployment, auth, clients, server proxying and composition, generating servers from REST APIs, dynamic tool rewriting, built-in testing tools, integrations, and more.
|
||||
>
|
||||
> Ready to upgrade or get started? Follow the [installation instructions](https://gofastmcp.com/getting-started/installation), which include steps for upgrading from the official MCP SDK.
|
||||
> **For production MCP applications, install FastMCP:** `pip install fastmcp`
|
||||
|
||||
---
|
||||
|
||||
The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) is a new, standardized way to provide context and tools to your LLMs, and FastMCP makes building MCP servers and clients simple and intuitive. Create tools, expose resources, define prompts, and connect components with clean, Pythonic code.
|
||||
**FastMCP is the standard framework for building MCP applications**, providing the fastest path from idea to production.
|
||||
|
||||
The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) is a standardized way to provide context and tools to LLMs. FastMCP makes building production-ready MCP servers simple, with enterprise auth, deployment tools, and a complete ecosystem built in.
|
||||
|
||||
```python
|
||||
# server.py
|
||||
|
|
@ -68,28 +75,33 @@ There are two ways to access the LLM-friendly documentation:
|
|||
<!-- omit in toc -->
|
||||
## Table of Contents
|
||||
|
||||
- [What is MCP?](#what-is-mcp)
|
||||
- [Why FastMCP?](#why-fastmcp)
|
||||
- [Installation](#installation)
|
||||
- [Core Concepts](#core-concepts)
|
||||
- [The `FastMCP` Server](#the-fastmcp-server)
|
||||
- [Tools](#tools)
|
||||
- [Resources \& Templates](#resources--templates)
|
||||
- [Prompts](#prompts)
|
||||
- [Context](#context)
|
||||
- [MCP Clients](#mcp-clients)
|
||||
- [Advanced Features](#advanced-features)
|
||||
- [Proxy Servers](#proxy-servers)
|
||||
- [Composing MCP Servers](#composing-mcp-servers)
|
||||
- [OpenAPI \& FastAPI Generation](#openapi--fastapi-generation)
|
||||
- [Authentication \& Security](#authentication--security)
|
||||
- [Running Your Server](#running-your-server)
|
||||
- [Contributing](#contributing)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Setup](#setup)
|
||||
- [Unit Tests](#unit-tests)
|
||||
- [Static Checks](#static-checks)
|
||||
- [Pull Requests](#pull-requests)
|
||||
- [FastMCP v2 🚀](#fastmcp-v2-)
|
||||
- [📚 Documentation](#-documentation)
|
||||
- [What is MCP?](#what-is-mcp)
|
||||
- [Why FastMCP?](#why-fastmcp)
|
||||
- [Installation](#installation)
|
||||
- [Core Concepts](#core-concepts)
|
||||
- [The `FastMCP` Server](#the-fastmcp-server)
|
||||
- [Tools](#tools)
|
||||
- [Resources \& Templates](#resources--templates)
|
||||
- [Prompts](#prompts)
|
||||
- [Context](#context)
|
||||
- [MCP Clients](#mcp-clients)
|
||||
- [Authentication](#authentication)
|
||||
- [Enterprise Authentication, Zero Configuration](#enterprise-authentication-zero-configuration)
|
||||
- [Deployment](#deployment)
|
||||
- [From Development to Production](#from-development-to-production)
|
||||
- [Advanced Features](#advanced-features)
|
||||
- [Proxy Servers](#proxy-servers)
|
||||
- [Composing MCP Servers](#composing-mcp-servers)
|
||||
- [OpenAPI \& FastAPI Generation](#openapi--fastapi-generation)
|
||||
- [Running Your Server](#running-your-server)
|
||||
- [Contributing](#contributing)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Setup](#setup)
|
||||
- [Unit Tests](#unit-tests)
|
||||
- [Static Checks](#static-checks)
|
||||
- [Pull Requests](#pull-requests)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -106,11 +118,7 @@ FastMCP provides a high-level, Pythonic interface for building, managing, and in
|
|||
|
||||
## Why FastMCP?
|
||||
|
||||
The MCP protocol is powerful but implementing it involves a lot of boilerplate - server setup, protocol handlers, content types, error management. FastMCP handles all the complex protocol details and server management, so you can focus on building great tools. It's designed to be high-level and Pythonic; in most cases, decorating a function is all you need.
|
||||
|
||||
FastMCP 2.0 has evolved into a comprehensive platform that goes far beyond basic protocol implementation. While 1.0 provided server-building capabilities (and is now part of the official MCP SDK), 2.0 offers a complete ecosystem including client libraries, authentication systems, deployment tools, integrations with major AI platforms, testing frameworks, and production-ready infrastructure patterns.
|
||||
|
||||
FastMCP aims to be:
|
||||
FastMCP handles all the complex protocol details so you can focus on building. In most cases, decorating a Python function is all you need — FastMCP handles the rest.
|
||||
|
||||
🚀 **Fast:** High-level interface means less code and faster development
|
||||
|
||||
|
|
@ -118,7 +126,9 @@ FastMCP aims to be:
|
|||
|
||||
🐍 **Pythonic:** Feels natural to Python developers
|
||||
|
||||
🔍 **Complete:** A comprehensive platform for all MCP use cases, from dev to prod
|
||||
🔍 **Complete:** Everything for production — enterprise auth (Google, GitHub, Azure, Auth0, WorkOS), deployment tools, testing frameworks, client libraries, and more
|
||||
|
||||
FastMCP provides the shortest path from idea to production. Deploy locally, to the cloud with [FastMCP Cloud](https://fastmcp.cloud), or to your own infrastructure.
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
@ -288,9 +298,82 @@ async def main():
|
|||
|
||||
Learn more in the [**Client Documentation**](https://gofastmcp.com/clients/client) and [**Transports Documentation**](https://gofastmcp.com/clients/transports).
|
||||
|
||||
## Authentication
|
||||
|
||||
### Enterprise Authentication, Zero Configuration
|
||||
|
||||
FastMCP provides comprehensive authentication support that sets it apart from basic MCP implementations. Secure your servers and authenticate your clients with the same enterprise-grade providers used by major corporations.
|
||||
|
||||
**Built-in OAuth Providers:**
|
||||
|
||||
- **Google**
|
||||
- **GitHub**
|
||||
- **Microsoft Azure**
|
||||
- **Auth0**
|
||||
- **WorkOS**
|
||||
- **Descope**
|
||||
- **JWT/Custom**
|
||||
- **API Keys**
|
||||
|
||||
Protecting a server takes just two lines:
|
||||
|
||||
```python
|
||||
from fastmcp.server.auth import GoogleProvider
|
||||
|
||||
auth = GoogleProvider(client_id="...", client_secret="...", base_url="https://myserver.com")
|
||||
mcp = FastMCP("Protected Server", auth=auth)
|
||||
```
|
||||
|
||||
Connecting to protected servers is even simpler:
|
||||
|
||||
```python
|
||||
async with Client("https://protected-server.com/mcp", auth="oauth") as client:
|
||||
# Automatic browser-based OAuth flow
|
||||
result = await client.call_tool("protected_tool")
|
||||
```
|
||||
|
||||
**Why FastMCP Auth Matters:**
|
||||
|
||||
- **Production-Ready:** Persistent storage, token refresh, comprehensive error handling
|
||||
- **Zero-Config OAuth:** Just pass `auth="oauth"` for automatic setup
|
||||
- **Enterprise Integration:** WorkOS SSO, Azure Active Directory, Auth0 tenants
|
||||
- **Developer Experience:** Automatic browser launch, local callback server, environment variable support
|
||||
- **Advanced Architecture:** Full OIDC support, Dynamic Client Registration (DCR), and unique OAuth proxy pattern that enables DCR with any provider
|
||||
|
||||
*Authentication this comprehensive is unique to FastMCP 2.0.*
|
||||
|
||||
Learn more in the **Authentication Documentation** for [servers](https://gofastmcp.com/servers/auth) and [clients](https://gofastmcp.com/clients/auth).
|
||||
|
||||
## Deployment
|
||||
|
||||
### From Development to Production
|
||||
|
||||
FastMCP supports every deployment scenario from local development to global scale:
|
||||
|
||||
**Development:** Run locally with a single command
|
||||
|
||||
```bash
|
||||
fastmcp run server.py
|
||||
```
|
||||
|
||||
**Production:** Deploy to [**FastMCP Cloud**](https://fastmcp.cloud) — Remote MCP that just works
|
||||
|
||||
- Instant HTTPS endpoints
|
||||
- Built-in authentication
|
||||
- Zero configuration
|
||||
- Free for personal servers
|
||||
|
||||
**Self-Hosted:** Use HTTP or SSE transports for your own infrastructure
|
||||
|
||||
```python
|
||||
mcp.run(transport="http", host="0.0.0.0", port=8000)
|
||||
```
|
||||
|
||||
Learn more in the [**Deployment Documentation**](https://gofastmcp.com/deployment).
|
||||
|
||||
## Advanced Features
|
||||
|
||||
FastMCP introduces powerful ways to structure and deploy your MCP applications.
|
||||
FastMCP introduces powerful ways to structure and compose your MCP applications.
|
||||
|
||||
### Proxy Servers
|
||||
|
||||
|
|
@ -310,16 +393,6 @@ Automatically generate FastMCP servers from existing OpenAPI specifications (`Fa
|
|||
|
||||
Learn more: [**OpenAPI Integration**](https://gofastmcp.com/integrations/openapi) | [**FastAPI Integration**](https://gofastmcp.com/integrations/fastapi).
|
||||
|
||||
### Authentication & Security
|
||||
|
||||
FastMCP provides built-in authentication support to secure both your MCP servers and clients in production environments. Protect your server endpoints from unauthorized access and authenticate your clients against secured MCP servers using industry-standard protocols.
|
||||
|
||||
- **Server Protection**: Secure your FastMCP server endpoints with configurable authentication providers
|
||||
- **Client Authentication**: Connect to authenticated MCP servers with automatic credential management
|
||||
- **Production Ready**: Support for common authentication patterns used in enterprise environments
|
||||
|
||||
Learn more in the **Authentication Documentation** for [servers](https://gofastmcp.com/servers/auth) and [clients](https://gofastmcp.com/clients/auth).
|
||||
|
||||
## Running Your Server
|
||||
|
||||
The main way to run a FastMCP server is by calling the `run()` method on your server instance:
|
||||
|
|
|
|||
BIN
docs/assets/brand/f-watercolor-waves-dark.png
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
docs/assets/brand/f-watercolor-waves.png
Normal file
|
After Width: | Height: | Size: 247 KiB |
BIN
docs/assets/brand/wordmark-padded.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
docs/assets/brand/wordmark-watercolor-rainbow-dark.png
Normal file
|
After Width: | Height: | Size: 401 KiB |
BIN
docs/assets/brand/wordmark-watercolor-rainbow.png
Normal file
|
After Width: | Height: | Size: 376 KiB |
BIN
docs/assets/brand/wordmark-watercolor-waves-dark.png
Normal file
|
After Width: | Height: | Size: 348 KiB |
BIN
docs/assets/brand/wordmark-watercolor-waves.png
Normal file
|
After Width: | Height: | Size: 349 KiB |
BIN
docs/assets/brand/wordmark-white-padded.png
Normal file
|
After Width: | Height: | Size: 9 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 12 KiB |
|
|
@ -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 📚
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ Try not to have too many assertions in a single test unless you truly need to ch
|
|||
|
||||
#### Inline Snapshots
|
||||
|
||||
FastMCP uses `inline-snapshot` for testing complex data structures. On first run with empty `snapshot()`, pytest will auto-populate the expected value. To update snapshots after intentional changes, run `pytest --inline-snapshot=fix`. This is particularly useful for testing JSON schemas and API responses.
|
||||
FastMCP uses `inline-snapshot` for testing complex data structures. On first run of `pytest --inline-snapshot=create` with an empty `snapshot()`, pytest will auto-populate the expected value. To update snapshots after intentional changes, run `pytest --inline-snapshot=fix`. This is particularly useful for testing JSON schemas and API responses.
|
||||
|
||||
```python
|
||||
from inline_snapshot import snapshot
|
||||
|
|
|
|||
|
|
@ -12,13 +12,19 @@
|
|||
"decoration": "windows"
|
||||
},
|
||||
"banner": {
|
||||
"content": "Remote MCP that just works: [FastMCP Cloud is here!](https://fastmcp.link/IhmBxWn) "
|
||||
"content": "Host your server on [FastMCP Cloud](https://fastmcp.cloud) for free!"
|
||||
},
|
||||
"colors": {
|
||||
"dark": "#f72585",
|
||||
"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",
|
||||
|
|
@ -45,6 +51,13 @@
|
|||
},
|
||||
"name": "FastMCP",
|
||||
"navbar": {
|
||||
"links": [
|
||||
{
|
||||
"href": "https://fastmcp.cloud",
|
||||
"icon": "cloud",
|
||||
"label": "FastMCP Cloud"
|
||||
}
|
||||
],
|
||||
"primary": {
|
||||
"href": "https://github.com/jlowin/fastmcp",
|
||||
"type": "github"
|
||||
|
|
@ -119,7 +132,10 @@
|
|||
{
|
||||
"group": "Essentials",
|
||||
"icon": "cube",
|
||||
"pages": ["clients/client", "clients/transports"]
|
||||
"pages": [
|
||||
"clients/client",
|
||||
"clients/transports"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Core Operations",
|
||||
|
|
@ -145,7 +161,10 @@
|
|||
{
|
||||
"group": "Authentication",
|
||||
"icon": "user-shield",
|
||||
"pages": ["clients/auth/oauth", "clients/auth/bearer"]
|
||||
"pages": [
|
||||
"clients/auth/oauth",
|
||||
"clients/auth/bearer"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -158,8 +177,11 @@
|
|||
"pages": [
|
||||
"integrations/auth0",
|
||||
"integrations/authkit",
|
||||
"integrations/aws-cognito",
|
||||
"integrations/azure",
|
||||
"integrations/descope",
|
||||
"integrations/github",
|
||||
"integrations/scalekit",
|
||||
"integrations/google",
|
||||
"integrations/workos"
|
||||
]
|
||||
|
|
@ -225,7 +247,10 @@
|
|||
"tab": "Documentation"
|
||||
},
|
||||
{
|
||||
"pages": ["updates", "changelog"],
|
||||
"pages": [
|
||||
"updates",
|
||||
"changelog"
|
||||
],
|
||||
"tab": "What's New"
|
||||
},
|
||||
{
|
||||
|
|
@ -309,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"
|
||||
]
|
||||
},
|
||||
|
|
@ -399,6 +429,7 @@
|
|||
]
|
||||
},
|
||||
"python-sdk/fastmcp-utilities-openapi",
|
||||
"python-sdk/fastmcp-utilities-storage",
|
||||
"python-sdk/fastmcp-utilities-tests",
|
||||
"python-sdk/fastmcp-utilities-types"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -119,11 +119,10 @@ Note that:
|
|||
|
||||
## Deploy to FastMCP Cloud
|
||||
|
||||
|
||||
[FastMCP Cloud](https://fastmcp.cloud) is a hosting service run by the FastMCP team at [Prefect](https://www.prefect.io/fastmcp). It is optimized to deploy authenticated FastMCP servers as quickly as possible, giving you a secure URL that you can plug into any LLM client.
|
||||
|
||||
<Info>
|
||||
Please note that FastMCP Cloud is a commercial service, though it is completely free for most personal servers.
|
||||
FastMCP Cloud is **free for personal servers** and offers simple pay-as-you-go pricing for teams.
|
||||
</Info>
|
||||
|
||||
To deploy your server, you'll need a [GitHub account](https://github.com). Once you have one, you can deploy your server in three steps:
|
||||
|
|
|
|||
|
|
@ -4,9 +4,22 @@ sidebarTitle: "Welcome!"
|
|||
description: The fast, Pythonic way to build MCP servers and clients.
|
||||
icon: hand-wave
|
||||
---
|
||||
<img
|
||||
src="/assets/brand/f-watercolor-waves.png"
|
||||
|
||||
alt="'F' logo on a watercolor background"
|
||||
noZoom
|
||||
className="rounded-2xl block dark:hidden"
|
||||
/>
|
||||
<img
|
||||
src="/assets/brand/f-watercolor-waves-dark.png"
|
||||
alt="'F' logo on a watercolor background"
|
||||
noZoom
|
||||
className="rounded-2xl hidden dark:block"
|
||||
/>
|
||||
|
||||
|
||||
The [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) is a new, standardized way to provide context and tools to your LLMs, and FastMCP makes building MCP servers and clients simple and intuitive. Create tools, expose resources, define prompts, and more with clean, Pythonic code:
|
||||
**FastMCP is the standard framework for building MCP applications.** The [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) provides a standardized way to connect LLMs to tools and data, and FastMCP makes it production-ready with clean, Pythonic code:
|
||||
|
||||
```python {1}
|
||||
from fastmcp import FastMCP
|
||||
|
|
@ -23,15 +36,13 @@ if __name__ == "__main__":
|
|||
```
|
||||
|
||||
|
||||
## Beyond the Protocol
|
||||
## Beyond Basic MCP
|
||||
|
||||
FastMCP is the standard framework for working with the Model Context Protocol. FastMCP 1.0 was incorporated into the [official MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk) in 2024.
|
||||
FastMCP pioneered Python MCP development, with FastMCP 1.0 being incorporated into the [official MCP SDK](https://github.com/modelcontextprotocol/python-sdk) in 2024.
|
||||
|
||||
This is FastMCP 2.0, the **actively maintained version** that provides a complete toolkit for working with the MCP ecosystem.
|
||||
**This is FastMCP 2.0** — the actively maintained version that extends far beyond basic protocol implementation. While the SDK provides core functionality, FastMCP 2.0 delivers everything needed for production: advanced MCP patterns (server composition, proxying, OpenAPI/FastAPI generation, tool transformation), enterprise auth (Google, GitHub, Azure, Auth0, WorkOS, and more), deployment tools, testing frameworks, and comprehensive client libraries.
|
||||
|
||||
FastMCP 2.0 has a comprehensive set of features that go far beyond the core MCP specification, all in service of providing **the simplest path to production**. These include deployment, auth, clients, server proxying and composition, generating servers from REST APIs, dynamic tool rewriting, built-in testing tools, integrations, and more.
|
||||
|
||||
Ready to upgrade or get started? Follow the [installation instructions](/getting-started/installation), which include steps for upgrading from the official MCP SDK.
|
||||
Ready to build? Start with our [installation guide](/getting-started/installation) or jump straight to the [quickstart](/getting-started/quickstart).
|
||||
|
||||
|
||||
## What is MCP?
|
||||
|
|
@ -47,11 +58,7 @@ FastMCP provides a high-level, Pythonic interface for building, managing, and in
|
|||
|
||||
## Why FastMCP?
|
||||
|
||||
The MCP protocol is powerful but implementing it involves a lot of boilerplate - server setup, protocol handlers, content types, error management. FastMCP handles all the complex protocol details and server management, so you can focus on building great tools. It's designed to be high-level and Pythonic; in most cases, decorating a function is all you need.
|
||||
|
||||
FastMCP 2.0 has evolved into a comprehensive platform that goes far beyond basic protocol implementation. While 1.0 provided server-building capabilities (and is now part of the official MCP SDK), 2.0 offers a complete ecosystem including client libraries, authentication systems, deployment tools, integrations with major AI platforms, testing frameworks, and production-ready infrastructure patterns.
|
||||
|
||||
FastMCP aims to be:
|
||||
FastMCP handles all the complex protocol details so you can focus on building. In most cases, decorating a Python function is all you need — FastMCP handles the rest.
|
||||
|
||||
🚀 **Fast**: High-level interface means less code and faster development
|
||||
|
||||
|
|
@ -59,7 +66,9 @@ FastMCP aims to be:
|
|||
|
||||
🐍 **Pythonic**: Feels natural to Python developers
|
||||
|
||||
🔍 **Complete**: A comprehensive platform for all MCP use cases, from dev to prod
|
||||
🔍 **Complete**: Everything for production — enterprise auth (Google, GitHub, Azure, Auth0, WorkOS), deployment tools, testing frameworks, client libraries, and more
|
||||
|
||||
FastMCP provides the shortest path from idea to production. Deploy locally, to the cloud with [FastMCP Cloud](https://fastmcp.cloud) (free for personal servers), or to your own infrastructure.
|
||||
|
||||
FastMCP is made with 💙 by [Prefect](https://www.prefect.io/).
|
||||
|
||||
|
|
@ -67,12 +76,35 @@ FastMCP is made with 💙 by [Prefect](https://www.prefect.io/).
|
|||
|
||||
## LLM-Friendly Docs
|
||||
|
||||
This documentation is also available in [llms.txt format](https://llmstxt.org/), which is a simple markdown standard that LLMs can consume easily.
|
||||
The FastMCP documentation is available in multiple LLM-friendly formats:
|
||||
|
||||
There are two ways to access the LLM-friendly documentation:
|
||||
- [llms.txt](https://gofastmcp.com/llms.txt) is essentially a sitemap, listing all the pages in the documentation.
|
||||
- [llms-full.txt](https://gofastmcp.com/llms-full.txt) contains the entire documentation. Note this may exceed the context window of your LLM.
|
||||
### MCP Server
|
||||
|
||||
In addition, any page can be accessed as markdown by appending `.md` to the URL. For example, this page would become `https://gofastmcp.com/getting-started/welcome.md`, which you can view [here](/getting-started/welcome.md).
|
||||
The FastMCP docs are accessible via MCP! The server URL is `https://gofastmcp.com/mcp`.
|
||||
|
||||
Finally, you can copy the contents of any page as markdown by pressing "Cmd+C" (or "Ctrl+C" on Windows) on your keyboard.
|
||||
In fact, you can use FastMCP to search the FastMCP docs:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from fastmcp import Client
|
||||
|
||||
async def main():
|
||||
async with Client("https://gofastmcp.com/mcp") as client:
|
||||
result = await client.call_tool(
|
||||
name="SearchFastMcp",
|
||||
arguments={"query": "deploy a FastMCP server"}
|
||||
)
|
||||
print(result)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
### Plain Text Formats
|
||||
|
||||
The docs are also available in [llms.txt format](https://llmstxt.org/):
|
||||
- [llms.txt](https://gofastmcp.com/llms.txt) - A sitemap listing all documentation pages
|
||||
- [llms-full.txt](https://gofastmcp.com/llms-full.txt) - The entire documentation in one file (may exceed context windows)
|
||||
|
||||
Any page can be accessed as markdown by appending `.md` to the URL. For example, this page becomes `https://gofastmcp.com/getting-started/welcome.md`.
|
||||
|
||||
You can also copy any page as markdown by pressing "Cmd+C" (or "Ctrl+C" on Windows) on your keyboard.
|
||||
|
|
|
|||
322
docs/integrations/aws-cognito.mdx
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
---
|
||||
title: AWS Cognito OAuth 🤝 FastMCP
|
||||
sidebarTitle: AWS Cognito
|
||||
description: Secure your FastMCP server with AWS Cognito user pools
|
||||
icon: aws
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
||||
<VersionBadge version="2.12.4" />
|
||||
|
||||
This guide shows you how to secure your FastMCP server using **AWS Cognito user pools**. Since AWS Cognito doesn't support Dynamic Client Registration, this integration uses the [**OAuth Proxy**](/servers/auth/oauth-proxy) pattern to bridge AWS Cognito's traditional OAuth with MCP's authentication requirements. It also includes robust JWT token validation, ensuring enterprise-grade authentication.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before you begin, you will need:
|
||||
1. An **[AWS Account](https://aws.amazon.com/)** with access to create AWS Cognito user pools
|
||||
2. Basic familiarity with AWS Cognito concepts (user pools, app clients)
|
||||
3. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:8000`)
|
||||
|
||||
### Step 1: Create an AWS Cognito User Pool and App Client
|
||||
|
||||
Set up AWS Cognito user pool with an app client to get the credentials needed for authentication:
|
||||
|
||||
<Steps>
|
||||
<Step title="Navigate to AWS Cognito">
|
||||
Go to the **[AWS Cognito Console](https://console.aws.amazon.com/cognito/)** and ensure you're in your desired AWS region.
|
||||
|
||||
Select **"User pools"** from the side navigation (click on the hamburger icon at the top left in case you don't see any), and click **"Create user pool"** to create a new user pool.
|
||||
</Step>
|
||||
|
||||
<Step title="Define Your Application">
|
||||
AWS Cognito now provides a streamlined setup experience:
|
||||
|
||||
1. **Application type**: Select **"Traditional web application"** (this is the correct choice for FastMCP server-side authentication)
|
||||
2. **Name your application**: Enter a descriptive name (e.g., `FastMCP Server`)
|
||||
|
||||
The traditional web application type automatically configures:
|
||||
- Server-side authentication with client secrets
|
||||
- Authorization code grant flow
|
||||
- Appropriate security settings for confidential clients
|
||||
|
||||
<Info>
|
||||
Choose "Traditional web application" rather than SPA, Mobile app, or Machine-to-machine options. This ensures proper OAuth 2.0 configuration for FastMCP.
|
||||
</Info>
|
||||
</Step>
|
||||
|
||||
<Step title="Configure Options">
|
||||
AWS will guide you through configuration options:
|
||||
|
||||
- **Sign-in identifiers**: Choose how users will sign in (email, username, or phone)
|
||||
- **Required attributes**: Select any additional user information you need
|
||||
- **Return URL**: Add your callback URL (e.g., `http://localhost:8000/auth/callback` for development)
|
||||
|
||||
<Tip>
|
||||
The simplified interface handles most OAuth security settings automatically based on your application type selection.
|
||||
</Tip>
|
||||
</Step>
|
||||
|
||||
<Step title="Review and Create">
|
||||
Review your configuration and click **"Create user pool"**.
|
||||
|
||||
After creation, you'll see your user pool details. Save these important values:
|
||||
- **User pool ID** (format: `eu-central-1_XXXXXXXXX`)
|
||||
- **Client ID** (found under → "Applications" → "App clients" in the side navigation → \<Your application name, e.g., `FastMCP Server`\> → "App client information")
|
||||
- **Client Secret** (found under → "Applications" → "App clients" in the side navigation → \<Your application name, e.g., `FastMCP Server`\> → "App client information")
|
||||
|
||||
<Tip>
|
||||
The user pool ID and app client credentials are all you need for FastMCP configuration.
|
||||
</Tip>
|
||||
</Step>
|
||||
|
||||
<Step title="Configure OAuth Settings">
|
||||
Under "Login pages" in your app client's settings, you can double check and adjust the OAuth configuration:
|
||||
|
||||
- **Allowed callback URLs**: Add your server URL + `/auth/callback` (e.g., `http://localhost:8000/auth/callback`)
|
||||
- **Allowed sign-out URLs**: Optional, for logout functionality
|
||||
- **OAuth 2.0 grant types**: Ensure "Authorization code grant" is selected
|
||||
- **OpenID Connect scopes**: Select scopes your application needs (e.g., `openid`, `email`, `profile`)
|
||||
|
||||
<Tip>
|
||||
For local development, you can use `http://localhost` URLs. For production, you must use HTTPS.
|
||||
</Tip>
|
||||
</Step>
|
||||
|
||||
<Step title="Pick Up AWS Cognito Domain">
|
||||
Navigate to **"Branding" → "Domain"** in the side navigation to find or configure Your AWS Cognito domain:
|
||||
|
||||
**Option 1: Use Auto-Generated Domain**
|
||||
- If AWS has already created a domain automatically, note the **domain prefix** (the part before `.auth.region.amazoncognito.com`)
|
||||
- This prefix is what you'll use in your FastMCP configuration
|
||||
|
||||
**Option 2: Create a Custom Domain Prefix**
|
||||
- If no domain exists or you want a better name, delete the existing domain and create a new one using the **"Actions"** menu
|
||||
- Under **"Domain"** → **"Cognito domain"** in the **"Create Cognito domain"** dialog, enter a meaningful prefix (e.g., `my-app`) that is available in the AWS region you are in
|
||||
- Just note the **domain prefix** you entered (e.g., `my-fastmcp-app`) - this is what you'll use in your FastMCP configuration
|
||||
|
||||
<Info>
|
||||
The FastMCP AWS Cognito provider automatically constructs the full domain from your prefix and region, simplifying configuration.
|
||||
</Info>
|
||||
</Step>
|
||||
|
||||
<Step title="Save Your Credentials">
|
||||
After setup, you'll have:
|
||||
|
||||
- **User Pool ID**: Format like `eu-central-1_XXXXXXXXX`
|
||||
- **Client ID**: Your application's client identifier
|
||||
- **Client Secret**: Generated client secret (keep secure)
|
||||
- **Domain Prefix**: The prefix of Your AWS Cognito domain
|
||||
- **AWS Region**: Where Your AWS Cognito user pool is located
|
||||
|
||||
<Tip>
|
||||
Store these credentials securely. Never commit them to version control. Use environment variables or AWS Secrets Manager in production.
|
||||
</Tip>
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### Step 2: FastMCP Configuration
|
||||
|
||||
Create your FastMCP server using the `AWSCognitoProvider`, which handles AWS Cognito's JWT tokens and user claims automatically:
|
||||
|
||||
```python server.py
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.aws import AWSCognitoProvider
|
||||
from fastmcp.server.dependencies import get_access_token
|
||||
|
||||
# The AWSCognitoProvider handles JWT validation and user claims
|
||||
auth_provider = AWSCognitoProvider(
|
||||
user_pool_id="eu-central-1_XXXXXXXXX", # Your AWS Cognito user pool ID
|
||||
aws_region="eu-central-1", # AWS region (defaults to eu-central-1)
|
||||
client_id="your-app-client-id", # Your app client ID
|
||||
client_secret="your-app-client-secret", # Your app client Secret
|
||||
base_url="http://localhost:8000", # Must match your callback URL
|
||||
# redirect_path="/auth/callback" # Default value, customize if needed
|
||||
)
|
||||
|
||||
mcp = FastMCP(name="AWS Cognito Secured App", auth=auth_provider)
|
||||
|
||||
# Add a protected tool to test authentication
|
||||
@mcp.tool
|
||||
async def get_access_token_claims() -> dict:
|
||||
"""Get the authenticated user's access token claims."""
|
||||
token = get_access_token()
|
||||
return {
|
||||
"sub": token.claims.get("sub"),
|
||||
"username": token.claims.get("username"),
|
||||
"cognito:groups": token.claims.get("cognito:groups", []),
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Running the Server
|
||||
|
||||
Start your FastMCP server with HTTP transport to enable OAuth flows:
|
||||
|
||||
```bash
|
||||
fastmcp run server.py --transport http --port 8000
|
||||
```
|
||||
|
||||
Your server is now running and protected by AWS Cognito OAuth authentication.
|
||||
|
||||
### Testing with a Client
|
||||
|
||||
Create a test client that authenticates with Your AWS Cognito-protected server:
|
||||
|
||||
```python test_client.py
|
||||
from fastmcp import Client
|
||||
import asyncio
|
||||
|
||||
async def main():
|
||||
# The client will automatically handle AWS Cognito OAuth
|
||||
async with Client("http://localhost:8000/mcp/", auth="oauth") as client:
|
||||
# First-time connection will open AWS Cognito login in your browser
|
||||
print("✓ Authenticated with AWS Cognito!")
|
||||
|
||||
# Test the protected tool
|
||||
print("Calling protected tool: get_access_token_claims")
|
||||
result = await client.call_tool("get_access_token_claims")
|
||||
user_data = result.data
|
||||
print("Available access token claims:")
|
||||
print(f"- sub: {user_data.get('sub', 'N/A')}")
|
||||
print(f"- username: {user_data.get('username', 'N/A')}")
|
||||
print(f"- cognito:groups: {user_data.get('cognito:groups', [])}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
When you run the client for the first time:
|
||||
1. Your browser will open to AWS Cognito's hosted UI login page
|
||||
2. After you sign in (or sign up), you'll be redirected back to your MCP server
|
||||
3. The client receives the JWT token and can make authenticated requests
|
||||
|
||||
<Info>
|
||||
The client caches tokens locally, so you won't need to re-authenticate for subsequent runs unless the token expires or you explicitly clear the cache.
|
||||
</Info>
|
||||
|
||||
## Environment Variables
|
||||
|
||||
For production deployments, use environment variables instead of hardcoding credentials.
|
||||
|
||||
### Provider Selection
|
||||
|
||||
Setting this environment variable allows the AWS Cognito 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.aws.AWSCognitoProvider` to use AWS Cognito authentication.
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
### AWS Cognito-Specific Configuration
|
||||
|
||||
These environment variables provide default values for the AWS Cognito provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
|
||||
|
||||
<Card>
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID" required>
|
||||
Your AWS Cognito user pool ID (e.g., `eu-central-1_XXXXXXXXX`)
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_AWS_REGION" default="eu-central-1">
|
||||
AWS region where your AWS Cognito user pool is located
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID" required>
|
||||
Your AWS Cognito app client ID
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET" required>
|
||||
Your AWS Cognito app client secret
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_BASE_URL" default="http://localhost:8000">
|
||||
Public URL of your FastMCP server for OAuth callbacks
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_REDIRECT_PATH" default="/auth/callback">
|
||||
One of the redirect paths configured in your AWS Cognito app client
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_REQUIRED_SCOPES" default='["openid"]'>
|
||||
Comma-, space-, or JSON-separated list of required OAuth scopes (e.g., `openid email` or `["openid","email","profile"]`)
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
Example `.env` file:
|
||||
```bash
|
||||
# Use the AWS Cognito provider
|
||||
FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.aws.AWSCognitoProvider
|
||||
|
||||
# AWS Cognito credentials
|
||||
FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID=eu-central-1_XXXXXXXXX
|
||||
FASTMCP_SERVER_AUTH_AWS_COGNITO_AWS_REGION=eu-central-1
|
||||
FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID=your-app-client-id
|
||||
FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET=your-app-client-secret
|
||||
FASTMCP_SERVER_AUTH_AWS_COGNITO_BASE_URL=https://your-server.com
|
||||
FASTMCP_SERVER_AUTH_AWS_COGNITO_REQUIRED_SCOPES=openid,email,profile
|
||||
```
|
||||
|
||||
With environment variables set, your server code simplifies to:
|
||||
|
||||
```python server.py
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.dependencies import get_access_token
|
||||
|
||||
# Authentication is automatically configured from environment
|
||||
mcp = FastMCP(name="AWS Cognito Secured App")
|
||||
|
||||
@mcp.tool
|
||||
async def get_access_token_claims() -> dict:
|
||||
"""Get the authenticated user's access token claims."""
|
||||
token = get_access_token()
|
||||
return {
|
||||
"sub": token.claims.get("sub"),
|
||||
"username": token.claims.get("username"),
|
||||
"cognito:groups": token.claims.get("cognito:groups", []),
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
### JWT Token Validation
|
||||
|
||||
The AWS Cognito provider includes robust JWT token validation:
|
||||
|
||||
- **Signature Verification**: Validates tokens against AWS Cognito's public keys (JWKS)
|
||||
- **Expiration Checking**: Automatically rejects expired tokens
|
||||
- **Issuer Validation**: Ensures tokens come from your specific AWS Cognito user pool
|
||||
- **Scope Enforcement**: Verifies required OAuth scopes are present
|
||||
|
||||
### User Claims and Groups
|
||||
|
||||
Access rich user information from AWS Cognito JWT tokens:
|
||||
|
||||
```python
|
||||
from fastmcp.server.dependencies import get_access_token
|
||||
|
||||
@mcp.tool
|
||||
async def admin_only_tool() -> str:
|
||||
"""A tool only available to admin users."""
|
||||
token = get_access_token()
|
||||
user_groups = token.claims.get("cognito:groups", [])
|
||||
|
||||
if "admin" not in user_groups:
|
||||
raise ValueError("This tool requires admin access")
|
||||
|
||||
return "Admin access granted!"
|
||||
```
|
||||
|
||||
### Enterprise Integration
|
||||
|
||||
Perfect for enterprise environments with:
|
||||
|
||||
- **Single Sign-On (SSO)**: Integrate with corporate identity providers
|
||||
- **Multi-Factor Authentication (MFA)**: Leverage AWS Cognito's built-in MFA
|
||||
- **User Groups**: Role-based access control through AWS Cognito groups
|
||||
- **Custom Attributes**: Access custom user attributes defined in your AWS Cognito user pool
|
||||
- **Compliance**: Meet enterprise security and compliance requirements
|
||||
|
|
@ -10,7 +10,7 @@ import { VersionBadge } from "/snippets/version-badge.mdx"
|
|||
|
||||
<VersionBadge version="2.12.0" />
|
||||
|
||||
This guide shows you how to secure your FastMCP server using **Azure OAuth** (Microsoft Entra ID). Since Azure doesn't support Dynamic Client Registration, this integration uses the [**OAuth Proxy**](/servers/auth/oauth-proxy) pattern to bridge Azure's traditional OAuth with MCP's authentication requirements.
|
||||
This guide shows you how to secure your FastMCP server using **Azure OAuth** (Microsoft Entra ID). Since Azure doesn't support Dynamic Client Registration, this integration uses the [**OAuth Proxy**](/servers/auth/oauth-proxy) pattern to bridge Azure's traditional OAuth with MCP's authentication requirements. FastMCP validates Azure JWTs against your application's client_id.
|
||||
|
||||
## Configuration
|
||||
|
||||
|
|
@ -49,8 +49,39 @@ Create an App registration in Azure Portal to get the credentials needed for aut
|
|||
<Tip>
|
||||
If you want to use a custom callback path (e.g., `/auth/azure/callback`), make sure to set the same path in both your Azure App registration and the `redirect_path` parameter when configuring the AzureProvider.
|
||||
</Tip>
|
||||
|
||||
- **Expose an API**: Configure your Application ID URI and define scopes
|
||||
- Go to **Expose an API** in the App registration sidebar.
|
||||
- Click **Set** next to "Application ID URI" and choose one of:
|
||||
- Keep the default `api://{client_id}`
|
||||
- Set a custom value, following the supported formats (see [Identifier URI restrictions](https://learn.microsoft.com/en-us/entra/identity-platform/identifier-uri-restrictions))
|
||||
- Click **Add a scope** and create a scope your app will require, for example:
|
||||
- Scope name: `read` (or `write`, etc.)
|
||||
- Admin consent display name/description: as appropriate for your org
|
||||
- Who can consent: as needed (Admins only or Admins and users)
|
||||
|
||||
- **Configure Access Token Version**: Ensure your app uses access token v2
|
||||
- Go to **Manifest** in the App registration sidebar.
|
||||
- Find the `requestedAccessTokenVersion` property and set it to `2`:
|
||||
```json
|
||||
"api": {
|
||||
"requestedAccessTokenVersion": 2
|
||||
}
|
||||
```
|
||||
- Click **Save** at the top of the manifest editor.
|
||||
|
||||
<Warning>
|
||||
Access token v2 is required for FastMCP's Azure integration to work correctly. If this is not set, you may encounter authentication errors.
|
||||
</Warning>
|
||||
|
||||
<Note>
|
||||
In FastMCP's `AzureProvider`, set `identifier_uri` to your Application ID URI (optional; defaults to `api://{client_id}`) and set `required_scopes` to the unprefixed scope names (e.g., `read`, `write`). During authorization, FastMCP automatically prefixes scopes with your `identifier_uri`.
|
||||
</Note>
|
||||
|
||||
|
||||
</Step>
|
||||
|
||||
|
||||
<Step title="Create Client Secret">
|
||||
After registration, navigate to **Certificates & secrets** in your app's settings.
|
||||
|
||||
|
|
@ -91,7 +122,11 @@ auth_provider = AzureProvider(
|
|||
client_secret="your-client-secret", # Your Azure App Client Secret
|
||||
tenant_id="08541b6e-646d-43de-a0eb-834e6713d6d5", # Your Azure Tenant ID (REQUIRED)
|
||||
base_url="http://localhost:8000", # Must match your App registration
|
||||
required_scopes=["User.Read", "email", "openid", "profile"], # Microsoft Graph permissions
|
||||
required_scopes=["your-scope"], # Name of scope created when configuring your App
|
||||
# identifier_uri defaults to api://{client_id}
|
||||
# identifier_uri="api://your-api-id",
|
||||
# Optional: request additional upstream scopes in the authorize request
|
||||
# additional_authorize_scopes=["User.Read", "offline_access", "openid", "email"],
|
||||
# redirect_path="/auth/callback" # Default value, customize if needed
|
||||
)
|
||||
|
||||
|
|
@ -215,12 +250,16 @@ Public URL of your FastMCP server for OAuth callbacks
|
|||
Redirect path configured in your Azure App registration
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_REQUIRED_SCOPES" default='["User.Read", "email", "openid", "profile"]'>
|
||||
Comma-, space-, or JSON-separated list of required Microsoft Graph scopes
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_REQUIRED_SCOPES" default="">
|
||||
Comma-, space-, or JSON-separated list of required scopes for your API. These are validated on tokens and used as defaults if the client does not request specific scopes.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_TIMEOUT_SECONDS" default="10">
|
||||
HTTP request timeout for Microsoft Graph API calls
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_ADDITIONAL_AUTHORIZE_SCOPES" default="">
|
||||
Comma-, space-, or JSON-separated list of additional scopes to include in the authorization request without prefixing. Use this to request upstream scopes such as Microsoft Graph permissions. These are not used for token validation.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_IDENTIFIER_URI" default="api://{client_id}">
|
||||
Application ID URI used to prefix scopes during authorization.
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
|
|
@ -234,7 +273,11 @@ FASTMCP_SERVER_AUTH_AZURE_CLIENT_ID=835f09b6-0f0f-40cc-85cb-f32c5829a149
|
|||
FASTMCP_SERVER_AUTH_AZURE_CLIENT_SECRET=your-client-secret-here
|
||||
FASTMCP_SERVER_AUTH_AZURE_TENANT_ID=08541b6e-646d-43de-a0eb-834e6713d6d5
|
||||
FASTMCP_SERVER_AUTH_AZURE_BASE_URL=https://your-server.com
|
||||
FASTMCP_SERVER_AUTH_AZURE_REQUIRED_SCOPES=User.Read,email,profile
|
||||
FASTMCP_SERVER_AUTH_AZURE_REQUIRED_SCOPES=read,write
|
||||
# Optional custom API configuration
|
||||
# FASTMCP_SERVER_AUTH_AZURE_IDENTIFIER_URI=api://your-api-id
|
||||
# Request additional upstream scopes (optional)
|
||||
# FASTMCP_SERVER_AUTH_AZURE_ADDITIONAL_AUTHORIZE_SCOPES=User.Read,Mail.Read
|
||||
```
|
||||
|
||||
With environment variables set, your server code simplifies to:
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
154
docs/integrations/descope.mdx
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
---
|
||||
title: Descope 🤝 FastMCP
|
||||
sidebarTitle: Descope
|
||||
description: Secure your FastMCP server with Descope
|
||||
icon: shield-check
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
||||
<VersionBadge version="2.12.4" />
|
||||
|
||||
This guide shows you how to secure your FastMCP server using [**Descope**](https://www.descope.com), a complete authentication and user management solution. This integration uses the [**Remote OAuth**](/servers/auth/remote-oauth) pattern, where Descope handles user login and your FastMCP server validates the tokens.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before you begin, you will need:
|
||||
1. To [sign up](https://www.descope.com/sign-up) for a Free Forever Descope account
|
||||
2. Your **Project ID** from the [Descope Console](https://app.descope.com/settings/project)
|
||||
3. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:3000`)
|
||||
|
||||
### Step 1: Configure Descope
|
||||
|
||||
<Steps>
|
||||
<Step title="Enable Dynamic Client Registration">
|
||||
1. Go to the [Inbound Apps page](https://app.descope.com/apps/inbound) of the Descope Console
|
||||
2. Click **DCR Settings**
|
||||
3. Enable **Dynamic Client Registration (DCR)**
|
||||
4. Define allowed scopes
|
||||
|
||||
<Warning>
|
||||
DCR is required for FastMCP clients to automatically register with your authentication server.
|
||||
</Warning>
|
||||
</Step>
|
||||
|
||||
<Step title="Note Your Project ID">
|
||||
Save your Project ID from [Project Settings](https://app.descope.com/settings/project):
|
||||
```
|
||||
Project ID: P2abc...123
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### Step 2: Environment Setup
|
||||
|
||||
Create a `.env` file with your Descope configuration:
|
||||
|
||||
```bash
|
||||
DESCOPE_PROJECT_ID=P2abc...123 # Your Descope Project ID
|
||||
DESCOPE_BASE_URL=https://api.descope.com # Descope API URL
|
||||
SERVER_URL=http://localhost:3000 # Your server's base URL
|
||||
```
|
||||
|
||||
<Note>
|
||||
You can find your project's Descope Base URL in the [Multi-Region Support Guide](https://docs.descope.com/management/project-settings/multi-regional).
|
||||
</Note>
|
||||
|
||||
### Step 3: FastMCP Configuration
|
||||
|
||||
Create your FastMCP server file and use the DescopeProvider to handle all the OAuth integration automatically:
|
||||
|
||||
```python server.py
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.descope import DescopeProvider
|
||||
|
||||
# The DescopeProvider automatically discovers Descope endpoints
|
||||
# and configures JWT token validation
|
||||
auth_provider = DescopeProvider(
|
||||
project_id=DESCOPE_PROJECT_ID, # Your Descope Project ID
|
||||
base_url=SERVER_URL, # Your server's public URL
|
||||
descope_base_url=DESCOPE_BASE_URL, # Descope API base URL
|
||||
)
|
||||
|
||||
# Create FastMCP server with auth
|
||||
mcp = FastMCP(name="My Descope Protected Server", auth=auth_provider)
|
||||
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
To test your server, you can use the `fastmcp` CLI to run it locally. Assuming you've saved the above code to `server.py` (after replacing the `project_id`, `base_url`, and `descope_base_url` with your actual values!), you can run the following command:
|
||||
|
||||
```bash
|
||||
fastmcp run server.py --transport http --port 8000
|
||||
```
|
||||
|
||||
Now, you can use a FastMCP client to test that you can reach your server after authenticating:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
import asyncio
|
||||
|
||||
async def main():
|
||||
async with Client("http://localhost:8000/mcp/", auth="oauth") as client:
|
||||
assert await client.ping()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
|
||||
For production deployments, use environment variables instead of hardcoding credentials.
|
||||
|
||||
### Provider Selection
|
||||
|
||||
Setting this environment variable allows the Descope 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.descope.DescopeProvider` to use Descope authentication.
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
### Descope-Specific Configuration
|
||||
|
||||
These environment variables provide default values for the Descope provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
|
||||
|
||||
<Card>
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_PROJECT_ID" required>
|
||||
Your Descope Project ID from the [Descope Console](https://app.descope.com/settings/project)
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_BASE_URL" required>
|
||||
Public URL of your FastMCP server (e.g., `https://your-server.com` or `http://localhost:8000` for development)
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_DESCOPE_BASE_URL" default="https://api.descope.com">
|
||||
Descope API base URL for your [region/environment](https://docs.descope.com/management/project-settings/multi-regional)
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
Example `.env` file:
|
||||
```bash
|
||||
# Use the Descope provider
|
||||
FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.descope.DescopeProvider
|
||||
|
||||
# Descope configuration
|
||||
FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_PROJECT_ID=P2abc...123
|
||||
FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_BASE_URL=https://your-server.com
|
||||
FASTMCP_SERVER_AUTH_DESCOPEPROVIDER_DESCOPE_BASE_URL=https://api.descope.com
|
||||
```
|
||||
|
||||
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 Descope Protected Server")
|
||||
```
|
||||
|
|
@ -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
|
|
@ -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"}
|
||||
```
|
||||
|
|
@ -116,7 +116,7 @@ def hello() -> str:
|
|||
You can run it with:
|
||||
|
||||
```bash
|
||||
fastmcp run server.py:custom_name
|
||||
fastmcp run server.py:my_server
|
||||
```
|
||||
|
||||
#### Factory Function
|
||||
|
|
|
|||
|
|
@ -548,7 +548,7 @@ Provide your own schema that differs from the parent. The tool must return data
|
|||
|
||||
**Remove Output Schema**
|
||||
```python
|
||||
Tool.from_tool(parent_tool, output_schema=False)
|
||||
Tool.from_tool(parent_tool, output_schema=None)
|
||||
```
|
||||
Removes the output schema declaration. Automatic structured content still works for object-like returns (dict, dataclass, Pydantic models) but primitive types won't be structured.
|
||||
|
||||
|
|
@ -566,8 +566,139 @@ Use a transform function returning `ToolResult` for complete control over both c
|
|||
|
||||
Tool transformation is a flexible feature that supports many powerful patterns. Here are a few common use cases to give you ideas.
|
||||
|
||||
### Exposing Client Methods as Tools
|
||||
|
||||
A powerful use case for tool transformation is exposing methods from existing Python clients (GitHub clients, API clients, database clients, etc.) directly as MCP tools. This pattern eliminates boilerplate wrapper functions and treats tools as annotations around client methods.
|
||||
|
||||
**Without Tool Transformation**, you typically create wrapper functions that duplicate annotations:
|
||||
|
||||
```python
|
||||
async def get_repository(
|
||||
owner: Annotated[str, "The owner of the repository."],
|
||||
repo: Annotated[str, "The name of the repository."],
|
||||
) -> Repository:
|
||||
"""Get basic information about a GitHub repository."""
|
||||
return await github_client.get_repository(owner=owner, repo=repo)
|
||||
```
|
||||
|
||||
**With Tool Transformation**, you can wrap the client method directly:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.tools import Tool
|
||||
from fastmcp.tools.tool_transform import ArgTransform
|
||||
|
||||
mcp = FastMCP("GitHub Tools")
|
||||
|
||||
# Wrap a client method directly as a tool
|
||||
get_repo_tool = Tool.from_tool(
|
||||
tool=Tool.from_function(fn=github_client.get_repository),
|
||||
description="Get basic information about a GitHub repository.",
|
||||
transform_args={
|
||||
"owner": ArgTransform(description="The owner of the repository."),
|
||||
"repo": ArgTransform(description="The name of the repository."),
|
||||
}
|
||||
)
|
||||
|
||||
mcp.add_tool(get_repo_tool)
|
||||
```
|
||||
|
||||
This pattern keeps the implementation in your client and treats the tool as an annotation layer, avoiding duplicate code.
|
||||
|
||||
#### Hiding Client-Specific Arguments
|
||||
|
||||
Client methods often have internal parameters (debug flags, auth tokens, rate limit settings) that shouldn't be exposed to LLMs. Use `hide=True` with a default value to handle these automatically:
|
||||
|
||||
```python
|
||||
get_issues_tool = Tool.from_tool(
|
||||
tool=Tool.from_function(fn=github_client.get_issues),
|
||||
description="Get issues from a GitHub repository.",
|
||||
transform_args={
|
||||
"owner": ArgTransform(description="The owner of the repository."),
|
||||
"repo": ArgTransform(description="The name of the repository."),
|
||||
"limit": ArgTransform(description="Maximum number of issues to return."),
|
||||
# Hide internal parameters
|
||||
"include_debug_info": ArgTransform(hide=True, default=False),
|
||||
"error_on_not_found": ArgTransform(hide=True, default=True),
|
||||
}
|
||||
)
|
||||
|
||||
mcp.add_tool(get_issues_tool)
|
||||
```
|
||||
|
||||
The LLM only sees `owner`, `repo`, and `limit`. Internal parameters are supplied automatically.
|
||||
|
||||
#### Reusable Argument Patterns
|
||||
|
||||
When wrapping multiple client methods, you can define reusable argument transformations. This scales well for larger tool sets and keeps annotations consistent:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.tools import Tool
|
||||
from fastmcp.tools.tool_transform import ArgTransform
|
||||
|
||||
mcp = FastMCP("GitHub Tools")
|
||||
|
||||
# Define reusable argument patterns
|
||||
OWNER_ARG = ArgTransform(description="The repository owner.")
|
||||
REPO_ARG = ArgTransform(description="The repository name.")
|
||||
LIMIT_ARG = ArgTransform(description="Maximum number of items to return.")
|
||||
HIDE_ERROR = ArgTransform(hide=True, default=True)
|
||||
|
||||
def create_github_tools(client):
|
||||
"""Create tools from GitHub client methods with shared argument patterns."""
|
||||
|
||||
owner_repo_args = {
|
||||
"owner": OWNER_ARG,
|
||||
"repo": REPO_ARG,
|
||||
}
|
||||
|
||||
error_args = {
|
||||
"error_on_not_found": HIDE_ERROR,
|
||||
}
|
||||
|
||||
return [
|
||||
Tool.from_tool(
|
||||
tool=Tool.from_function(fn=client.get_repository),
|
||||
description="Get basic information about a GitHub repository.",
|
||||
transform_args={**owner_repo_args, **error_args}
|
||||
),
|
||||
Tool.from_tool(
|
||||
tool=Tool.from_function(fn=client.get_issue),
|
||||
description="Get a specific issue from a repository.",
|
||||
transform_args={
|
||||
**owner_repo_args,
|
||||
"issue_number": ArgTransform(description="The issue number."),
|
||||
"limit_comments": LIMIT_ARG,
|
||||
**error_args,
|
||||
}
|
||||
),
|
||||
Tool.from_tool(
|
||||
tool=Tool.from_function(fn=client.get_pull_request),
|
||||
description="Get a specific pull request from a repository.",
|
||||
transform_args={
|
||||
**owner_repo_args,
|
||||
"pull_request_number": ArgTransform(description="The PR number."),
|
||||
"limit_comments": LIMIT_ARG,
|
||||
**error_args,
|
||||
}
|
||||
),
|
||||
]
|
||||
|
||||
# Add all tools to the server
|
||||
for tool in create_github_tools(github_client):
|
||||
mcp.add_tool(tool)
|
||||
```
|
||||
|
||||
This pattern provides several benefits:
|
||||
|
||||
- **No duplicate implementation**: Logic stays in the client
|
||||
- **Consistent annotations**: Reusable argument patterns ensure consistency
|
||||
- **Easy maintenance**: Update the client, not wrapper functions
|
||||
- **Scalable**: Easily add new tools by wrapping additional client methods
|
||||
|
||||
### Adapting Remote or Generated Tools
|
||||
This is one of the most common reasons to use tool transformation. Tools from remote servers (via a [proxy](/servers/proxy)) or generated from an [OpenAPI spec](/integrations/openapi) are often too generic for direct use by an LLM. You can use transformation to create a simpler, more intuitive version for your specific needs.
|
||||
This is one of the most common reasons to use tool transformation. Tools from remote MCP servers (via a [proxy](/servers/proxy)) or generated from an [OpenAPI spec](/integrations/openapi) are often too generic for direct use by an LLM. You can use transformation to create a simpler, more intuitive version for your specific needs.
|
||||
|
||||
### Chaining Transformations
|
||||
You can chain transformations by using an already transformed tool as the parent for a new transformation. This lets you build up complex behaviors in layers, for example, first renaming arguments, and then adding validation logic to the renamed tool.
|
||||
|
|
|
|||
|
|
@ -250,6 +250,7 @@
|
|||
"requirements": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
|
|
@ -267,6 +268,7 @@
|
|||
"project": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
|
|
@ -285,6 +287,7 @@
|
|||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
|
|
|
|||
|
|
@ -250,6 +250,7 @@
|
|||
"requirements": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
|
|
@ -267,6 +268,7 @@
|
|||
"project": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
|
|
@ -285,6 +287,7 @@
|
|||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"format": "path",
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -374,7 +374,7 @@ containing the prompt messages and any additional metadata.
|
|||
#### `complete_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L744" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
complete_mcp(self, ref: mcp.types.ResourceTemplateReference | mcp.types.PromptReference, argument: dict[str, str]) -> mcp.types.CompleteResult
|
||||
complete_mcp(self, ref: mcp.types.ResourceTemplateReference | mcp.types.PromptReference, argument: dict[str, str], context_arguments: dict[str, Any] | None = None) -> mcp.types.CompleteResult
|
||||
```
|
||||
|
||||
Send a completion request and return the complete MCP protocol result.
|
||||
|
|
@ -382,6 +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.
|
||||
|
||||
**Returns:**
|
||||
- mcp.types.CompleteResult: The complete response object from the protocol,
|
||||
|
|
@ -391,10 +393,10 @@ containing the completion and any additional metadata.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `complete` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L767" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `complete` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L772" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
complete(self, ref: mcp.types.ResourceTemplateReference | mcp.types.PromptReference, argument: dict[str, str]) -> mcp.types.Completion
|
||||
complete(self, ref: mcp.types.ResourceTemplateReference | mcp.types.PromptReference, argument: dict[str, str], context_arguments: dict[str, Any] | None = None) -> mcp.types.Completion
|
||||
```
|
||||
|
||||
Send a completion request to the server.
|
||||
|
|
@ -402,6 +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.
|
||||
|
||||
**Returns:**
|
||||
- mcp.types.Completion: The completion object.
|
||||
|
|
@ -410,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
|
||||
|
|
@ -426,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]
|
||||
|
|
@ -441,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
|
||||
|
|
@ -466,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
|
||||
|
|
@ -496,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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ This example configures token validation against a JWT issuer. The `JWTVerifier`
|
|||
|
||||
### RemoteAuthProvider
|
||||
|
||||
`RemoteAuthProvider` enables authentication with identity providers that **support Dynamic Client Registration (DCR)**, such as WorkOS AuthKit. With DCR, MCP clients can automatically register themselves with the identity provider and obtain credentials without any manual configuration.
|
||||
`RemoteAuthProvider` enables authentication with identity providers that **support Dynamic Client Registration (DCR)**, such as Descope and WorkOS AuthKit. With DCR, MCP clients can automatically register themselves with the identity provider and obtain credentials without any manual configuration.
|
||||
|
||||
This class combines token validation with OAuth discovery metadata. It extends `TokenVerifier` functionality by adding OAuth 2.0 protected resource endpoints that advertise your authentication requirements. MCP clients examine these endpoints to understand which identity providers you trust and how to obtain valid tokens.
|
||||
|
||||
|
|
@ -127,7 +127,7 @@ This example uses WorkOS AuthKit as the external identity provider. The `AuthKit
|
|||
|
||||
<VersionBadge version="2.12.0" />
|
||||
|
||||
`OAuthProxy` enables authentication with OAuth providers that **don't support Dynamic Client Registration (DCR)**, such as GitHub, Google, Azure, and most traditional enterprise identity systems.
|
||||
`OAuthProxy` enables authentication with OAuth providers that **don't support Dynamic Client Registration (DCR)**, such as GitHub, Google, Azure, AWS, and most traditional enterprise identity systems.
|
||||
|
||||
When identity providers require manual app registration and fixed credentials, `OAuthProxy` bridges the gap. It presents a DCR-compliant interface to MCP clients (accepting any registration request) while using your pre-registered credentials with the upstream provider. The proxy handles the complexity of callback forwarding, enabling dynamic client callbacks to work with providers that require fixed redirect URIs.
|
||||
|
||||
|
|
@ -256,9 +256,9 @@ This approach simplifies deployment pipelines and follows twelve-factor app prin
|
|||
|
||||
The authentication approach you choose depends on your existing infrastructure, security requirements, and operational constraints.
|
||||
|
||||
**For OAuth providers without DCR support (GitHub, Google, Azure, most enterprise systems), use OAuth Proxy.** These providers require manual app registration through their developer consoles. OAuth Proxy bridges the gap by presenting a DCR-compliant interface to MCP clients while using your fixed credentials with the provider. The proxy's callback forwarding pattern enables dynamic client ports to work with providers that require fixed redirect URIs.
|
||||
**For OAuth providers without DCR support (GitHub, Google, Azure, AWS, most enterprise systems), use OAuth Proxy.** These providers require manual app registration through their developer consoles. OAuth Proxy bridges the gap by presenting a DCR-compliant interface to MCP clients while using your fixed credentials with the provider. The proxy's callback forwarding pattern enables dynamic client ports to work with providers that require fixed redirect URIs.
|
||||
|
||||
**For identity providers with DCR support (WorkOS AuthKit, modern auth platforms), use RemoteAuthProvider.** These providers allow clients to dynamically register and obtain credentials without manual configuration. This enables the fully automated authentication flow that MCP is designed for, providing the best user experience and simplest implementation.
|
||||
**For identity providers with DCR support (Descope, WorkOS AuthKit, modern auth platforms), use RemoteAuthProvider.** These providers allow clients to dynamically register and obtain credentials without manual configuration. This enables the fully automated authentication flow that MCP is designed for, providing the best user experience and simplest implementation.
|
||||
|
||||
**Token validation works well when you already have authentication infrastructure that issues structured tokens.** If your organization already uses JWT-based systems, API gateways, or enterprise SSO that can generate tokens, this approach integrates seamlessly while keeping your MCP server focused on its core functionality. The simplicity comes from leveraging existing investment in authentication infrastructure.
|
||||
|
||||
|
|
|
|||
|
|
@ -10,12 +10,20 @@ import { VersionBadge } from "/snippets/version-badge.mdx";
|
|||
|
||||
<VersionBadge version="2.12.0" />
|
||||
|
||||
OAuth Proxy enables FastMCP servers to authenticate with OAuth providers that **don't support Dynamic Client Registration (DCR)**. This includes virtually all traditional OAuth providers: GitHub, Google, Azure, Discord, Facebook, and most enterprise identity systems. For providers that do support DCR (like WorkOS AuthKit), use [`RemoteAuthProvider`](/servers/auth/remote-oauth) instead.
|
||||
OAuth Proxy enables FastMCP servers to authenticate with OAuth providers that **don't support Dynamic Client Registration (DCR)**. This includes virtually all traditional OAuth providers: GitHub, Google, Azure, AWS, Discord, Facebook, and most enterprise identity systems. For providers that do support DCR (like Descope and WorkOS AuthKit), use [`RemoteAuthProvider`](/servers/auth/remote-oauth) instead.
|
||||
|
||||
MCP clients expect to register automatically and obtain credentials on the fly, but traditional providers require manual app registration through their developer consoles. OAuth Proxy bridges this gap by presenting a DCR-compliant interface to MCP clients while using your pre-registered credentials with the upstream provider. When a client attempts to register, the proxy returns your fixed credentials. When a client initiates authorization, the proxy handles the complexity of callback forwarding—storing the client's dynamic callback URL, using its own fixed callback with the provider, then forwarding back to the client after token exchange.
|
||||
|
||||
This approach enables any MCP client (whether using random localhost ports or fixed URLs like Claude.ai) to authenticate with any traditional OAuth provider, all while maintaining full OAuth 2.1 and PKCE security.
|
||||
|
||||
<Note>
|
||||
For providers that support OIDC discovery (Auth0, Google with OIDC
|
||||
configuration, Azure AD), consider using [`OIDC
|
||||
Proxy`](/servers/auth/oidc-proxy) for automatic configuration. OIDC Proxy
|
||||
extends OAuth Proxy to automatically discover endpoints from the provider's
|
||||
`/.well-known/openid-configuration` URL, simplifying setup.
|
||||
</Note>
|
||||
|
||||
## Implementation
|
||||
|
||||
### Provider Setup Requirements
|
||||
|
|
@ -24,7 +32,7 @@ Before using OAuth Proxy, you need to register your application with your OAuth
|
|||
|
||||
1. **Register your application** in the provider's developer console (GitHub Settings, Google Cloud Console, Azure Portal, etc.)
|
||||
2. **Configure the redirect URI** as your FastMCP server URL plus your chosen callback path:
|
||||
- Default: `https://your-server.com/auth/callback`
|
||||
- Default: `https://your-server.com/auth/callback`
|
||||
- Custom: `https://your-server.com/your/custom/path` (if you set `redirect_path`)
|
||||
- Development: `http://localhost:8000/auth/callback`
|
||||
3. **Obtain your credentials**: Client ID and Client Secret
|
||||
|
|
@ -68,7 +76,7 @@ auth = OAuthProxy(
|
|||
|
||||
# Your FastMCP server's public URL
|
||||
base_url="https://your-server.com",
|
||||
|
||||
|
||||
# Optional: customize the callback path (default is "/auth/callback")
|
||||
# redirect_path="/custom/callback",
|
||||
)
|
||||
|
|
@ -84,7 +92,8 @@ mcp = FastMCP(name="My Server", auth=auth)
|
|||
</ParamField>
|
||||
|
||||
<ParamField body="upstream_token_endpoint" type="str" required>
|
||||
URL of your OAuth provider's token endpoint (e.g., `https://github.com/login/oauth/access_token`)
|
||||
URL of your OAuth provider's token endpoint (e.g.,
|
||||
`https://github.com/login/oauth/access_token`)
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="upstream_client_id" type="str" required>
|
||||
|
|
@ -96,7 +105,8 @@ mcp = FastMCP(name="My Server", auth=auth)
|
|||
</ParamField>
|
||||
|
||||
<ParamField body="token_verifier" type="TokenVerifier" required>
|
||||
A [`TokenVerifier`](/servers/auth/token-verification) instance to validate the provider's tokens
|
||||
A [`TokenVerifier`](/servers/auth/token-verification) instance to validate the
|
||||
provider's tokens
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="base_url" type="AnyHttpUrl | str" required>
|
||||
|
|
@ -104,7 +114,8 @@ mcp = FastMCP(name="My Server", auth=auth)
|
|||
</ParamField>
|
||||
|
||||
<ParamField body="redirect_path" type="str" default="/auth/callback">
|
||||
Path for OAuth callbacks. Must match the redirect URI configured in your OAuth application
|
||||
Path for OAuth callbacks. Must match the redirect URI configured in your OAuth
|
||||
application
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="upstream_revocation_endpoint" type="str | None">
|
||||
|
|
@ -120,32 +131,39 @@ mcp = FastMCP(name="My Server", auth=auth)
|
|||
</ParamField>
|
||||
|
||||
<ParamField body="forward_pkce" type="bool" default="True">
|
||||
Whether to forward PKCE (Proof Key for Code Exchange) to the upstream OAuth provider. When enabled and the client uses PKCE, the proxy generates its own PKCE parameters to send upstream while separately validating the client's PKCE. This ensures end-to-end PKCE security at both layers (client-to-proxy and proxy-to-upstream).
|
||||
- `True` (default): Forward PKCE for providers that support it (Google, Azure, GitHub, etc.)
|
||||
- `False`: Disable only if upstream provider doesn't support PKCE
|
||||
Whether to forward PKCE (Proof Key for Code Exchange) to the upstream OAuth
|
||||
provider. When enabled and the client uses PKCE, the proxy generates its own
|
||||
PKCE parameters to send upstream while separately validating the client's
|
||||
PKCE. This ensures end-to-end PKCE security at both layers (client-to-proxy
|
||||
and proxy-to-upstream). - `True` (default): Forward PKCE for providers that
|
||||
support it (Google, Azure, AWS, GitHub, etc.) - `False`: Disable only if upstream
|
||||
provider doesn't support PKCE
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="token_endpoint_auth_method" type="str | None">
|
||||
Token endpoint authentication method for the upstream OAuth server. Controls how the proxy authenticates when exchanging authorization codes and refresh tokens with the upstream provider.
|
||||
- `"client_secret_basic"`: Send credentials in Authorization header (most common)
|
||||
- `"client_secret_post"`: Send credentials in request body (required by some providers)
|
||||
- `"none"`: No authentication (for public clients)
|
||||
- `None` (default): Uses authlib's default (typically `"client_secret_basic"`)
|
||||
|
||||
Set this if your provider requires a specific authentication method and the default doesn't work.
|
||||
Token endpoint authentication method for the upstream OAuth server. Controls
|
||||
how the proxy authenticates when exchanging authorization codes and refresh
|
||||
tokens with the upstream provider. - `"client_secret_basic"`: Send credentials
|
||||
in Authorization header (most common) - `"client_secret_post"`: Send
|
||||
credentials in request body (required by some providers) - `"none"`: No
|
||||
authentication (for public clients) - `None` (default): Uses authlib's default
|
||||
(typically `"client_secret_basic"`) Set this if your provider requires a
|
||||
specific authentication method and the default doesn't work.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="allowed_client_redirect_uris" type="list[str] | None">
|
||||
List of allowed redirect URI patterns for MCP clients. Patterns support wildcards (e.g., `"http://localhost:*"`, `"https://*.example.com/*"`).
|
||||
- `None` (default): All redirect URIs allowed (for MCP/DCR compatibility)
|
||||
- Empty list `[]`: No redirect URIs allowed
|
||||
- Custom list: Only matching patterns allowed
|
||||
|
||||
These patterns apply to MCP client loopback redirects, NOT the upstream OAuth app redirect URI.
|
||||
List of allowed redirect URI patterns for MCP clients. Patterns support
|
||||
wildcards (e.g., `"http://localhost:*"`, `"https://*.example.com/*"`). -
|
||||
`None` (default): All redirect URIs allowed (for MCP/DCR compatibility) -
|
||||
Empty list `[]`: No redirect URIs allowed - Custom list: Only matching
|
||||
patterns allowed These patterns apply to MCP client loopback redirects, NOT
|
||||
the upstream OAuth app redirect URI.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="valid_scopes" type="list[str] | None">
|
||||
List of all possible valid scopes for the OAuth provider. These are advertised to clients through the `/.well-known` endpoints. Defaults to `required_scopes` from your TokenVerifier if not specified.
|
||||
List of all possible valid scopes for the OAuth provider. These are advertised
|
||||
to clients through the `/.well-known` endpoints. Defaults to `required_scopes`
|
||||
from your TokenVerifier if not specified.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="extra_authorize_params" type="dict[str, str] | None">
|
||||
|
|
@ -161,13 +179,27 @@ mcp = FastMCP(name="My Server", auth=auth)
|
|||
|
||||
<ParamField body="extra_token_params" type="dict[str, str] | None">
|
||||
Additional parameters to forward to the upstream token endpoint during code exchange and token refresh. Useful for provider-specific requirements during token operations.
|
||||
|
||||
For example, some providers require additional context during token exchange:
|
||||
```python
|
||||
extra_token_params={"audience": "https://api.example.com"}
|
||||
```
|
||||
|
||||
These parameters are included in all token requests to the upstream provider.
|
||||
|
||||
For example, some providers require additional context during token exchange:
|
||||
|
||||
```python
|
||||
extra_token_params={"audience": "https://api.example.com"}
|
||||
```
|
||||
|
||||
These parameters are included in all token requests to the upstream provider.
|
||||
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="client_storage" type="KVStorage | None">
|
||||
Storage backend for persisting OAuth client registrations. By default, clients are automatically persisted to disk in `~/.config/fastmcp/oauth-proxy-clients/`, allowing them to survive server restarts as long as the filesystem remains accessible. This means MCP clients only need to register once and can reconnect seamlessly after your server restarts.
|
||||
|
||||
```python
|
||||
from fastmcp.utilities.storage import InMemoryStorage
|
||||
|
||||
# Use in-memory storage for testing (clients lost on restart)
|
||||
auth = OAuthProxy(..., client_storage=InMemoryStorage())
|
||||
```
|
||||
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
|
|
@ -185,21 +217,21 @@ auth = OAuthProxy(
|
|||
upstream_token_endpoint="https://your-domain.auth0.com/oauth/token",
|
||||
upstream_client_id="your-auth0-client-id",
|
||||
upstream_client_secret="your-auth0-client-secret",
|
||||
|
||||
|
||||
# Auth0 requires audience for JWT tokens
|
||||
extra_authorize_params={
|
||||
"audience": "https://your-api-identifier.com"
|
||||
},
|
||||
extra_token_params={
|
||||
"audience": "https://your-api-identifier.com"
|
||||
"audience": "https://your-api-identifier.com"
|
||||
},
|
||||
|
||||
|
||||
token_verifier=JWTVerifier(
|
||||
jwks_uri="https://your-domain.auth0.com/.well-known/jwks.json",
|
||||
issuer="https://your-domain.auth0.com/",
|
||||
audience="https://your-api-identifier.com"
|
||||
),
|
||||
|
||||
|
||||
base_url="https://your-server.com"
|
||||
)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { VersionBadge } from "/snippets/version-badge.mdx";
|
|||
|
||||
<VersionBadge version="2.12.4" />
|
||||
|
||||
OIDC Proxy enables FastMCP servers to authenticate with OIDC providers that **don't support Dynamic Client Registration (DCR)** out of the box. This includes OAuth providers like: Auth0, Google, Azure, etc. For providers that do support DCR (like WorkOS AuthKit), use [`RemoteAuthProvider`](/servers/auth/remote-oauth) instead.
|
||||
OIDC Proxy enables FastMCP servers to authenticate with OIDC providers that **don't support Dynamic Client Registration (DCR)** out of the box. This includes OAuth providers like: Auth0, Google, Azure, AWS, etc. For providers that do support DCR (like WorkOS AuthKit), use [`RemoteAuthProvider`](/servers/auth/remote-oauth) instead.
|
||||
|
||||
The OIDC Proxy is built upon [`OAuthProxy`](/servers/auth/oauth-proxy) so it has all the same functionality under the covers.
|
||||
|
||||
|
|
@ -79,28 +79,33 @@ mcp = FastMCP(name="My Server", auth=auth)
|
|||
Public URL of your FastMCP server (e.g., `https://your-server.com`)
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="strict" type="str">
|
||||
Strict flag for configuration validation
|
||||
<ParamField body="strict" type="bool | None">
|
||||
Strict flag for configuration validation. When True, requires all OIDC
|
||||
mandatory fields.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="audience" type="str">
|
||||
Audience from your registered OAuth application
|
||||
<ParamField body="audience" type="str | None">
|
||||
Audience parameter for OIDC providers that require it (e.g., Auth0). This is
|
||||
typically your API identifier.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="timeout_seconds" type="str">
|
||||
HTTP request timeout in seconds
|
||||
<ParamField body="timeout_seconds" type="int | None" default="10">
|
||||
HTTP request timeout in seconds for fetching OIDC configuration
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="algorithm" type="str">
|
||||
The algorithm for the token verifier
|
||||
<ParamField body="algorithm" type="str | None">
|
||||
JWT algorithm to use for token verification (e.g., "RS256"). If not specified,
|
||||
uses the provider's default.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="required_scopes" type="str">
|
||||
The required scopes for the token verifier
|
||||
<ParamField body="required_scopes" type="list[str] | None">
|
||||
List of OAuth scopes to request from the provider. These are automatically
|
||||
included in authorization requests.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="redirect_path" type="str" default="/auth/callback">
|
||||
Path for OAuth callbacks. Must match the redirect URI configured in your OAuth application
|
||||
Path for OAuth callbacks. Must match the redirect URI configured in your OAuth
|
||||
application
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="allowed_client_redirect_uris" type="list[str] | None">
|
||||
|
|
@ -109,7 +114,8 @@ mcp = FastMCP(name="My Server", auth=auth)
|
|||
- Empty list `[]`: No redirect URIs allowed
|
||||
- Custom list: Only matching patterns allowed
|
||||
|
||||
These patterns apply to MCP client loopback redirects, NOT the upstream OAuth app redirect URI.
|
||||
These patterns apply to MCP client loopback redirects, NOT the upstream OAuth app redirect URI.
|
||||
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="token_endpoint_auth_method" type="str | None">
|
||||
|
|
@ -119,7 +125,20 @@ mcp = FastMCP(name="My Server", auth=auth)
|
|||
- `"none"`: No authentication (for public clients)
|
||||
- `None` (default): Uses authlib's default (typically `"client_secret_basic"`)
|
||||
|
||||
Set this if your provider requires a specific authentication method and the default doesn't work.
|
||||
Set this if your provider requires a specific authentication method and the default doesn't work.
|
||||
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="client_storage" type="KVStorage | None">
|
||||
Storage backend for persisting OAuth client registrations. By default, clients are automatically persisted to disk in `~/.config/fastmcp/oidc-proxy-clients/`, allowing them to survive server restarts as long as the filesystem remains accessible. This means MCP clients only need to register once and can reconnect seamlessly after your server restarts.
|
||||
|
||||
```python
|
||||
from fastmcp.utilities.storage import InMemoryStorage
|
||||
|
||||
# Use in-memory storage for testing (clients lost on restart)
|
||||
auth = OIDCProxy(..., client_storage=InMemoryStorage())
|
||||
```
|
||||
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
title: Remote OAuth
|
||||
sidebarTitle: Remote OAuth
|
||||
description: Integrate your FastMCP server with external identity providers like WorkOS, Auth0, and corporate SSO systems.
|
||||
description: Integrate your FastMCP server with external identity providers like Descope, WorkOS, Auth0, and corporate SSO systems.
|
||||
icon: camera-cctv
|
||||
tag: NEW
|
||||
---
|
||||
|
|
@ -14,8 +14,8 @@ Remote OAuth integration allows your FastMCP server to leverage external identit
|
|||
|
||||
<Tip>
|
||||
**When to use RemoteAuthProvider vs OAuth Proxy:**
|
||||
- **RemoteAuthProvider**: For providers WITH Dynamic Client Registration (WorkOS AuthKit, modern OIDC providers)
|
||||
- **OAuth Proxy**: For providers WITHOUT Dynamic Client Registration (GitHub, Google, Azure, Discord, etc.)
|
||||
- **RemoteAuthProvider**: For providers WITH Dynamic Client Registration (Descope, WorkOS AuthKit, modern OIDC providers)
|
||||
- **OAuth Proxy**: For providers WITHOUT Dynamic Client Registration (GitHub, Google, Azure, AWS, Discord, etc.)
|
||||
|
||||
RemoteAuthProvider requires DCR support for fully automated client registration and authentication.
|
||||
</Tip>
|
||||
|
|
@ -29,7 +29,7 @@ RemoteAuthProvider works with identity providers that support **Dynamic Client R
|
|||
| **Client Registration** | Automatic via API | Manual in provider console |
|
||||
| **Credentials** | Dynamic per client | Fixed app credentials |
|
||||
| **Configuration** | Zero client config | Pre-shared credentials |
|
||||
| **Examples** | WorkOS AuthKit, modern OIDC | GitHub, Google, Azure |
|
||||
| **Examples** | Descope, WorkOS AuthKit, modern OIDC | GitHub, Google, Azure |
|
||||
| **FastMCP Class** | `RemoteAuthProvider` | [`OAuthProxy`](/servers/auth/oauth-proxy) |
|
||||
|
||||
If your provider doesn't support DCR (most traditional OAuth providers), you'll need to use [`OAuth Proxy`](/servers/auth/oauth-proxy) instead, which bridges the gap between MCP's DCR expectations and fixed OAuth credentials.
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ FastMCP supports [MCP proxying](/servers/proxy), which allows you to mirror a lo
|
|||
|
||||
You can also create proxies from configuration dictionaries that follow the MCPConfig schema, which is useful for quickly connecting to one or more remote servers. See the [Proxy Servers documentation](/servers/proxy#configuration-based-proxies) for details on configuration-based proxying. Note that MCPConfig follows an emerging standard and its format may evolve over time.
|
||||
|
||||
Prefixing rules for tools, prompts, resources, and templates are identical across importing, mounting, and proxies.
|
||||
|
||||
## Importing (Static Composition)
|
||||
|
||||
The `import_server()` method copies all components (tools, resources, templates, prompts) from one `FastMCP` instance (the *subserver*) into another (the *main server*). An optional `prefix` can be provided to avoid naming conflicts. If no prefix is provided, components are imported without modification. When multiple servers are imported with the same prefix (or no prefix), the most recently imported server's components take precedence.
|
||||
|
|
|
|||
|
|
@ -89,10 +89,12 @@ Note that the MCP SDK may perform additional operations like listing tools for c
|
|||
This hierarchy allows you to target your middleware logic with the right level of specificity. Use `on_message` for broad concerns like logging, `on_request` for authentication, and `on_call_tool` for tool-specific logic like performance monitoring.
|
||||
|
||||
### Available Hooks
|
||||
<VersionBadge version="2.9.0" />
|
||||
|
||||
- `on_message`: Called for all MCP messages (requests and notifications)
|
||||
- `on_request`: Called specifically for MCP requests (that expect responses)
|
||||
- `on_notification`: Called specifically for MCP notifications (fire-and-forget)
|
||||
|
||||
- `on_call_tool`: Called when tools are being executed
|
||||
- `on_read_resource`: Called when resources are being read
|
||||
- `on_get_prompt`: Called when prompts are being retrieved
|
||||
|
|
@ -100,6 +102,11 @@ This hierarchy allows you to target your middleware logic with the right level o
|
|||
- `on_list_resources`: Called when listing available resources
|
||||
- `on_list_resource_templates`: Called when listing resource templates
|
||||
- `on_list_prompts`: Called when listing available prompts
|
||||
<VersionBadge version="2.13.0" />
|
||||
- `on_initialize`: Called when a client connects and initializes the session (returns `None`)
|
||||
<Note>
|
||||
The `on_initialize` hook receives the client's initialization request but **returns `None`** rather than a result. The initialization response is handled internally by the MCP protocol and cannot be modified by middleware. This hook is useful for client detection, logging connections, or initializing session state, but not for modifying the initialization handshake itself.
|
||||
</Note>
|
||||
|
||||
## Component Access in Middleware
|
||||
|
||||
|
|
|
|||
|
|
@ -245,11 +245,29 @@ config = {
|
|||
# Create a unified proxy to multiple servers
|
||||
composite_proxy = FastMCP.as_proxy(config, name="Composite Proxy")
|
||||
|
||||
# Tools and resources are accessible with prefixes:
|
||||
# - weather_get_forecast, calendar_add_event
|
||||
# - weather://weather/icons/sunny, calendar://calendar/events/today
|
||||
# Tools, resources, prompts, and templates are accessible with prefixes:
|
||||
# - Tools: weather_get_forecast, calendar_add_event
|
||||
# - Prompts: weather_daily_summary, calendar_quick_add
|
||||
# - Resources: weather://weather/icons/sunny, calendar://calendar/events/today
|
||||
# - Templates: weather://weather/locations/{id}, calendar://calendar/events/{date}
|
||||
```
|
||||
|
||||
## Component Prefixing
|
||||
|
||||
When proxying one or more servers, component names are prefixed the same way as with mounting and importing:
|
||||
|
||||
- Tools: `{prefix}_{tool_name}`
|
||||
- Prompts: `{prefix}_{prompt_name}`
|
||||
- Resources: `protocol://{prefix}/path/to/resource` (default path format)
|
||||
- Resource templates: `protocol://{prefix}/...` and template names are also prefixed
|
||||
|
||||
These rules apply uniformly whether you:
|
||||
- Mount a proxy on another server
|
||||
- Create a multi-server proxy from an `MCPConfig`
|
||||
- Use `FastMCP.as_proxy()` directly
|
||||
|
||||
For resource URI prefix formats (path vs legacy protocol style) and configuration options, see Server Composition → Resource Prefix Formats.
|
||||
|
||||
## Mirrored Components
|
||||
|
||||
<VersionBadge version="2.10.5" />
|
||||
|
|
@ -332,4 +350,3 @@ def custom_client_factory():
|
|||
|
||||
proxy = FastMCPProxy(client_factory=custom_client_factory)
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -412,15 +412,15 @@ With these two templates defined, clients can request a variety of resources:
|
|||
- `repos://jlowin/fastmcp/info` → Returns info about the jlowin/fastmcp repository
|
||||
- `repos://prefecthq/prefect/info` → Returns info about the prefecthq/prefect repository
|
||||
|
||||
### Wildcard Parameters
|
||||
### RFC 6570 URI Templates
|
||||
|
||||
|
||||
FastMCP implements [RFC 6570 URI Templates](https://datatracker.ietf.org/doc/html/rfc6570) for resource templates, providing a standardized way to define parameterized URIs. This includes support for simple expansion, wildcard path parameters, and form-style query parameters.
|
||||
|
||||
#### Wildcard Parameters
|
||||
|
||||
<VersionBadge version="2.2.4" />
|
||||
|
||||
<Tip>
|
||||
Please note: FastMCP's support for wildcard parameters is an **extension** of the Model Context Protocol standard, which otherwise follows RFC 6570. Since all template processing happens in the FastMCP server, this should not cause any compatibility issues with other MCP implementations.
|
||||
</Tip>
|
||||
|
||||
|
||||
Resource templates support wildcard parameters that can match multiple path segments. While standard parameters (`{param}`) only match a single path segment and don't cross "/" boundaries, wildcard parameters (`{param*}`) can capture multiple segments including slashes. Wildcards capture all subsequent path segments *up until* the defined part of the URI template (whether literal or another parameter). This allows you to have multiple wildcard parameters in a single URI template.
|
||||
|
||||
```python {15, 23}
|
||||
|
|
@ -448,7 +448,7 @@ def get_path_content(filepath: str) -> str:
|
|||
# Mixing standard and wildcard parameters
|
||||
@mcp.resource("repo://{owner}/{path*}/template.py")
|
||||
def get_template_file(owner: str, path: str) -> dict:
|
||||
"""Retrieves a file from a specific repository and path, but
|
||||
"""Retrieves a file from a specific repository and path, but
|
||||
only if the resource ends with `template.py`"""
|
||||
# Can match repo://jlowin/fastmcp/src/resources/template.py
|
||||
return {
|
||||
|
|
@ -466,43 +466,88 @@ Wildcard parameters are useful when:
|
|||
|
||||
Note that like regular parameters, each wildcard parameter must still be a named parameter in your function signature, and all required function parameters must appear in the URI template.
|
||||
|
||||
### Default Values
|
||||
#### Query Parameters
|
||||
|
||||
<VersionBadge version="2.2.0" />
|
||||
<VersionBadge version="2.13.0" />
|
||||
|
||||
When creating resource templates, FastMCP enforces two rules for the relationship between URI template parameters and function parameters:
|
||||
FastMCP supports RFC 6570 form-style query parameters using the `{?param1,param2}` syntax. Query parameters provide a clean way to pass optional configuration to resources without cluttering the path.
|
||||
|
||||
1. **Required Function Parameters:** All function parameters without default values (required parameters) must appear in the URI template.
|
||||
2. **URI Parameters:** All URI template parameters must exist as function parameters.
|
||||
|
||||
However, function parameters with default values don't need to be included in the URI template. When a client requests a resource, FastMCP will:
|
||||
|
||||
- Extract parameter values from the URI for parameters included in the template
|
||||
- Use default values for any function parameters not in the URI template
|
||||
|
||||
This allows for flexible API designs. For example, a simple search template with optional parameters:
|
||||
Query parameters must be optional function parameters (have default values), while path parameters map to required function parameters. This enforces a clear separation: required data goes in the path, optional configuration in query params.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP(name="DataServer")
|
||||
|
||||
@mcp.resource("search://{query}")
|
||||
def search_resources(query: str, max_results: int = 10, include_archived: bool = False) -> dict:
|
||||
"""Search for resources matching the query string."""
|
||||
# Only 'query' is required in the URI, the other parameters use their defaults
|
||||
results = perform_search(query, limit=max_results, archived=include_archived)
|
||||
# Basic query parameters
|
||||
@mcp.resource("data://{id}{?format}")
|
||||
def get_data(id: str, format: str = "json") -> str:
|
||||
"""Retrieve data in specified format."""
|
||||
if format == "xml":
|
||||
return f"<data id='{id}' />"
|
||||
return f'{{"id": "{id}"}}'
|
||||
|
||||
# Multiple query parameters with type coercion
|
||||
@mcp.resource("api://{endpoint}{?version,limit,offset}")
|
||||
def call_api(endpoint: str, version: int = 1, limit: int = 10, offset: int = 0) -> dict:
|
||||
"""Call API endpoint with pagination."""
|
||||
return {
|
||||
"query": query,
|
||||
"max_results": max_results,
|
||||
"include_archived": include_archived,
|
||||
"results": results
|
||||
"endpoint": endpoint,
|
||||
"version": version,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
"results": fetch_results(endpoint, version, limit, offset)
|
||||
}
|
||||
|
||||
# Query parameters with wildcards
|
||||
@mcp.resource("files://{path*}{?encoding,lines}")
|
||||
def read_file(path: str, encoding: str = "utf-8", lines: int = 100) -> str:
|
||||
"""Read file with optional encoding and line limit."""
|
||||
return read_file_content(path, encoding, lines)
|
||||
```
|
||||
|
||||
With this template, clients can request `search://python` and the function will be called with `query="python", max_results=10, include_archived=False`. MCP Developers can still call the underlying `search_resources` function directly with more specific parameters.
|
||||
**Example requests:**
|
||||
- `data://123` → Uses default format `"json"`
|
||||
- `data://123?format=xml` → Uses format `"xml"`
|
||||
- `api://users?version=2&limit=50` → `version=2, limit=50, offset=0`
|
||||
- `files://src/main.py?encoding=ascii&lines=50` → Custom encoding and line limit
|
||||
|
||||
You can also create multiple resource templates that provide different ways to access the same underlying data by manually applying decorators to a single function:
|
||||
FastMCP automatically coerces query parameter string values to the correct types based on your function's type hints (`int`, `float`, `bool`, `str`).
|
||||
|
||||
**Query parameters vs. hidden defaults:**
|
||||
|
||||
Query parameters expose optional configuration to clients. To hide optional parameters from clients entirely (always use defaults), simply omit them from the URI template:
|
||||
|
||||
```python
|
||||
# Clients CAN override max_results via query string
|
||||
@mcp.resource("search://{query}{?max_results}")
|
||||
def search_configurable(query: str, max_results: int = 10) -> dict:
|
||||
return {"query": query, "limit": max_results}
|
||||
|
||||
# Clients CANNOT override max_results (not in URI template)
|
||||
@mcp.resource("search://{query}")
|
||||
def search_fixed(query: str, max_results: int = 10) -> dict:
|
||||
return {"query": query, "limit": max_results}
|
||||
```
|
||||
|
||||
### Template Parameter Rules
|
||||
|
||||
<VersionBadge version="2.2.0" />
|
||||
|
||||
FastMCP enforces these validation rules when creating resource templates:
|
||||
|
||||
1. **Required function parameters** (no default values) must appear in the URI path template
|
||||
2. **Query parameters** (specified with `{?param}` syntax) must be optional function parameters with default values
|
||||
3. **All URI template parameters** (path and query) must exist as function parameters
|
||||
|
||||
Optional function parameters (those with default values) can be:
|
||||
- Included as query parameters (`{?param}`) - clients can override via query string
|
||||
- Omitted from URI template - always uses default value, not exposed to clients
|
||||
- Used in alternative path templates - enables multiple ways to access the same resource
|
||||
|
||||
**Multiple templates for one function:**
|
||||
|
||||
Create multiple resource templates that expose the same function through different URI patterns by manually applying decorators:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
|
|
|
|||
|
|
@ -344,6 +344,7 @@ Common global settings include:
|
|||
- **`mask_error_details`**: Whether to hide detailed error information from clients, set with `FASTMCP_MASK_ERROR_DETAILS`
|
||||
- **`resource_prefix_format`**: How to format resource prefixes ("path" or "protocol"), set with `FASTMCP_RESOURCE_PREFIX_FORMAT`
|
||||
- **`include_fastmcp_meta`**: Whether to include FastMCP metadata in component responses (default: True), set with `FASTMCP_INCLUDE_FASTMCP_META`
|
||||
- **`env_file`**: Path to the environment file to load settings from (default: ".env"), set with `FASTMCP_ENV_FILE`. Useful when your project uses a `.env` file with syntax incompatible with python-dotenv
|
||||
|
||||
### Transport-Specific Configuration
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,53 @@ icon: "sparkles"
|
|||
tag: NEW
|
||||
---
|
||||
|
||||
<Update label="FastMCP 2.12" description="December 31, 2024" tags={["Releases"]}>
|
||||
<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"
|
||||
href="https://github.com/jlowin/fastmcp/releases/tag/v2.12.0"
|
||||
|
|
@ -21,7 +67,7 @@ FastMCP 2.12 represents one of our most significant releases to date. After exte
|
|||
</Card>
|
||||
</Update>
|
||||
|
||||
<Update label="FastMCP 2.11" description="December 13, 2024" tags={["Releases"]}>
|
||||
<Update label="FastMCP 2.11" description="August 1, 2025" tags={["Releases"]}>
|
||||
<Card
|
||||
title="FastMCP 2.11: Auth to a Good Start"
|
||||
href="https://github.com/jlowin/fastmcp/releases/tag/v2.11.0"
|
||||
|
|
@ -39,7 +85,7 @@ This release emphasizes speed and simplicity while setting the foundation for fu
|
|||
</Card>
|
||||
</Update>
|
||||
|
||||
<Update label="FastMCP 2.10" description="November 21, 2024" tags={["Releases"]}>
|
||||
<Update label="FastMCP 2.10" description="July 2, 2025" tags={["Releases"]}>
|
||||
<Card
|
||||
title="FastMCP 2.10: Great Spec-tations"
|
||||
href="https://github.com/jlowin/fastmcp/releases/tag/v2.10.0"
|
||||
|
|
|
|||
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
|
|
@ -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
|
|
@ -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)
|
||||
47
examples/auth/aws_oauth/README.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# AWS Cognito OAuth Example
|
||||
|
||||
Demonstrates FastMCP server protection with AWS Cognito OAuth.
|
||||
|
||||
## Setup
|
||||
|
||||
1. Create an AWS Cognito User Pool and App Client:
|
||||
- Go to [AWS Cognito Console](https://console.aws.amazon.com/cognito/)
|
||||
- Create a new User Pool or use an existing one
|
||||
- Create an App Client in your User Pool
|
||||
- Configure the App Client settings:
|
||||
- Enable "Authorization code grant" flow
|
||||
- Add Callback URL: `http://localhost:8000/auth/callback`
|
||||
- Configure OAuth scopes (at minimum: `openid`)
|
||||
- Note your User Pool ID, App Client ID, Client Secret, and Cognito Domain Prefix
|
||||
|
||||
2. Set environment variables:
|
||||
|
||||
```bash
|
||||
export FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID="your-user-pool-id"
|
||||
export FASTMCP_SERVER_AUTH_AWS_COGNITO_AWS_REGION="your-aws-region"
|
||||
export FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID="your-app-client-id"
|
||||
export FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET="your-app-client-secret"
|
||||
```
|
||||
|
||||
Or create a `.env` file:
|
||||
|
||||
```env
|
||||
FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID=your-user-pool-id
|
||||
FASTMCP_SERVER_AUTH_AWS_COGNITO_AWS_REGION=your-aws-region
|
||||
FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID=your-app-client-id
|
||||
FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET=your-app-client-secret
|
||||
```
|
||||
|
||||
3. Run the server:
|
||||
|
||||
```bash
|
||||
python server.py
|
||||
```
|
||||
|
||||
4. In another terminal, run the client:
|
||||
|
||||
```bash
|
||||
python client.py
|
||||
```
|
||||
|
||||
The client will open your browser for AWS Cognito authentication.
|
||||
42
examples/auth/aws_oauth/client.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
"""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://localhost: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}")
|
||||
|
||||
# Test the protected tool
|
||||
print("🔒 Calling protected tool: get_access_token_claims")
|
||||
result = await client.call_tool("get_access_token_claims")
|
||||
user_data = result.data
|
||||
print("📄 Available access token claims:")
|
||||
print(f" - sub: {user_data.get('sub', 'N/A')}")
|
||||
print(f" - username: {user_data.get('username', 'N/A')}")
|
||||
print(f" - cognito:groups: {user_data.get('cognito:groups', [])}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Authentication failed: {e}")
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
2
examples/auth/aws_oauth/requirements.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fastmcp
|
||||
python-dotenv
|
||||
59
examples/auth/aws_oauth/server.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
"""AWS Cognito OAuth server example for FastMCP.
|
||||
|
||||
This example demonstrates how to protect a FastMCP server with AWS Cognito.
|
||||
|
||||
Required environment variables:
|
||||
- FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID: Your AWS Cognito User Pool ID
|
||||
- FASTMCP_SERVER_AUTH_AWS_COGNITO_AWS_REGION: Your AWS region (optional, defaults to eu-central-1)
|
||||
- FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID: Your Cognito app client ID
|
||||
- FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET: Your Cognito app client secret
|
||||
|
||||
To run:
|
||||
python server.py
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.aws import AWSCognitoProvider
|
||||
from fastmcp.server.dependencies import get_access_token
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
load_dotenv(".env", override=True)
|
||||
|
||||
auth = AWSCognitoProvider(
|
||||
user_pool_id=os.getenv("FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID") or "",
|
||||
aws_region=os.getenv("FASTMCP_SERVER_AUTH_AWS_COGNITO_AWS_REGION")
|
||||
or "eu-central-1",
|
||||
client_id=os.getenv("FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID") or "",
|
||||
client_secret=os.getenv("FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET") or "",
|
||||
base_url="http://localhost:8000",
|
||||
# redirect_path="/custom/callback"
|
||||
)
|
||||
|
||||
mcp = FastMCP("AWS Cognito OAuth Example Server", auth=auth)
|
||||
|
||||
|
||||
@mcp.tool
|
||||
def echo(message: str) -> str:
|
||||
"""Echo the provided message."""
|
||||
return message
|
||||
|
||||
|
||||
@mcp.tool
|
||||
async def get_access_token_claims() -> dict:
|
||||
"""Get the authenticated user's access token claims."""
|
||||
token = get_access_token()
|
||||
return {
|
||||
"sub": token.claims.get("sub"),
|
||||
"username": token.claims.get("username"),
|
||||
"cognito:groups": token.claims.get("cognito:groups", []),
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="http", port=8000)
|
||||
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
|
|
@ -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
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ async def get_server_details():
|
|||
print(f" - Imported from news app: {news_resources}")
|
||||
|
||||
# Let's try to access resources using the prefixed URI
|
||||
weather_data = await app._mcp_read_resource(uri="weather://weather/forecast")
|
||||
weather_data = await app._read_resource_mcp(uri="weather://weather/forecast")
|
||||
print(f"\nWeather data from prefixed URI: {weather_data}")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ def get_example_data() -> dict:
|
|||
|
||||
|
||||
async def example_usage():
|
||||
result = await server._mcp_call_tool("get_example_data", {})
|
||||
result = await server._call_tool_mcp("get_example_data", {})
|
||||
print("Tool Result:")
|
||||
print(result)
|
||||
print("This is an example of using a custom serializer with FastMCP.")
|
||||
|
|
|
|||
657
examples/smart_home/uv.lock
generated
|
|
@ -1,657 +0,0 @@
|
|||
version = 1
|
||||
revision = 1
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "idna" },
|
||||
{ name = "sniffio" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asttokens"
|
||||
version = "3.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.1.31"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "decorator"
|
||||
version = "5.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dotenv"
|
||||
version = "0.9.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "python-dotenv" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/b7/545d2c10c1fc15e48653c91efde329a790f2eecfbbf2bd16003b5db2bab0/dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9", size = 1892 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "executing"
|
||||
version = "2.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.115.12"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "starlette" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastmcp"
|
||||
version = "2.0.0"
|
||||
source = { git = "https://github.com/jlowin/fastmcp.git#b6b23d6866249c29dbef7021d305ca62400162c4" }
|
||||
dependencies = [
|
||||
{ name = "dotenv" },
|
||||
{ name = "fastapi" },
|
||||
{ name = "mcp" },
|
||||
{ name = "openapi-pydantic" },
|
||||
{ name = "rich" },
|
||||
{ name = "typer" },
|
||||
{ name = "websockets" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.14.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9f/45/ad3e1b4d448f22c0cff4f5692f5ed0666658578e358b8d58a19846048059/httpcore-1.0.8.tar.gz", hash = "sha256:86e94505ed24ea06514883fd44d2bc02d90e77e7979c8eb71b90f41d364a1bad", size = 85385 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/18/8d/f052b1e336bb2c1fc7ed1aaed898aa570c0b61a09707b108979d9fc6e308/httpcore-1.0.8-py3-none-any.whl", hash = "sha256:5254cf149bcb5f75e9d1b2b9f729ea4a4b883d1ad7379fc632b727cec23674be", size = 78732 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.28.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "certifi" },
|
||||
{ name = "httpcore" },
|
||||
{ name = "idna" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx-sse"
|
||||
version = "0.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.10"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipython"
|
||||
version = "9.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "decorator" },
|
||||
{ name = "ipython-pygments-lexers" },
|
||||
{ name = "jedi" },
|
||||
{ name = "matplotlib-inline" },
|
||||
{ name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
||||
{ name = "prompt-toolkit" },
|
||||
{ name = "pygments" },
|
||||
{ name = "stack-data" },
|
||||
{ name = "traitlets" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/70/9a/6b8984bedc990f3a4aa40ba8436dea27e23d26a64527de7c2e5e12e76841/ipython-9.1.0.tar.gz", hash = "sha256:a47e13a5e05e02f3b8e1e7a0f9db372199fe8c3763532fe7a1e0379e4e135f16", size = 4373688 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/9d/4ff2adf55d1b6e3777b0303fdbe5b723f76e46cba4a53a32fe82260d2077/ipython-9.1.0-py3-none-any.whl", hash = "sha256:2df07257ec2f84a6b346b8d83100bcf8fa501c6e01ab75cd3799b0bb253b3d2a", size = 604053 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipython-pygments-lexers"
|
||||
version = "1.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jedi"
|
||||
version = "0.19.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "parso" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markdown-it-py"
|
||||
version = "3.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "mdurl" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matplotlib-inline"
|
||||
version = "0.1.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "traitlets" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mcp"
|
||||
version = "1.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "httpx" },
|
||||
{ name = "httpx-sse" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pydantic-settings" },
|
||||
{ name = "sse-starlette" },
|
||||
{ name = "starlette" },
|
||||
{ name = "uvicorn" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/95/d2/f587cb965a56e992634bebc8611c5b579af912b74e04eb9164bd49527d21/mcp-1.6.0.tar.gz", hash = "sha256:d9324876de2c5637369f43161cd71eebfd803df5a95e46225cab8d280e366723", size = 200031 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/10/30/20a7f33b0b884a9d14dd3aa94ff1ac9da1479fe2ad66dd9e2736075d2506/mcp-1.6.0-py3-none-any.whl", hash = "sha256:7bd24c6ea042dbec44c754f100984d186620d8b841ec30f1b19eda9b93a634d0", size = 76077 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mdurl"
|
||||
version = "0.1.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openapi-pydantic"
|
||||
version = "0.5.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/02/2e/58d83848dd1a79cb92ed8e63f6ba901ca282c5f09d04af9423ec26c56fd7/openapi_pydantic-0.5.1.tar.gz", hash = "sha256:ff6835af6bde7a459fb93eb93bb92b8749b754fc6e51b2f1590a19dc3005ee0d", size = 60892 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/12/cf/03675d8bd8ecbf4445504d8071adab19f5f993676795708e36402ab38263/openapi_pydantic-0.5.1-py3-none-any.whl", hash = "sha256:a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146", size = 96381 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parso"
|
||||
version = "0.8.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pexpect"
|
||||
version = "4.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "ptyprocess" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phue2"
|
||||
version = "0.0.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "httpx" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c2/48/8dd58546dd9867e4e1d6ed912a3952e5aea5da49040fb61bd07df59b32d4/phue2-0.0.3.tar.gz", hash = "sha256:c05cb28286880b202da29b825c3124fa6c44119323930fd118a81c20f29be0f9", size = 71925 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/88/58/5d4a080926d1862fac5524ac15fea38fd7b3dcdfa7315734b850e6051c63/phue2-0.0.3-py3-none-any.whl", hash = "sha256:d2717bcfe1f8572e8b6fff305aa7b051b2dc7818eb9f1bff5eb0941f80bde8b2", size = 27535 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prompt-toolkit"
|
||||
version = "3.0.50"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "wcwidth" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a1/e1/bd15cb8ffdcfeeb2bdc215de3c3cffca11408d829e4b8416dcfe71ba8854/prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab", size = 429087 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/ea/d836f008d33151c7a1f62caf3d8dd782e4d15f6a43897f64480c2b8de2ad/prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198", size = 387816 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ptyprocess"
|
||||
version = "0.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pure-eval"
|
||||
version = "0.2.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.11.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
{ name = "pydantic-core" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/10/2e/ca897f093ee6c5f3b0bee123ee4465c50e75431c3d5b6a3b44a47134e891/pydantic-2.11.3.tar.gz", hash = "sha256:7471657138c16adad9322fe3070c0116dd6c3ad8d649300e3cbdfe91f4db4ec3", size = 785513 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/1d/407b29780a289868ed696d1616f4aad49d6388e5a77f567dcd2629dcd7b8/pydantic-2.11.3-py3-none-any.whl", hash = "sha256:a082753436a07f9ba1289c6ffa01cd93db3548776088aa917cc43b63f68fa60f", size = 443591 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.33.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/17/19/ed6a078a5287aea7922de6841ef4c06157931622c89c2a47940837b5eecd/pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df", size = 434395 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/ce/3cb22b07c29938f97ff5f5bb27521f95e2ebec399b882392deb68d6c440e/pydantic_core-2.33.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1293d7febb995e9d3ec3ea09caf1a26214eec45b0f29f6074abb004723fc1de8", size = 2026640 },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/78/f381d643b12378fee782a72126ec5d793081ef03791c28a0fd542a5bee64/pydantic_core-2.33.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99b56acd433386c8f20be5c4000786d1e7ca0523c8eefc995d14d79c7a081498", size = 1852649 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/2b/98a37b80b15aac9eb2c6cfc6dbd35e5058a352891c5cce3a8472d77665a6/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35a5ec3fa8c2fe6c53e1b2ccc2454398f95d5393ab398478f53e1afbbeb4d939", size = 1892472 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/d4/3c59514e0f55a161004792b9ff3039da52448f43f5834f905abef9db6e4a/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b172f7b9d2f3abc0efd12e3386f7e48b576ef309544ac3a63e5e9cdd2e24585d", size = 1977509 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/b6/c2c7946ef70576f79a25db59a576bce088bdc5952d1b93c9789b091df716/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9097b9f17f91eea659b9ec58148c0747ec354a42f7389b9d50701610d86f812e", size = 2128702 },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/fe/65a880f81e3f2a974312b61f82a03d85528f89a010ce21ad92f109d94deb/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc77ec5b7e2118b152b0d886c7514a4653bcb58c6b1d760134a9fab915f777b3", size = 2679428 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/ff/4459e4146afd0462fb483bb98aa2436d69c484737feaceba1341615fb0ac/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3d15245b08fa4a84cefc6c9222e6f37c98111c8679fbd94aa145f9a0ae23d", size = 2008753 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/76/1c42e384e8d78452ededac8b583fe2550c84abfef83a0552e0e7478ccbc3/pydantic_core-2.33.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef99779001d7ac2e2461d8ab55d3373fe7315caefdbecd8ced75304ae5a6fc6b", size = 2114849 },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/72/7d0cf05095c15f7ffe0eb78914b166d591c0eed72f294da68378da205101/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fc6bf8869e193855e8d91d91f6bf59699a5cdfaa47a404e278e776dd7f168b39", size = 2069541 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/69/94a514066bb7d8be499aa764926937409d2389c09be0b5107a970286ef81/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:b1caa0bc2741b043db7823843e1bde8aaa58a55a58fda06083b0569f8b45693a", size = 2239225 },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/b0/e390071eadb44b41f4f54c3cef64d8bf5f9612c92686c9299eaa09e267e2/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ec259f62538e8bf364903a7d0d0239447059f9434b284f5536e8402b7dd198db", size = 2248373 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/b2/288b3579ffc07e92af66e2f1a11be3b056fe1214aab314748461f21a31c3/pydantic_core-2.33.1-cp312-cp312-win32.whl", hash = "sha256:e14f369c98a7c15772b9da98987f58e2b509a93235582838bd0d1d8c08b68fda", size = 1907034 },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/28/58442ad1c22b5b6742b992ba9518420235adced665513868f99a1c2638a5/pydantic_core-2.33.1-cp312-cp312-win_amd64.whl", hash = "sha256:1c607801d85e2e123357b3893f82c97a42856192997b95b4d8325deb1cd0c5f4", size = 1956848 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/eb/f54809b51c7e2a1d9f439f158b8dd94359321abcc98767e16fc48ae5a77e/pydantic_core-2.33.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d13f0276806ee722e70a1c93da19748594f19ac4299c7e41237fc791d1861ea", size = 1903986 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/24/eed3466a4308d79155f1cdd5c7432c80ddcc4530ba8623b79d5ced021641/pydantic_core-2.33.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a", size = 2033551 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/14/df54b1a0bc9b6ded9b758b73139d2c11b4e8eb43e8ab9c5847c0a2913ada/pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266", size = 1852785 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/96/e275f15ff3d34bb04b0125d9bc8848bf69f25d784d92a63676112451bfb9/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3", size = 1897758 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/d8/96bc536e975b69e3a924b507d2a19aedbf50b24e08c80fb00e35f9baaed8/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a", size = 1986109 },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/72/ab58e43ce7e900b88cb571ed057b2fcd0e95b708a2e0bed475b10130393e/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516", size = 2129159 },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/3f/52d85781406886c6870ac995ec0ba7ccc028b530b0798c9080531b409fdb/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764", size = 2680222 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/56/6e2ef42f363a0eec0fd92f74a91e0ac48cd2e49b695aac1509ad81eee86a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d", size = 2006980 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/c0/604536c4379cc78359f9ee0aa319f4aedf6b652ec2854953f5a14fc38c5a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4", size = 2120840 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/46/9eb764814f508f0edfb291a0f75d10854d78113fa13900ce13729aaec3ae/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde", size = 2072518 },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/e3/fb6b2a732b82d1666fa6bf53e3627867ea3131c5f39f98ce92141e3e3dc1/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e", size = 2248025 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/9d/fbe8fe9d1aa4dac88723f10a921bc7418bd3378a567cb5e21193a3c48b43/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd", size = 2254991 },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/99/07e2237b8a66438d9b26482332cda99a9acccb58d284af7bc7c946a42fd3/pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f", size = 1915262 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/f4/e457a7849beeed1e5defbcf5051c6f7b3c91a0624dd31543a64fc9adcf52/pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40", size = 1956626 },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/d0/e8d567a7cff7b04e017ae164d98011f1e1894269fe8e90ea187a3cbfb562/pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523", size = 1909590 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/fd/24ea4302d7a527d672c5be06e17df16aabfb4e9fdc6e0b345c21580f3d2a/pydantic_core-2.33.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d", size = 1812963 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/95/4fbc2ecdeb5c1c53f1175a32d870250194eb2fdf6291b795ab08c8646d5d/pydantic_core-2.33.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c", size = 1986896 },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/ae/fe31e7f4a62431222d8f65a3bd02e3fa7e6026d154a00818e6d30520ea77/pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18", size = 1931810 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-settings"
|
||||
version = "2.8.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "python-dotenv" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/88/82/c79424d7d8c29b994fb01d277da57b0a9b09cc03c3ff875f9bd8a86b2145/pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585", size = 83550 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/53/a64f03044927dc47aafe029c42a5b7aabc38dfb813475e0e1bf71c4a59d0/pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", size = 30839 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.19.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "14.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markdown-it-py" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.11.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/45/71/5759b2a6b2279bb77fe15b1435b89473631c2cd6374d45ccdb6b785810be/ruff-0.11.5.tar.gz", hash = "sha256:cae2e2439cb88853e421901ec040a758960b576126dab520fa08e9de431d1bef", size = 3976488 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/23/db/6efda6381778eec7f35875b5cbefd194904832a1153d68d36d6b269d81a8/ruff-0.11.5-py3-none-linux_armv6l.whl", hash = "sha256:2561294e108eb648e50f210671cc56aee590fb6167b594144401532138c66c7b", size = 10103150 },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/f2/06cd9006077a8db61956768bc200a8e52515bf33a8f9b671ee527bb10d77/ruff-0.11.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac12884b9e005c12d0bd121f56ccf8033e1614f736f766c118ad60780882a077", size = 10898637 },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/f5/af390a013c56022fe6f72b95c86eb7b2585c89cc25d63882d3bfe411ecf1/ruff-0.11.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4bfd80a6ec559a5eeb96c33f832418bf0fb96752de0539905cf7b0cc1d31d779", size = 10236012 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/ca/b9bf954cfed165e1a0c24b86305d5c8ea75def256707f2448439ac5e0d8b/ruff-0.11.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0947c0a1afa75dcb5db4b34b070ec2bccee869d40e6cc8ab25aca11a7d527794", size = 10415338 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/4d/2522dde4e790f1b59885283f8786ab0046958dfd39959c81acc75d347467/ruff-0.11.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad871ff74b5ec9caa66cb725b85d4ef89b53f8170f47c3406e32ef040400b038", size = 9965277 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/7a/749f56f150eef71ce2f626a2f6988446c620af2f9ba2a7804295ca450397/ruff-0.11.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6cf918390cfe46d240732d4d72fa6e18e528ca1f60e318a10835cf2fa3dc19f", size = 11541614 },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/b2/7d9b8435222485b6aac627d9c29793ba89be40b5de11584ca604b829e960/ruff-0.11.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:56145ee1478582f61c08f21076dc59153310d606ad663acc00ea3ab5b2125f82", size = 12198873 },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/e0/a1a69ef5ffb5c5f9c31554b27e030a9c468fc6f57055886d27d316dfbabd/ruff-0.11.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5f66f8f1e8c9fc594cbd66fbc5f246a8d91f916cb9667e80208663ec3728304", size = 11670190 },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/61/c1c16df6e92975072c07f8b20dad35cd858e8462b8865bc856fe5d6ccb63/ruff-0.11.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80b4df4d335a80315ab9afc81ed1cff62be112bd165e162b5eed8ac55bfc8470", size = 13902301 },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/89/0af10c8af4363304fd8cb833bd407a2850c760b71edf742c18d5a87bb3ad/ruff-0.11.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3068befab73620b8a0cc2431bd46b3cd619bc17d6f7695a3e1bb166b652c382a", size = 11350132 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/e1/ecb4c687cbf15164dd00e38cf62cbab238cad05dd8b6b0fc68b0c2785e15/ruff-0.11.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5da2e710a9641828e09aa98b92c9ebbc60518fdf3921241326ca3e8f8e55b8b", size = 10312937 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/4f/0e53fe5e500b65934500949361e3cd290c5ba60f0324ed59d15f46479c06/ruff-0.11.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ef39f19cb8ec98cbc762344921e216f3857a06c47412030374fffd413fb8fd3a", size = 9936683 },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/a8/8183c4da6d35794ae7f76f96261ef5960853cd3f899c2671961f97a27d8e/ruff-0.11.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b2a7cedf47244f431fd11aa5a7e2806dda2e0c365873bda7834e8f7d785ae159", size = 10950217 },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/88/9b85a5a8af21e46a0639b107fcf9bfc31da4f1d263f2fc7fbe7199b47f0a/ruff-0.11.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:81be52e7519f3d1a0beadcf8e974715b2dfc808ae8ec729ecfc79bddf8dbb783", size = 11404521 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/52/047f35d3b20fd1ae9ccfe28791ef0f3ca0ef0b3e6c1a58badd97d450131b/ruff-0.11.5-py3-none-win32.whl", hash = "sha256:e268da7b40f56e3eca571508a7e567e794f9bfcc0f412c4b607931d3af9c4afe", size = 10320697 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/fe/00c78010e3332a6e92762424cf4c1919065707e962232797d0b57fd8267e/ruff-0.11.5-py3-none-win_amd64.whl", hash = "sha256:6c6dc38af3cfe2863213ea25b6dc616d679205732dc0fb673356c2d69608f800", size = 11378665 },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/7c/c83fe5cbb70ff017612ff36654edfebec4b1ef79b558b8e5fd933bab836b/ruff-0.11.5-py3-none-win_arm64.whl", hash = "sha256:67e241b4314f4eacf14a601d586026a962f4002a475aa702c69980a38087aa4e", size = 10460287 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shellingham"
|
||||
version = "1.5.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smart-home"
|
||||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "fastmcp" },
|
||||
{ name = "phue2" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "ipython" },
|
||||
{ name = "ruff" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "fastmcp", git = "https://github.com/jlowin/fastmcp.git" },
|
||||
{ name = "phue2" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "ipython" },
|
||||
{ name = "ruff" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sse-starlette"
|
||||
version = "2.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "starlette" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/71/a4/80d2a11af59fe75b48230846989e93979c892d3a20016b42bb44edb9e398/sse_starlette-2.2.1.tar.gz", hash = "sha256:54470d5f19274aeed6b2d473430b08b4b379ea851d953b11d7f1c4a2c118b419", size = 17376 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/e0/5b8bd393f27f4a62461c5cf2479c75a2cc2ffa330976f9f00f5f6e4f50eb/sse_starlette-2.2.1-py3-none-any.whl", hash = "sha256:6410a3d3ba0c89e7675d4c273a301d64649c03a5ef1ca101f10b47f895fd0e99", size = 10120 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stack-data"
|
||||
version = "0.6.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "asttokens" },
|
||||
{ name = "executing" },
|
||||
{ name = "pure-eval" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
version = "0.46.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/04/1b/52b27f2e13ceedc79a908e29eac426a63465a1a01248e5f24aa36a62aeb3/starlette-0.46.1.tar.gz", hash = "sha256:3c88d58ee4bd1bb807c0d1acb381838afc7752f9ddaec81bbe4383611d833230", size = 2580102 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/4b/528ccf7a982216885a1ff4908e886b8fb5f19862d1962f56a3fce2435a70/starlette-0.46.1-py3-none-any.whl", hash = "sha256:77c74ed9d2720138b25875133f3a2dae6d854af2ec37dceb56aef370c1d8a227", size = 71995 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "traitlets"
|
||||
version = "5.14.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typer"
|
||||
version = "0.15.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "rich" },
|
||||
{ name = "shellingham" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8b/6f/3991f0f1c7fcb2df31aef28e0594d8d54b05393a0e4e34c65e475c2a5d41/typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5", size = 100711 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/fc/5b29fea8cee020515ca82cc68e3b8e1e34bb19a3535ad854cac9257b414c/typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc", size = 45061 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.13.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-inspection"
|
||||
version = "0.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.34.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wcwidth"
|
||||
version = "0.2.13"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "websockets"
|
||||
version = "15.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152 },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096 },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790 },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165 },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329 },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111 },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496 },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217 },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195 },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743 },
|
||||
]
|
||||
|
|
@ -15,6 +15,8 @@ dependencies = [
|
|||
"pydantic[email]>=2.11.7",
|
||||
"pyperclip>=1.9.0",
|
||||
"openapi-core>=0.19.5",
|
||||
"py-key-value-aio[disk,memory]>=0.2.1",
|
||||
"websockets>=15.0.1",
|
||||
]
|
||||
|
||||
requires-python = ">=3.10"
|
||||
|
|
@ -41,7 +43,6 @@ classifiers = [
|
|||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
websockets = ["websockets>=15.0.1"]
|
||||
openai = ["openai>=1.102.0"]
|
||||
caching = ["diskcache>=5.6.3", "cachetools>=6.2.0"]
|
||||
contrib-middleware-elasticsearch_cache = ["fastmcp[caching]", "elasticsearch>=8.15.0", "aiohttp>=3.11.10"]
|
||||
|
|
@ -68,6 +69,7 @@ dev = [
|
|||
"pytest-flakefinder",
|
||||
"pytest-httpx>=0.35.0",
|
||||
"pytest-report>=0.2.1",
|
||||
"pytest-retry>=1.7.0",
|
||||
"pytest-timeout>=2.4.0",
|
||||
"pytest-xdist>=3.6.1",
|
||||
"ruff",
|
||||
|
|
@ -105,7 +107,7 @@ asyncio_mode = "auto"
|
|||
asyncio_default_fixture_loop_scope = "session"
|
||||
asyncio_default_test_loop_scope = "session"
|
||||
# filterwarnings = ["error::DeprecationWarning"]
|
||||
timeout = 3
|
||||
timeout = 5
|
||||
env = [
|
||||
"FASTMCP_TEST_MODE=1",
|
||||
'D:FASTMCP_LOG_LEVEL=DEBUG',
|
||||
|
|
@ -122,6 +124,7 @@ testpaths = ["tests"]
|
|||
python_files = ["test_*.py", "*_test.py"]
|
||||
python_classes = ["Test*"]
|
||||
python_functions = ["test_*"]
|
||||
addopts = ["--inline-snapshot=disable"]
|
||||
|
||||
[tool.ty.src]
|
||||
include = ["src", "tests"]
|
||||
|
|
@ -145,3 +148,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"
|
||||
|
|
|
|||
|
|
@ -110,9 +110,9 @@ def install_claude_code(
|
|||
env_config = UVEnvironment(
|
||||
python=python_version,
|
||||
dependencies=(with_packages or []) + ["fastmcp"],
|
||||
requirements=str(with_requirements) if with_requirements else None,
|
||||
project=str(project) if project else None,
|
||||
editable=[str(p) for p in with_editable] if with_editable else None,
|
||||
requirements=with_requirements,
|
||||
project=project,
|
||||
editable=with_editable,
|
||||
)
|
||||
|
||||
# Build server spec from parsed components
|
||||
|
|
|
|||
|
|
@ -76,9 +76,9 @@ def install_claude_desktop(
|
|||
env_config = UVEnvironment(
|
||||
python=python_version,
|
||||
dependencies=(with_packages or []) + ["fastmcp"],
|
||||
requirements=str(with_requirements) if with_requirements else None,
|
||||
project=str(project) if project else None,
|
||||
editable=[str(p) for p in with_editable] if with_editable else None,
|
||||
requirements=with_requirements,
|
||||
project=project,
|
||||
editable=with_editable,
|
||||
)
|
||||
# Build server spec from parsed components
|
||||
if server_object:
|
||||
|
|
|
|||
|
|
@ -110,9 +110,9 @@ def install_cursor_workspace(
|
|||
env_config = UVEnvironment(
|
||||
python=python_version,
|
||||
dependencies=(with_packages or []) + ["fastmcp"],
|
||||
requirements=str(with_requirements.resolve()) if with_requirements else None,
|
||||
project=str(project.resolve()) if project else None,
|
||||
editable=[str(p.resolve()) for p in with_editable] if with_editable else None,
|
||||
requirements=with_requirements,
|
||||
project=project,
|
||||
editable=with_editable,
|
||||
)
|
||||
# Build server spec from parsed components
|
||||
if server_object:
|
||||
|
|
@ -180,9 +180,9 @@ def install_cursor(
|
|||
env_config = UVEnvironment(
|
||||
python=python_version,
|
||||
dependencies=(with_packages or []) + ["fastmcp"],
|
||||
requirements=str(with_requirements.resolve()) if with_requirements else None,
|
||||
project=str(project.resolve()) if project else None,
|
||||
editable=[str(p.resolve()) for p in with_editable] if with_editable else None,
|
||||
requirements=with_requirements,
|
||||
project=project,
|
||||
editable=with_editable,
|
||||
)
|
||||
# Build server spec from parsed components
|
||||
if server_object:
|
||||
|
|
|
|||
|
|
@ -104,13 +104,12 @@ 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"],
|
||||
requirements=str(with_requirements) if with_requirements else None,
|
||||
project=str(project) if project else None,
|
||||
editable=[str(p) for p in with_editable] if with_editable else None,
|
||||
requirements=with_requirements,
|
||||
project=project,
|
||||
editable=with_editable,
|
||||
)
|
||||
|
||||
# Build server spec from parsed components
|
||||
|
|
|
|||
|
|
@ -51,9 +51,9 @@ def install_mcp_json(
|
|||
env_config = UVEnvironment(
|
||||
python=python_version,
|
||||
dependencies=(with_packages or []) + ["fastmcp"],
|
||||
requirements=str(with_requirements) if with_requirements else None,
|
||||
project=str(project) if project else None,
|
||||
editable=[str(p) for p in with_editable] if with_editable else None,
|
||||
requirements=with_requirements,
|
||||
project=project,
|
||||
editable=with_editable,
|
||||
)
|
||||
# Build server spec from parsed components
|
||||
if server_object:
|
||||
|
|
|
|||
|
|
@ -184,8 +184,8 @@ async def run_command(
|
|||
kwargs["port"] = port
|
||||
if path:
|
||||
kwargs["path"] = path
|
||||
# Note: log_level is not currently supported by run_async
|
||||
# TODO: Add log_level support to server.run_async
|
||||
if log_level:
|
||||
kwargs["log_level"] = log_level
|
||||
|
||||
if not show_banner:
|
||||
kwargs["show_banner"] = False
|
||||
|
|
|
|||
|
|
@ -1,29 +1,28 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import time
|
||||
import webbrowser
|
||||
from asyncio import Future
|
||||
from collections.abc import AsyncGenerator
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Literal
|
||||
from typing import Any
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import anyio
|
||||
import httpx
|
||||
from key_value.aio.adapters.pydantic import PydanticAdapter
|
||||
from key_value.aio.protocols import AsyncKeyValue
|
||||
from key_value.aio.stores.memory import MemoryStore
|
||||
from mcp.client.auth import OAuthClientProvider, TokenStorage
|
||||
from mcp.shared.auth import (
|
||||
OAuthClientInformationFull,
|
||||
OAuthClientMetadata,
|
||||
OAuthToken,
|
||||
)
|
||||
from mcp.shared.auth import (
|
||||
OAuthToken as OAuthToken,
|
||||
)
|
||||
from pydantic import AnyHttpUrl, BaseModel, TypeAdapter, ValidationError
|
||||
from pydantic import AnyHttpUrl
|
||||
from typing_extensions import override
|
||||
from uvicorn.server import Server
|
||||
|
||||
from fastmcp import settings as fastmcp_global_settings
|
||||
from fastmcp.client.oauth_callback import (
|
||||
create_oauth_callback_server,
|
||||
)
|
||||
|
|
@ -41,161 +40,6 @@ class ClientNotFoundError(Exception):
|
|||
pass
|
||||
|
||||
|
||||
class StoredToken(BaseModel):
|
||||
"""Token storage format with absolute expiry time."""
|
||||
|
||||
token_payload: OAuthToken
|
||||
expires_at: datetime | None
|
||||
|
||||
|
||||
# Create TypeAdapter at module level for efficient parsing
|
||||
stored_token_adapter = TypeAdapter(StoredToken)
|
||||
|
||||
|
||||
def default_cache_dir() -> Path:
|
||||
return fastmcp_global_settings.home / "oauth-mcp-client-cache"
|
||||
|
||||
|
||||
class FileTokenStorage(TokenStorage):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
def __init__(self, server_url: str, cache_dir: Path | None = None):
|
||||
"""Initialize storage for a specific server URL."""
|
||||
self.server_url = server_url
|
||||
self.cache_dir = cache_dir or default_cache_dir()
|
||||
self.cache_dir.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
@staticmethod
|
||||
def get_base_url(url: str) -> str:
|
||||
"""Extract the base URL (scheme + host) from a URL."""
|
||||
parsed = urlparse(url)
|
||||
return f"{parsed.scheme}://{parsed.netloc}"
|
||||
|
||||
def get_cache_key(self) -> str:
|
||||
"""Generate a safe filesystem key from the server's base URL."""
|
||||
base_url = self.get_base_url(self.server_url)
|
||||
return (
|
||||
base_url.replace("://", "_")
|
||||
.replace(".", "_")
|
||||
.replace("/", "_")
|
||||
.replace(":", "_")
|
||||
)
|
||||
|
||||
def _get_file_path(self, file_type: Literal["client_info", "tokens"]) -> Path:
|
||||
"""Get the file path for the specified cache file type."""
|
||||
key = self.get_cache_key()
|
||||
return self.cache_dir / f"{key}_{file_type}.json"
|
||||
|
||||
async def get_tokens(self) -> OAuthToken | None:
|
||||
"""Load tokens from file storage."""
|
||||
path = self._get_file_path("tokens")
|
||||
|
||||
try:
|
||||
# Parse JSON and validate as StoredToken
|
||||
stored = stored_token_adapter.validate_json(path.read_text())
|
||||
|
||||
# Check if token is expired
|
||||
if stored.expires_at is not None:
|
||||
now = datetime.now(timezone.utc)
|
||||
if now >= stored.expires_at:
|
||||
logger.debug(
|
||||
f"Token expired for {self.get_base_url(self.server_url)}"
|
||||
)
|
||||
return None
|
||||
|
||||
# Recalculate expires_in to be correct relative to now
|
||||
if stored.token_payload.expires_in is not None:
|
||||
remaining = stored.expires_at - now
|
||||
stored.token_payload.expires_in = max(
|
||||
0, int(remaining.total_seconds())
|
||||
)
|
||||
|
||||
return stored.token_payload
|
||||
|
||||
except (FileNotFoundError, ValidationError) as e:
|
||||
logger.debug(
|
||||
f"Could not load tokens for {self.get_base_url(self.server_url)}: {e}"
|
||||
)
|
||||
return None
|
||||
|
||||
async def set_tokens(self, tokens: OAuthToken) -> None:
|
||||
"""Save tokens to file storage."""
|
||||
path = self._get_file_path("tokens")
|
||||
|
||||
# Calculate absolute expiry time if expires_in is present
|
||||
expires_at = None
|
||||
if tokens.expires_in is not None:
|
||||
expires_at = datetime.now(timezone.utc) + timedelta(
|
||||
seconds=tokens.expires_in
|
||||
)
|
||||
|
||||
# Create StoredToken and save using Pydantic serialization
|
||||
stored = StoredToken(token_payload=tokens, expires_at=expires_at)
|
||||
|
||||
path.write_text(stored.model_dump_json(indent=2))
|
||||
logger.debug(f"Saved tokens for {self.get_base_url(self.server_url)}")
|
||||
|
||||
async def get_client_info(self) -> OAuthClientInformationFull | None:
|
||||
"""Load client information from file storage."""
|
||||
path = self._get_file_path("client_info")
|
||||
try:
|
||||
client_info = OAuthClientInformationFull.model_validate_json(
|
||||
path.read_text()
|
||||
)
|
||||
# Check if we have corresponding valid tokens
|
||||
# If no tokens exist, the OAuth flow was incomplete and we should
|
||||
# force a fresh client registration
|
||||
tokens = await self.get_tokens()
|
||||
if tokens is None:
|
||||
logger.debug(
|
||||
f"No tokens found for client info at {self.get_base_url(self.server_url)}. "
|
||||
"OAuth flow may have been incomplete. Clearing client info to force fresh registration."
|
||||
)
|
||||
# Clear the incomplete client info
|
||||
client_info_path = self._get_file_path("client_info")
|
||||
client_info_path.unlink(missing_ok=True)
|
||||
return None
|
||||
|
||||
return client_info
|
||||
except (FileNotFoundError, json.JSONDecodeError, ValidationError) as e:
|
||||
logger.debug(
|
||||
f"Could not load client info for {self.get_base_url(self.server_url)}: {e}"
|
||||
)
|
||||
return None
|
||||
|
||||
async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
|
||||
"""Save client information to file storage."""
|
||||
path = self._get_file_path("client_info")
|
||||
path.write_text(client_info.model_dump_json(indent=2))
|
||||
logger.debug(f"Saved client info for {self.get_base_url(self.server_url)}")
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clear all cached data for this server."""
|
||||
file_types: list[Literal["client_info", "tokens"]] = ["client_info", "tokens"]
|
||||
for file_type in file_types:
|
||||
path = self._get_file_path(file_type)
|
||||
path.unlink(missing_ok=True)
|
||||
logger.debug(f"Cleared OAuth cache for {self.get_base_url(self.server_url)}")
|
||||
|
||||
@classmethod
|
||||
def clear_all(cls, cache_dir: Path | None = None) -> None:
|
||||
"""Clear all cached data for all servers."""
|
||||
cache_dir = cache_dir or default_cache_dir()
|
||||
if not cache_dir.exists():
|
||||
return
|
||||
|
||||
file_types: list[Literal["client_info", "tokens"]] = ["client_info", "tokens"]
|
||||
for file_type in file_types:
|
||||
for file in cache_dir.glob(f"*_{file_type}.json"):
|
||||
file.unlink(missing_ok=True)
|
||||
logger.info("Cleared all OAuth client cache data.")
|
||||
|
||||
|
||||
async def check_if_auth_required(
|
||||
mcp_url: str, httpx_kwargs: dict[str, Any] | None = None
|
||||
) -> bool:
|
||||
|
|
@ -226,6 +70,70 @@ async def check_if_auth_required(
|
|||
return True
|
||||
|
||||
|
||||
class TokenStorageAdapter(TokenStorage):
|
||||
_server_url: str
|
||||
_key_value_store: AsyncKeyValue
|
||||
_storage_oauth_token: PydanticAdapter[OAuthToken]
|
||||
_storage_client_info: PydanticAdapter[OAuthClientInformationFull]
|
||||
|
||||
def __init__(self, async_key_value: AsyncKeyValue, server_url: str):
|
||||
self._server_url = server_url
|
||||
self._key_value_store = async_key_value
|
||||
self._storage_oauth_token = PydanticAdapter[OAuthToken](
|
||||
default_collection="mcp-oauth-token",
|
||||
key_value=async_key_value,
|
||||
pydantic_model=OAuthToken,
|
||||
raise_on_validation_error=True,
|
||||
)
|
||||
self._storage_client_info = PydanticAdapter[OAuthClientInformationFull](
|
||||
default_collection="mcp-oauth-client-info",
|
||||
key_value=async_key_value,
|
||||
pydantic_model=OAuthClientInformationFull,
|
||||
raise_on_validation_error=True,
|
||||
)
|
||||
|
||||
def _get_token_cache_key(self) -> str:
|
||||
return f"{self._server_url}/tokens"
|
||||
|
||||
def _get_client_info_cache_key(self) -> str:
|
||||
return f"{self._server_url}/client_info"
|
||||
|
||||
async def clear(self) -> None:
|
||||
await self._storage_oauth_token.delete(key=self._get_token_cache_key())
|
||||
await self._storage_client_info.delete(key=self._get_client_info_cache_key())
|
||||
|
||||
@override
|
||||
async def get_tokens(self) -> OAuthToken | None:
|
||||
return await self._storage_oauth_token.get(key=self._get_token_cache_key())
|
||||
|
||||
@override
|
||||
async def set_tokens(self, tokens: OAuthToken) -> None:
|
||||
await self._storage_oauth_token.put(
|
||||
key=self._get_token_cache_key(),
|
||||
value=tokens,
|
||||
ttl=tokens.expires_in,
|
||||
)
|
||||
|
||||
@override
|
||||
async def get_client_info(self) -> OAuthClientInformationFull | None:
|
||||
return await self._storage_client_info.get(
|
||||
key=self._get_client_info_cache_key()
|
||||
)
|
||||
|
||||
@override
|
||||
async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
|
||||
ttl: int | None = None
|
||||
|
||||
if client_info.client_secret_expires_at:
|
||||
ttl = client_info.client_secret_expires_at - int(time.time())
|
||||
|
||||
await self._storage_client_info.put(
|
||||
key=self._get_client_info_cache_key(),
|
||||
value=client_info,
|
||||
ttl=ttl,
|
||||
)
|
||||
|
||||
|
||||
class OAuth(OAuthClientProvider):
|
||||
"""
|
||||
OAuth client provider for MCP servers with browser-based authentication.
|
||||
|
|
@ -239,7 +147,7 @@ class OAuth(OAuthClientProvider):
|
|||
mcp_url: str,
|
||||
scopes: str | list[str] | None = None,
|
||||
client_name: str = "FastMCP Client",
|
||||
token_storage_cache_dir: Path | None = None,
|
||||
token_storage: AsyncKeyValue | None = None,
|
||||
additional_client_metadata: dict[str, Any] | None = None,
|
||||
callback_port: int | None = None,
|
||||
):
|
||||
|
|
@ -251,7 +159,7 @@ class OAuth(OAuthClientProvider):
|
|||
scopes: OAuth scopes to request. Can be a
|
||||
space-separated string or a list of strings.
|
||||
client_name: Name for this client during registration
|
||||
token_storage_cache_dir: Directory for FileTokenStorage
|
||||
token_storage: An AsyncKeyValue-compatible token store, tokens are stored in memory if not provided
|
||||
additional_client_metadata: Extra fields for OAuthClientMetadata
|
||||
callback_port: Fixed port for OAuth callback (default: random available port)
|
||||
"""
|
||||
|
|
@ -281,8 +189,10 @@ class OAuth(OAuthClientProvider):
|
|||
)
|
||||
|
||||
# Create server-specific token storage
|
||||
storage = FileTokenStorage(
|
||||
server_url=server_base_url, cache_dir=token_storage_cache_dir
|
||||
token_storage = token_storage or MemoryStore()
|
||||
|
||||
self.token_storage_adapter: TokenStorageAdapter = TokenStorageAdapter(
|
||||
async_key_value=token_storage, server_url=server_base_url
|
||||
)
|
||||
|
||||
# Store server_base_url for use in callback_handler
|
||||
|
|
@ -292,7 +202,7 @@ class OAuth(OAuthClientProvider):
|
|||
super().__init__(
|
||||
server_url=server_base_url,
|
||||
client_metadata=client_metadata,
|
||||
storage=storage,
|
||||
storage=self.token_storage_adapter,
|
||||
redirect_handler=self.redirect_handler,
|
||||
callback_handler=self.callback_handler,
|
||||
)
|
||||
|
|
@ -318,8 +228,8 @@ class OAuth(OAuthClientProvider):
|
|||
"OAuth client not found - cached credentials may be stale"
|
||||
)
|
||||
|
||||
# For any non-redirect response, something is wrong
|
||||
if response.status_code not in (302, 303, 307, 308):
|
||||
# OAuth typically returns redirects, but some providers return 200 with HTML login pages
|
||||
if response.status_code not in (200, 302, 303, 307, 308):
|
||||
raise RuntimeError(
|
||||
f"Unexpected authorization response: {response.status_code}"
|
||||
)
|
||||
|
|
@ -386,23 +296,7 @@ class OAuth(OAuthClientProvider):
|
|||
|
||||
# Clear cached state and retry once
|
||||
self._initialized = False
|
||||
|
||||
# Try to clear storage if it supports it
|
||||
if hasattr(self.context.storage, "clear"):
|
||||
try:
|
||||
self.context.storage.clear()
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to clear OAuth storage cache: {e}")
|
||||
# Can't retry without clearing cache, re-raise original error
|
||||
raise ClientNotFoundError(
|
||||
"OAuth client not found and cache could not be cleared"
|
||||
) from e
|
||||
else:
|
||||
logger.warning(
|
||||
"Storage does not support clear() - cannot retry with fresh credentials"
|
||||
)
|
||||
# Can't retry without clearing cache, re-raise original error
|
||||
raise
|
||||
await self.token_storage_adapter.clear()
|
||||
|
||||
gen = super().async_auth_flow(request)
|
||||
response = None
|
||||
|
|
|
|||
|
|
@ -155,38 +155,38 @@ class Client(Generic[ClientTransportT]):
|
|||
"""
|
||||
|
||||
@overload
|
||||
def __init__(self: Client[T], transport: T, *args, **kwargs) -> None: ...
|
||||
def __init__(self: Client[T], transport: T, *args: Any, **kwargs: Any) -> None: ...
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: Client[SSETransport | StreamableHttpTransport],
|
||||
transport: AnyUrl,
|
||||
*args,
|
||||
**kwargs,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> None: ...
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: Client[FastMCPTransport],
|
||||
transport: FastMCP | FastMCP1Server,
|
||||
*args,
|
||||
**kwargs,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> None: ...
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: Client[PythonStdioTransport | NodeStdioTransport],
|
||||
transport: Path,
|
||||
*args,
|
||||
**kwargs,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> None: ...
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: Client[MCPConfigTransport],
|
||||
transport: MCPConfig | dict[str, Any],
|
||||
*args,
|
||||
**kwargs,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> None: ...
|
||||
|
||||
@overload
|
||||
|
|
@ -198,8 +198,8 @@ class Client(Generic[ClientTransportT]):
|
|||
| StreamableHttpTransport
|
||||
],
|
||||
transport: str,
|
||||
*args,
|
||||
**kwargs,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> None: ...
|
||||
|
||||
def __init__(
|
||||
|
|
@ -745,12 +745,15 @@ class Client(Generic[ClientTransportT]):
|
|||
self,
|
||||
ref: mcp.types.ResourceTemplateReference | mcp.types.PromptReference,
|
||||
argument: dict[str, str],
|
||||
context_arguments: dict[str, Any] | None = None,
|
||||
) -> mcp.types.CompleteResult:
|
||||
"""Send a completion request and return the complete MCP protocol result.
|
||||
|
||||
Args:
|
||||
ref (mcp.types.ResourceTemplateReference | mcp.types.PromptReference): The reference to complete.
|
||||
argument (dict[str, str]): Arguments to pass to the completion request.
|
||||
context_arguments (dict[str, Any] | None, optional): Optional context arguments to
|
||||
include with the completion request. Defaults to None.
|
||||
|
||||
Returns:
|
||||
mcp.types.CompleteResult: The complete response object from the protocol,
|
||||
|
|
@ -761,19 +764,24 @@ class Client(Generic[ClientTransportT]):
|
|||
"""
|
||||
logger.debug(f"[{self.name}] called complete: {ref}")
|
||||
|
||||
result = await self.session.complete(ref=ref, argument=argument)
|
||||
result = await self.session.complete(
|
||||
ref=ref, argument=argument, context_arguments=context_arguments
|
||||
)
|
||||
return result
|
||||
|
||||
async def complete(
|
||||
self,
|
||||
ref: mcp.types.ResourceTemplateReference | mcp.types.PromptReference,
|
||||
argument: dict[str, str],
|
||||
context_arguments: dict[str, Any] | None = None,
|
||||
) -> mcp.types.Completion:
|
||||
"""Send a completion request to the server.
|
||||
|
||||
Args:
|
||||
ref (mcp.types.ResourceTemplateReference | mcp.types.PromptReference): The reference to complete.
|
||||
argument (dict[str, str]): Arguments to pass to the completion request.
|
||||
context_arguments (dict[str, Any] | None, optional): Optional context arguments to
|
||||
include with the completion request. Defaults to None.
|
||||
|
||||
Returns:
|
||||
mcp.types.Completion: The completion object.
|
||||
|
|
@ -781,7 +789,9 @@ class Client(Generic[ClientTransportT]):
|
|||
Raises:
|
||||
RuntimeError: If called while the client is not connected.
|
||||
"""
|
||||
result = await self.complete_mcp(ref=ref, argument=argument)
|
||||
result = await self.complete_mcp(
|
||||
ref=ref, argument=argument, context_arguments=context_arguments
|
||||
)
|
||||
return result.completion
|
||||
|
||||
# --- Tools ---
|
||||
|
|
|
|||
|
|
@ -289,6 +289,7 @@ def create_oauth_callback_server(
|
|||
port=port,
|
||||
lifespan="off",
|
||||
log_level="warning",
|
||||
ws="websockets-sansio",
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -583,15 +583,15 @@ class UvStdioTransport(StdioTransport):
|
|||
command: str,
|
||||
args: list[str] | None = None,
|
||||
module: bool = False,
|
||||
project_directory: str | None = None,
|
||||
project_directory: Path | None = None,
|
||||
python_version: str | None = None,
|
||||
with_packages: list[str] | None = None,
|
||||
with_requirements: str | None = None,
|
||||
with_requirements: Path | None = None,
|
||||
env_vars: dict[str, str] | None = None,
|
||||
keep_alive: bool | None = None,
|
||||
):
|
||||
# Basic validation
|
||||
if project_directory and not Path(project_directory).exists():
|
||||
if project_directory and not project_directory.exists():
|
||||
raise NotADirectoryError(
|
||||
f"Project directory not found: {project_directory}"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ def _replace_ref_with_defs(
|
|||
|
||||
Examples:
|
||||
- {"type": "object", "properties": {"$ref": "#/components/schemas/..."}}
|
||||
- {"type": "object", "additionalProperties": {"$ref": "#/components/schemas/..."}, "properties": {...}}
|
||||
- {"$ref": "#/components/schemas/..."}
|
||||
- {"items": {"$ref": "#/components/schemas/..."}}
|
||||
- {"anyOf": [{"$ref": "#/components/schemas/..."}]}
|
||||
|
|
@ -117,6 +118,11 @@ def _replace_ref_with_defs(
|
|||
for section in ["anyOf", "allOf", "oneOf"]:
|
||||
for i, item in enumerate(schema.get(section, [])):
|
||||
schema[section][i] = _replace_ref_with_defs(item)
|
||||
if additionalProperties := schema.get("additionalProperties"):
|
||||
if not isinstance(additionalProperties, bool):
|
||||
schema["additionalProperties"] = _replace_ref_with_defs(
|
||||
additionalProperties
|
||||
)
|
||||
if info.get("description", description) and not schema.get("description"):
|
||||
schema["description"] = description
|
||||
return schema
|
||||
|
|
@ -297,9 +303,11 @@ def _combine_schemas_and_map_params(
|
|||
|
||||
# Convert refs if needed
|
||||
if convert_refs:
|
||||
param_schema = _replace_ref_with_defs(param.schema_)
|
||||
param_schema = _replace_ref_with_defs(param.schema_, param.description)
|
||||
else:
|
||||
param_schema = param.schema_
|
||||
param_schema = param.schema_.copy()
|
||||
if param.description and not param_schema.get("description"):
|
||||
param_schema["description"] = param.description
|
||||
original_desc = param_schema.get("description", "")
|
||||
location_desc = f"({param.location.capitalize()} parameter)"
|
||||
if original_desc:
|
||||
|
|
@ -324,9 +332,11 @@ def _combine_schemas_and_map_params(
|
|||
|
||||
# Convert refs if needed
|
||||
if convert_refs:
|
||||
param_schema = _replace_ref_with_defs(param.schema_)
|
||||
param_schema = _replace_ref_with_defs(param.schema_, param.description)
|
||||
else:
|
||||
param_schema = param.schema_
|
||||
param_schema = param.schema_.copy()
|
||||
if param.description and not param_schema.get("description"):
|
||||
param_schema["description"] = param.description
|
||||
|
||||
# Don't make optional parameters nullable - they can simply be omitted
|
||||
# The OpenAPI specification doesn't require optional parameters to accept null values
|
||||
|
|
@ -344,7 +354,7 @@ def _combine_schemas_and_map_params(
|
|||
if route.request_body.required:
|
||||
required.append("body")
|
||||
parameter_map["body"] = {"location": "body", "openapi_name": "body"}
|
||||
else:
|
||||
elif body_props:
|
||||
# Normal case: body has properties
|
||||
for prop_name, prop_schema in body_props.items():
|
||||
properties[prop_name] = prop_schema
|
||||
|
|
@ -357,6 +367,22 @@ def _combine_schemas_and_map_params(
|
|||
|
||||
if route.request_body.required:
|
||||
required.extend(body_schema.get("required", []))
|
||||
else:
|
||||
# Handle direct array/primitive schemas (like list[str] parameters from FastAPI)
|
||||
# Use the schema title as parameter name, fall back to generic name
|
||||
param_name = body_schema.get("title", "body").lower()
|
||||
|
||||
# Clean the parameter name to be valid
|
||||
import re
|
||||
|
||||
param_name = re.sub(r"[^a-zA-Z0-9_]", "_", param_name)
|
||||
if not param_name or param_name[0].isdigit():
|
||||
param_name = "body_data"
|
||||
|
||||
properties[param_name] = body_schema
|
||||
if route.request_body.required:
|
||||
required.append(param_name)
|
||||
parameter_map[param_name] = {"location": "body", "openapi_name": param_name}
|
||||
|
||||
result = {
|
||||
"type": "object",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from __future__ import annotations as _annotations
|
|||
|
||||
import inspect
|
||||
import json
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Awaitable, Callable, Sequence
|
||||
from typing import Any
|
||||
|
||||
|
|
@ -62,7 +61,7 @@ class PromptArgument(FastMCPBaseModel):
|
|||
)
|
||||
|
||||
|
||||
class Prompt(FastMCPComponent, ABC):
|
||||
class Prompt(FastMCPComponent):
|
||||
"""A prompt template that can be rendered with parameters."""
|
||||
|
||||
arguments: list[PromptArgument] | None = Field(
|
||||
|
|
@ -139,13 +138,16 @@ class Prompt(FastMCPComponent, ABC):
|
|||
meta=meta,
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
async def render(
|
||||
self,
|
||||
arguments: dict[str, Any] | None = None,
|
||||
) -> list[PromptMessage]:
|
||||
"""Render the prompt with arguments."""
|
||||
raise NotImplementedError("Prompt.render() must be implemented by subclasses")
|
||||
"""Render the prompt with arguments.
|
||||
|
||||
This method is not implemented in the base Prompt class and must be
|
||||
implemented by subclasses.
|
||||
"""
|
||||
raise NotImplementedError("Subclasses must implement render()")
|
||||
|
||||
|
||||
class FunctionPrompt(Prompt):
|
||||
|
|
|
|||
|
|
@ -46,21 +46,23 @@ class PromptManager:
|
|||
"""Adds a mounted server as a source for prompts."""
|
||||
self._mounted_servers.append(server)
|
||||
|
||||
async def _load_prompts(self, *, via_server: bool = False) -> dict[str, Prompt]:
|
||||
async def _load_prompts(
|
||||
self, *, apply_filtering: bool = False
|
||||
) -> dict[str, Prompt]:
|
||||
"""
|
||||
The single, consolidated recursive method for fetching prompts. The 'via_server'
|
||||
The single, consolidated recursive method for fetching prompts. The 'apply_filtering'
|
||||
parameter determines the communication path.
|
||||
|
||||
- via_server=False: Manager-to-manager path for complete, unfiltered inventory
|
||||
- via_server=True: Server-to-server path for filtered MCP requests
|
||||
- apply_filtering=False: Manager-to-manager path for complete, unfiltered inventory
|
||||
- apply_filtering=True: Server-to-server path for filtered MCP requests
|
||||
"""
|
||||
all_prompts: dict[str, Prompt] = {}
|
||||
|
||||
for mounted in self._mounted_servers:
|
||||
try:
|
||||
if via_server:
|
||||
if apply_filtering:
|
||||
# Use the server-to-server filtered path
|
||||
child_results = await mounted.server._list_prompts()
|
||||
child_results = await mounted.server._list_prompts_middleware()
|
||||
else:
|
||||
# Use the manager-to-manager unfiltered path
|
||||
child_results = await mounted.server._prompt_manager.list_prompts()
|
||||
|
|
@ -104,13 +106,13 @@ class PromptManager:
|
|||
"""
|
||||
Gets the complete, unfiltered inventory of all prompts.
|
||||
"""
|
||||
return await self._load_prompts(via_server=False)
|
||||
return await self._load_prompts(apply_filtering=False)
|
||||
|
||||
async def list_prompts(self) -> list[Prompt]:
|
||||
"""
|
||||
Lists all prompts, applying protocol filtering.
|
||||
"""
|
||||
prompts_dict = await self._load_prompts(via_server=True)
|
||||
prompts_dict = await self._load_prompts(apply_filtering=True)
|
||||
return list(prompts_dict.values())
|
||||
|
||||
def add_prompt_from_fn(
|
||||
|
|
@ -196,7 +198,9 @@ class PromptManager:
|
|||
else:
|
||||
continue
|
||||
try:
|
||||
return await mounted.server._get_prompt(prompt_key, arguments)
|
||||
return await mounted.server._get_prompt_middleware(
|
||||
prompt_key, arguments
|
||||
)
|
||||
except NotFoundError:
|
||||
continue
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import abc
|
||||
import inspect
|
||||
from collections.abc import Callable
|
||||
from typing import TYPE_CHECKING, Annotated, Any
|
||||
|
|
@ -31,7 +30,7 @@ if TYPE_CHECKING:
|
|||
pass
|
||||
|
||||
|
||||
class Resource(FastMCPComponent, abc.ABC):
|
||||
class Resource(FastMCPComponent):
|
||||
"""Base class for all resources."""
|
||||
|
||||
model_config = ConfigDict(validate_default=True)
|
||||
|
|
@ -111,10 +110,13 @@ class Resource(FastMCPComponent, abc.ABC):
|
|||
raise ValueError("Either name or uri must be provided")
|
||||
return self
|
||||
|
||||
@abc.abstractmethod
|
||||
async def read(self) -> str | bytes:
|
||||
"""Read the resource content."""
|
||||
pass
|
||||
"""Read the resource content.
|
||||
|
||||
This method is not implemented in the base Resource class and must be
|
||||
implemented by subclasses.
|
||||
"""
|
||||
raise NotImplementedError("Subclasses must implement read()")
|
||||
|
||||
def to_mcp_resource(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -63,27 +63,31 @@ class ResourceManager:
|
|||
|
||||
async def get_resources(self) -> dict[str, Resource]:
|
||||
"""Get all registered resources, keyed by URI."""
|
||||
return await self._load_resources(via_server=False)
|
||||
return await self._load_resources(apply_filtering=False)
|
||||
|
||||
async def get_resource_templates(self) -> dict[str, ResourceTemplate]:
|
||||
"""Get all registered templates, keyed by URI template."""
|
||||
return await self._load_resource_templates(via_server=False)
|
||||
return await self._load_resource_templates(apply_filtering=False)
|
||||
|
||||
async def _load_resources(self, *, via_server: bool = False) -> dict[str, Resource]:
|
||||
async def _load_resources(
|
||||
self, *, apply_filtering: bool = False
|
||||
) -> dict[str, Resource]:
|
||||
"""
|
||||
The single, consolidated recursive method for fetching resources. The 'via_server'
|
||||
The single, consolidated recursive method for fetching resources. The 'apply_filtering'
|
||||
parameter determines the communication path.
|
||||
|
||||
- via_server=False: Manager-to-manager path for complete, unfiltered inventory
|
||||
- via_server=True: Server-to-server path for filtered MCP requests
|
||||
- apply_filtering=False: Manager-to-manager path for complete, unfiltered inventory
|
||||
- apply_filtering=True: Server-to-server path for filtered MCP requests
|
||||
"""
|
||||
all_resources: dict[str, Resource] = {}
|
||||
|
||||
for mounted in self._mounted_servers:
|
||||
try:
|
||||
if via_server:
|
||||
if apply_filtering:
|
||||
# Use the server-to-server filtered path
|
||||
child_resources_list = await mounted.server._list_resources()
|
||||
child_resources_list = (
|
||||
await mounted.server._list_resources_middleware()
|
||||
)
|
||||
child_resources = {
|
||||
resource.key: resource for resource in child_resources_list
|
||||
}
|
||||
|
|
@ -123,22 +127,24 @@ class ResourceManager:
|
|||
return all_resources
|
||||
|
||||
async def _load_resource_templates(
|
||||
self, *, via_server: bool = False
|
||||
self, *, apply_filtering: bool = False
|
||||
) -> dict[str, ResourceTemplate]:
|
||||
"""
|
||||
The single, consolidated recursive method for fetching templates. The 'via_server'
|
||||
The single, consolidated recursive method for fetching templates. The 'apply_filtering'
|
||||
parameter determines the communication path.
|
||||
|
||||
- via_server=False: Manager-to-manager path for complete, unfiltered inventory
|
||||
- via_server=True: Server-to-server path for filtered MCP requests
|
||||
- apply_filtering=False: Manager-to-manager path for complete, unfiltered inventory
|
||||
- apply_filtering=True: Server-to-server path for filtered MCP requests
|
||||
"""
|
||||
all_templates: dict[str, ResourceTemplate] = {}
|
||||
|
||||
for mounted in self._mounted_servers:
|
||||
try:
|
||||
if via_server:
|
||||
if apply_filtering:
|
||||
# Use the server-to-server filtered path
|
||||
child_templates = await mounted.server._list_resource_templates()
|
||||
child_templates = (
|
||||
await mounted.server._list_resource_templates_middleware()
|
||||
)
|
||||
else:
|
||||
# Use the manager-to-manager unfiltered path
|
||||
child_templates = (
|
||||
|
|
@ -179,14 +185,14 @@ class ResourceManager:
|
|||
"""
|
||||
Lists all resources, applying protocol filtering.
|
||||
"""
|
||||
resources_dict = await self._load_resources(via_server=True)
|
||||
resources_dict = await self._load_resources(apply_filtering=True)
|
||||
return list(resources_dict.values())
|
||||
|
||||
async def list_resource_templates(self) -> list[ResourceTemplate]:
|
||||
"""
|
||||
Lists all templates, applying protocol filtering.
|
||||
"""
|
||||
templates_dict = await self._load_resource_templates(via_server=True)
|
||||
templates_dict = await self._load_resource_templates(apply_filtering=True)
|
||||
return list(templates_dict.values())
|
||||
|
||||
def add_resource_or_template_from_fn(
|
||||
|
|
@ -492,7 +498,7 @@ class ResourceManager:
|
|||
continue
|
||||
|
||||
try:
|
||||
result = await mounted.server._read_resource(key)
|
||||
result = await mounted.server._read_resource_middleware(key)
|
||||
return result[0].content
|
||||
except NotFoundError:
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import inspect
|
|||
import re
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
from urllib.parse import unquote
|
||||
from urllib.parse import parse_qs, unquote
|
||||
|
||||
from mcp.types import Annotations
|
||||
from mcp.types import ResourceTemplate as MCPResourceTemplate
|
||||
|
|
@ -26,8 +26,26 @@ from fastmcp.utilities.types import (
|
|||
)
|
||||
|
||||
|
||||
def extract_query_params(uri_template: str) -> set[str]:
|
||||
"""Extract query parameter names from RFC 6570 {?param1,param2} syntax."""
|
||||
match = re.search(r"\{\?([^}]+)\}", uri_template)
|
||||
if match:
|
||||
return {p.strip() for p in match.group(1).split(",")}
|
||||
return set()
|
||||
|
||||
|
||||
def build_regex(template: str) -> re.Pattern:
|
||||
parts = re.split(r"(\{[^}]+\})", template)
|
||||
"""Build regex pattern for URI template, handling RFC 6570 syntax.
|
||||
|
||||
Supports:
|
||||
- {var} - simple path parameter
|
||||
- {var*} - wildcard path parameter (captures multiple segments)
|
||||
- {?var1,var2} - query parameters (ignored in path matching)
|
||||
"""
|
||||
# Remove query parameter syntax for path matching
|
||||
template_without_query = re.sub(r"\{\?[^}]+\}", "", template)
|
||||
|
||||
parts = re.split(r"(\{[^}]+\})", template_without_query)
|
||||
pattern = ""
|
||||
for part in parts:
|
||||
if part.startswith("{") and part.endswith("}"):
|
||||
|
|
@ -43,11 +61,34 @@ def build_regex(template: str) -> re.Pattern:
|
|||
|
||||
|
||||
def match_uri_template(uri: str, uri_template: str) -> dict[str, str] | None:
|
||||
"""Match URI against template and extract both path and query parameters.
|
||||
|
||||
Supports RFC 6570 URI templates:
|
||||
- Path params: {var}, {var*}
|
||||
- Query params: {?var1,var2}
|
||||
"""
|
||||
# Split URI into path and query parts
|
||||
uri_path, _, query_string = uri.partition("?")
|
||||
|
||||
# Match path parameters
|
||||
regex = build_regex(uri_template)
|
||||
match = regex.match(uri)
|
||||
if match:
|
||||
return {k: unquote(v) for k, v in match.groupdict().items()}
|
||||
return None
|
||||
match = regex.match(uri_path)
|
||||
if not match:
|
||||
return None
|
||||
|
||||
params = {k: unquote(v) for k, v in match.groupdict().items()}
|
||||
|
||||
# Extract query parameters if present in URI and template
|
||||
if query_string:
|
||||
query_param_names = extract_query_params(uri_template)
|
||||
parsed_query = parse_qs(query_string)
|
||||
|
||||
for name in query_param_names:
|
||||
if name in parsed_query:
|
||||
# Take first value if multiple provided
|
||||
params[name] = parsed_query[name][0] # type: ignore[index]
|
||||
|
||||
return params
|
||||
|
||||
|
||||
class ResourceTemplate(FastMCPComponent):
|
||||
|
|
@ -206,6 +247,31 @@ class FunctionResourceTemplate(ResourceTemplate):
|
|||
if context_kwarg and context_kwarg not in kwargs:
|
||||
kwargs[context_kwarg] = get_context()
|
||||
|
||||
# Type coercion for query parameters (which arrive as strings)
|
||||
# Get function signature for type hints
|
||||
sig = inspect.signature(self.fn)
|
||||
for param_name, param_value in list(kwargs.items()):
|
||||
if param_name in sig.parameters and isinstance(param_value, str):
|
||||
param = sig.parameters[param_name]
|
||||
annotation = param.annotation
|
||||
|
||||
# Skip if no annotation or annotation is str
|
||||
if annotation is inspect.Parameter.empty or annotation is str:
|
||||
continue
|
||||
|
||||
# Handle common type coercions
|
||||
try:
|
||||
if annotation is int:
|
||||
kwargs[param_name] = int(param_value)
|
||||
elif annotation is float:
|
||||
kwargs[param_name] = float(param_value)
|
||||
elif annotation is bool:
|
||||
# Handle boolean strings
|
||||
kwargs[param_name] = param_value.lower() in ("true", "1", "yes")
|
||||
except (ValueError, AttributeError):
|
||||
# Let validate_call handle the error
|
||||
pass
|
||||
|
||||
result = self.fn(**kwargs)
|
||||
if inspect.isawaitable(result):
|
||||
result = await result
|
||||
|
|
@ -245,16 +311,19 @@ class FunctionResourceTemplate(ResourceTemplate):
|
|||
|
||||
context_kwarg = find_kwarg_by_type(fn, kwarg_type=Context)
|
||||
|
||||
# Validate that URI params match function params
|
||||
uri_params = set(re.findall(r"{(\w+)(?:\*)?}", uri_template))
|
||||
if not uri_params:
|
||||
# Extract path and query parameters from URI template
|
||||
path_params = set(re.findall(r"{(\w+)(?:\*)?}", uri_template))
|
||||
query_params = extract_query_params(uri_template)
|
||||
all_uri_params = path_params | query_params
|
||||
|
||||
if not all_uri_params:
|
||||
raise ValueError("URI template must contain at least one parameter")
|
||||
|
||||
func_params = set(sig.parameters.keys())
|
||||
if context_kwarg:
|
||||
func_params.discard(context_kwarg)
|
||||
|
||||
# get the parameters that are required
|
||||
# Get required and optional function parameters
|
||||
required_params = {
|
||||
p
|
||||
for p in func_params
|
||||
|
|
@ -262,21 +331,37 @@ class FunctionResourceTemplate(ResourceTemplate):
|
|||
and sig.parameters[p].kind != inspect.Parameter.VAR_KEYWORD
|
||||
and p != context_kwarg
|
||||
}
|
||||
optional_params = {
|
||||
p
|
||||
for p in func_params
|
||||
if sig.parameters[p].default is not inspect.Parameter.empty
|
||||
and sig.parameters[p].kind != inspect.Parameter.VAR_KEYWORD
|
||||
and p != context_kwarg
|
||||
}
|
||||
|
||||
# Check if required parameters are a subset of the URI parameters
|
||||
if not required_params.issubset(uri_params):
|
||||
# Validate RFC 6570 query parameters
|
||||
# Query params must be optional (have defaults)
|
||||
if query_params:
|
||||
invalid_query_params = query_params - optional_params
|
||||
if invalid_query_params:
|
||||
raise ValueError(
|
||||
f"Query parameters {invalid_query_params} must be optional function parameters with default values"
|
||||
)
|
||||
|
||||
# Check if required parameters are a subset of the path parameters
|
||||
if not required_params.issubset(path_params):
|
||||
raise ValueError(
|
||||
f"Required function arguments {required_params} must be a subset of the URI parameters {uri_params}"
|
||||
f"Required function arguments {required_params} must be a subset of the URI path parameters {path_params}"
|
||||
)
|
||||
|
||||
# Check if the URI parameters are a subset of the function parameters (skip if **kwargs present)
|
||||
# Check if all URI parameters are valid function parameters (skip if **kwargs present)
|
||||
if not any(
|
||||
param.kind == inspect.Parameter.VAR_KEYWORD
|
||||
for param in sig.parameters.values()
|
||||
):
|
||||
if not uri_params.issubset(func_params):
|
||||
if not all_uri_params.issubset(func_params):
|
||||
raise ValueError(
|
||||
f"URI parameters {uri_params} must be a subset of the function arguments: {func_params}"
|
||||
f"URI parameters {all_uri_params} must be a subset of the function arguments: {func_params}"
|
||||
)
|
||||
|
||||
description = description or inspect.getdoc(fn)
|
||||
|
|
|
|||