mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-26 15:34:18 +02:00
Merge branch 'main' into main
This commit is contained in:
commit
1a72bfa4a7
119 changed files with 5776 additions and 2547 deletions
28
.github/pull_request_template.md
vendored
Normal file
28
.github/pull_request_template.md
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
## Description
|
||||
<!--
|
||||
Please provide a clear and concise description of the changes made in this pull request.
|
||||
|
||||
Using AI to generate code? Please include a note in the description with which AI tool you used.
|
||||
-->
|
||||
|
||||
**Contributors Checklist**
|
||||
<!--
|
||||
NOTE:
|
||||
1. You must create an issue in the repository before making a Pull Request.
|
||||
2. You must not create a Pull Request for an issue that is already assigned to someone else.
|
||||
|
||||
If you do not follow these steps, your Pull Request will be closed without review.
|
||||
-->
|
||||
|
||||
- [ ] My change is related to issue #(issue number)
|
||||
- [ ] I have followed the repository's development workflow
|
||||
- [ ] I have tested my changes manually and by adding relevant tests
|
||||
- [ ] I have performed all required documentation updates
|
||||
|
||||
**Review Checklist**
|
||||
<!-- Your Pull Request will not be reviewed if tests are failing, you have not self-reviewed your changes, or you have not checked all of the following: -->
|
||||
|
||||
- [ ] I have self-reviewed my changes
|
||||
- [ ] My Pull Request is ready for review
|
||||
|
||||
---
|
||||
4
.github/workflows/auto-close-duplicates.yml
vendored
4
.github/workflows/auto-close-duplicates.yml
vendored
|
|
@ -15,10 +15,10 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v4
|
||||
uses: astral-sh/setup-uv@v6
|
||||
|
||||
- name: Auto-close duplicate issues
|
||||
run: uv run scripts/auto_close_duplicates.py
|
||||
|
|
|
|||
7
.github/workflows/marvin-dedupe-issues.yml
vendored
7
.github/workflows/marvin-dedupe-issues.yml
vendored
|
|
@ -20,7 +20,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Generate Marvin App token
|
||||
id: marvin-token
|
||||
|
|
@ -45,7 +45,7 @@ jobs:
|
|||
|
||||
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.
|
||||
|
||||
5. Finally, comment back on the issue with a list of up to three duplicate issues (or zero, if there are no likely duplicates)
|
||||
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.
|
||||
|
||||
Notes for your agents:
|
||||
- Use `gh` to interact with GitHub, rather than web fetch
|
||||
|
|
@ -72,8 +72,9 @@ jobs:
|
|||
- name: Run Marvin dedupe command
|
||||
uses: anthropics/claude-code-base-action@beta
|
||||
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"
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY_FOR_CI }}
|
||||
claude_env: |
|
||||
GH_TOKEN: ${{ steps.marvin-token.outputs.token }}
|
||||
|
|
|
|||
4
.github/workflows/marvin-label-triage.yml
vendored
4
.github/workflows/marvin-label-triage.yml
vendored
|
|
@ -27,7 +27,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout base repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
repository: ${{ github.repository }}
|
||||
ref: ${{ github.event.repository.default_branch }}
|
||||
|
|
@ -151,7 +151,7 @@ jobs:
|
|||
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 }}
|
||||
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 }}
|
||||
|
|
|
|||
12
.github/workflows/marvin.yml
vendored
12
.github/workflows/marvin.yml
vendored
|
|
@ -31,24 +31,18 @@ jobs:
|
|||
(github.event_name == 'issues' && github.event.action == 'labeled' && github.event.label.name == 'marvin')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Set up Python environment
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
# Install UV package manager
|
||||
- name: Install UV
|
||||
uses: astral-sh/setup-uv@v5
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
enable-cache: true
|
||||
cache-dependency-glob: "uv.lock"
|
||||
|
||||
# Install project dependencies
|
||||
- name: Install dependencies
|
||||
run: uv sync --dev
|
||||
run: uv sync --python 3.12
|
||||
|
||||
# Install pre-commit hooks automatically
|
||||
- name: Install pre-commit hooks
|
||||
|
|
|
|||
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
|
|
@ -12,7 +12,7 @@ jobs:
|
|||
id-token: write # For PyPI's trusted publishing
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
|
|
|||
2
.github/workflows/run-static.yml
vendored
2
.github/workflows/run-static.yml
vendored
|
|
@ -30,7 +30,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
|
|
|
|||
10
.github/workflows/run-tests.yml
vendored
10
.github/workflows/run-tests.yml
vendored
|
|
@ -34,7 +34,7 @@ jobs:
|
|||
timeout-minutes: 5
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
|
|
@ -48,10 +48,10 @@ jobs:
|
|||
run: uv sync --frozen
|
||||
|
||||
- name: Run tests (excluding integration and client_process)
|
||||
run: uv run pytest tests -m "not integration and not client_process"
|
||||
run: uv run pytest -v tests -m "not integration and not client_process"
|
||||
|
||||
- name: Run client process tests separately
|
||||
run: uv run pytest tests -m "client_process" -x
|
||||
run: uv run pytest -v tests -m "client_process" -x
|
||||
|
||||
run_integration_tests:
|
||||
name: "Run integration tests"
|
||||
|
|
@ -59,7 +59,7 @@ jobs:
|
|||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
|
|
@ -73,7 +73,7 @@ jobs:
|
|||
run: uv sync --frozen
|
||||
|
||||
- name: Run integration tests
|
||||
run: uv run pytest tests -m "integration"
|
||||
run: uv run pytest -v tests -m "integration"
|
||||
env:
|
||||
FASTMCP_GITHUB_TOKEN: ${{ secrets.FASTMCP_GITHUB_TOKEN }}
|
||||
FASTMCP_TEST_AUTH_GITHUB_CLIENT_ID: ${{ secrets.FASTMCP_TEST_AUTH_GITHUB_CLIENT_ID }}
|
||||
|
|
|
|||
89
.github/workflows/update-config-schema.yml
vendored
Normal file
89
.github/workflows/update-config-schema.yml
vendored
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
name: Update FastMCPConfig Schema
|
||||
|
||||
# This workflow runs on merges to main to automatically update the config schema
|
||||
# by creating a PR when changes are needed.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
paths:
|
||||
- "src/fastmcp/utilities/fastmcp_config/**"
|
||||
- "!src/fastmcp/utilities/fastmcp_config/v1/schema.json" # Exclude the local schema file
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
update-config-schema:
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- 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: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
enable-cache: true
|
||||
cache-dependency-glob: "uv.lock"
|
||||
|
||||
- name: Install dependencies
|
||||
run: uv sync --python 3.12
|
||||
|
||||
- name: Generate config schema
|
||||
run: |
|
||||
echo "🔄 Generating fastmcp.json schema..."
|
||||
|
||||
# Generate schema in docs/public for web access
|
||||
uv run python -c "
|
||||
from fastmcp.utilities.fastmcp_config import generate_schema
|
||||
generate_schema('docs/public/schemas/fastmcp.json/latest.json')
|
||||
print('✅ Latest schema generated in docs/public')
|
||||
"
|
||||
|
||||
# Also update the v1 schema in docs/public
|
||||
uv run python -c "
|
||||
from fastmcp.utilities.fastmcp_config import generate_schema
|
||||
generate_schema('docs/public/schemas/fastmcp.json/v1.json')
|
||||
print('✅ v1 schema generated in docs/public')
|
||||
"
|
||||
|
||||
# Generate schema in the source directory for local development
|
||||
uv run python -c "
|
||||
from fastmcp.utilities.fastmcp_config import generate_schema
|
||||
generate_schema('src/fastmcp/utilities/fastmcp_config/v1/schema.json')
|
||||
print('✅ Schema generated in utilities/fastmcp_config/v1/')
|
||||
"
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: ${{ steps.marvin-token.outputs.token }}
|
||||
commit-message: "chore: Update fastmcp.json schema"
|
||||
title: "chore: Update fastmcp.json schema"
|
||||
body: |
|
||||
This PR updates the fastmcp.json schema files to match the current source code.
|
||||
|
||||
The schema is automatically generated from `src/fastmcp/utilities/fastmcp_config/` to ensure consistency.
|
||||
|
||||
**Note:** This PR is fully automated and will update itself with any subsequent changes to the schema, or close automatically if the schema becomes up-to-date through other means. Feel free to leave it open until you're ready to merge.
|
||||
|
||||
🤖 Generated by Marvin
|
||||
branch: marvin/update-config-schema
|
||||
delete-branch: true
|
||||
author: "marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com>"
|
||||
committer: "marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com>"
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
echo "✅ Config schema generation workflow completed"
|
||||
echo "PR will be created if there are changes, or closed if schema is already up to date"
|
||||
61
.github/workflows/update-sdk-docs.yml
vendored
61
.github/workflows/update-sdk-docs.yml
vendored
|
|
@ -1,8 +1,7 @@
|
|||
name: Update SDK Documentation
|
||||
|
||||
# This workflow runs on merges to main to automatically update SDK docs
|
||||
# without blocking PRs. SDK doc generation can fail if another PR merges
|
||||
# first, so handling it post-merge prevents annoying CI failures for contributors.
|
||||
# by creating a PR when changes are needed.
|
||||
|
||||
on:
|
||||
push:
|
||||
|
|
@ -22,9 +21,14 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Generate Marvin App token
|
||||
id: marvin-token
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
app-id: ${{ secrets.MARVIN_APP_ID }}
|
||||
private-key: ${{ secrets.MARVIN_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
|
|
@ -32,13 +36,8 @@ jobs:
|
|||
enable-cache: true
|
||||
cache-dependency-glob: "uv.lock"
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install dependencies
|
||||
run: uv sync --dev
|
||||
run: uv sync --python 3.12
|
||||
|
||||
- name: Install just
|
||||
uses: extractions/setup-just@v3
|
||||
|
|
@ -48,34 +47,26 @@ jobs:
|
|||
echo "🔄 Generating SDK documentation..."
|
||||
just api-ref-all
|
||||
|
||||
- name: Check for changes
|
||||
id: check_changes
|
||||
run: |
|
||||
if git diff --quiet docs/python-sdk/ docs/docs.json; then
|
||||
echo "No changes detected in SDK documentation"
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Changes detected in SDK documentation"
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: ${{ steps.marvin-token.outputs.token }}
|
||||
commit-message: "chore: Update SDK documentation"
|
||||
title: "chore: Update SDK documentation"
|
||||
body: |
|
||||
This PR updates the auto-generated SDK documentation to reflect the latest source code changes.
|
||||
|
||||
- name: Commit and push changes
|
||||
if: steps.check_changes.outputs.has_changes == 'true'
|
||||
run: |
|
||||
git config --local user.email "action@github.com"
|
||||
git config --local user.name "GitHub Action"
|
||||
git add docs/python-sdk/ docs/docs.json
|
||||
git commit -m "chore: Update SDK documentation
|
||||
📚 Documentation is automatically generated from the source code docstrings and type annotations.
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.ai/code)
|
||||
**Note:** This PR is fully automated and will update itself with any subsequent changes to the SDK, or close automatically if the documentation becomes up-to-date through other means. Feel free to leave it open until you're ready to merge.
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>"
|
||||
git push
|
||||
🤖 Generated by Marvin
|
||||
branch: marvin/update-sdk-docs
|
||||
delete-branch: true
|
||||
author: "marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com>"
|
||||
committer: "marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com>"
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
if [ "${{ steps.check_changes.outputs.has_changes }}" == "true" ]; then
|
||||
echo "✅ SDK documentation updated and committed"
|
||||
else
|
||||
echo "✅ SDK documentation is already up to date"
|
||||
fi
|
||||
echo "✅ SDK documentation generation workflow completed"
|
||||
echo "PR will be created if there are changes, or closed if documentation is already up to date"
|
||||
|
|
|
|||
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -64,7 +64,13 @@ dmypy.json
|
|||
# Claude worktree management
|
||||
.claude-wt/worktrees
|
||||
|
||||
# Agents
|
||||
/PLAN.md
|
||||
/TODO.md
|
||||
/STATUS.md
|
||||
|
||||
# Common FastMCP test files
|
||||
/test.py
|
||||
/server.py
|
||||
/client.py
|
||||
/test.json
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ repos:
|
|||
- repo: local
|
||||
hooks:
|
||||
- id: ty
|
||||
name: type check
|
||||
name: ty check
|
||||
entry: uv run ty check
|
||||
language: system
|
||||
types: [python]
|
||||
|
|
@ -37,4 +37,5 @@ repos:
|
|||
rev: v6.0.0
|
||||
hooks:
|
||||
- id: no-commit-to-branch
|
||||
name: prevent commits to main
|
||||
args: [--branch, main]
|
||||
|
|
|
|||
348
docs/assets/schemas/fastmcp_config/latest.json
Normal file
348
docs/assets/schemas/fastmcp_config/latest.json
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
{
|
||||
"$defs": {
|
||||
"Deployment": {
|
||||
"description": "Configuration for server deployment and runtime settings.",
|
||||
"properties": {
|
||||
"transport": {
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"stdio",
|
||||
"http",
|
||||
"sse"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Transport protocol to use",
|
||||
"title": "Transport"
|
||||
},
|
||||
"host": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Host to bind to when using HTTP transport",
|
||||
"examples": [
|
||||
"127.0.0.1",
|
||||
"0.0.0.0",
|
||||
"localhost"
|
||||
],
|
||||
"title": "Host"
|
||||
},
|
||||
"port": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Port to bind to when using HTTP transport",
|
||||
"examples": [
|
||||
8000,
|
||||
3000,
|
||||
5000
|
||||
],
|
||||
"title": "Port"
|
||||
},
|
||||
"path": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "URL path for the server endpoint",
|
||||
"examples": [
|
||||
"/mcp/",
|
||||
"/api/mcp/",
|
||||
"/sse/"
|
||||
],
|
||||
"title": "Path"
|
||||
},
|
||||
"log_level": {
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"DEBUG",
|
||||
"INFO",
|
||||
"WARNING",
|
||||
"ERROR",
|
||||
"CRITICAL"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Log level for the server",
|
||||
"title": "Log Level"
|
||||
},
|
||||
"cwd": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Working directory for the server process",
|
||||
"examples": [
|
||||
".",
|
||||
"./src",
|
||||
"/app"
|
||||
],
|
||||
"title": "Cwd"
|
||||
},
|
||||
"env": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Environment variables to set when running the server",
|
||||
"examples": [
|
||||
{
|
||||
"API_KEY": "secret",
|
||||
"DEBUG": "true"
|
||||
}
|
||||
],
|
||||
"title": "Env"
|
||||
},
|
||||
"args": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Arguments to pass to the server (after --)",
|
||||
"examples": [
|
||||
[
|
||||
"--config",
|
||||
"config.json",
|
||||
"--debug"
|
||||
]
|
||||
],
|
||||
"title": "Args"
|
||||
}
|
||||
},
|
||||
"title": "Deployment",
|
||||
"type": "object"
|
||||
},
|
||||
"Environment": {
|
||||
"description": "Configuration for Python environment setup.",
|
||||
"properties": {
|
||||
"python": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Python version constraint",
|
||||
"examples": [
|
||||
"3.10",
|
||||
"3.11",
|
||||
"3.12"
|
||||
],
|
||||
"title": "Python"
|
||||
},
|
||||
"dependencies": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Python packages to install with PEP 508 specifiers",
|
||||
"examples": [
|
||||
[
|
||||
"fastmcp>=2.0,<3",
|
||||
"httpx",
|
||||
"pandas>=2.0"
|
||||
]
|
||||
],
|
||||
"title": "Dependencies"
|
||||
},
|
||||
"requirements": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Path to requirements.txt file",
|
||||
"examples": [
|
||||
"requirements.txt",
|
||||
"../requirements/prod.txt"
|
||||
],
|
||||
"title": "Requirements"
|
||||
},
|
||||
"project": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Path to project directory containing pyproject.toml",
|
||||
"examples": [
|
||||
".",
|
||||
"../my-project"
|
||||
],
|
||||
"title": "Project"
|
||||
},
|
||||
"editable": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Directory to install in editable mode",
|
||||
"examples": [
|
||||
".",
|
||||
"../my-package"
|
||||
],
|
||||
"title": "Editable"
|
||||
}
|
||||
},
|
||||
"title": "Environment",
|
||||
"type": "object"
|
||||
},
|
||||
"FileSystemSource": {
|
||||
"description": "Source for local Python files.",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "filesystem",
|
||||
"default": "filesystem",
|
||||
"description": "Source type",
|
||||
"title": "Type",
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"description": "Path to Python file containing the server",
|
||||
"title": "Path",
|
||||
"type": "string"
|
||||
},
|
||||
"entrypoint": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Name of server instance or factory function (a no-arg function that returns a FastMCP server)",
|
||||
"title": "Entrypoint"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"title": "FileSystemSource",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"description": "Configuration file for FastMCP servers",
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"description": "JSON schema for IDE support and validation",
|
||||
"title": "$Schema"
|
||||
},
|
||||
"source": {
|
||||
"$ref": "#/$defs/FileSystemSource",
|
||||
"description": "Source configuration for the server",
|
||||
"examples": [
|
||||
{
|
||||
"path": "server.py"
|
||||
},
|
||||
{
|
||||
"entrypoint": "app",
|
||||
"path": "server.py"
|
||||
},
|
||||
{
|
||||
"entrypoint": "mcp",
|
||||
"path": "src/server.py",
|
||||
"type": "filesystem"
|
||||
}
|
||||
]
|
||||
},
|
||||
"environment": {
|
||||
"$ref": "#/$defs/Environment",
|
||||
"description": "Python environment setup configuration"
|
||||
},
|
||||
"deployment": {
|
||||
"$ref": "#/$defs/Deployment",
|
||||
"description": "Server deployment and runtime settings"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"source"
|
||||
],
|
||||
"title": "FastMCP Configuration",
|
||||
"type": "object",
|
||||
"$id": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json"
|
||||
}
|
||||
348
docs/assets/schemas/fastmcp_config/v1.json
Normal file
348
docs/assets/schemas/fastmcp_config/v1.json
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
{
|
||||
"$defs": {
|
||||
"Deployment": {
|
||||
"description": "Configuration for server deployment and runtime settings.",
|
||||
"properties": {
|
||||
"transport": {
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"stdio",
|
||||
"http",
|
||||
"sse"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Transport protocol to use",
|
||||
"title": "Transport"
|
||||
},
|
||||
"host": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Host to bind to when using HTTP transport",
|
||||
"examples": [
|
||||
"127.0.0.1",
|
||||
"0.0.0.0",
|
||||
"localhost"
|
||||
],
|
||||
"title": "Host"
|
||||
},
|
||||
"port": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Port to bind to when using HTTP transport",
|
||||
"examples": [
|
||||
8000,
|
||||
3000,
|
||||
5000
|
||||
],
|
||||
"title": "Port"
|
||||
},
|
||||
"path": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "URL path for the server endpoint",
|
||||
"examples": [
|
||||
"/mcp/",
|
||||
"/api/mcp/",
|
||||
"/sse/"
|
||||
],
|
||||
"title": "Path"
|
||||
},
|
||||
"log_level": {
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"DEBUG",
|
||||
"INFO",
|
||||
"WARNING",
|
||||
"ERROR",
|
||||
"CRITICAL"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Log level for the server",
|
||||
"title": "Log Level"
|
||||
},
|
||||
"cwd": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Working directory for the server process",
|
||||
"examples": [
|
||||
".",
|
||||
"./src",
|
||||
"/app"
|
||||
],
|
||||
"title": "Cwd"
|
||||
},
|
||||
"env": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Environment variables to set when running the server",
|
||||
"examples": [
|
||||
{
|
||||
"API_KEY": "secret",
|
||||
"DEBUG": "true"
|
||||
}
|
||||
],
|
||||
"title": "Env"
|
||||
},
|
||||
"args": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Arguments to pass to the server (after --)",
|
||||
"examples": [
|
||||
[
|
||||
"--config",
|
||||
"config.json",
|
||||
"--debug"
|
||||
]
|
||||
],
|
||||
"title": "Args"
|
||||
}
|
||||
},
|
||||
"title": "Deployment",
|
||||
"type": "object"
|
||||
},
|
||||
"Environment": {
|
||||
"description": "Configuration for Python environment setup.",
|
||||
"properties": {
|
||||
"python": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Python version constraint",
|
||||
"examples": [
|
||||
"3.10",
|
||||
"3.11",
|
||||
"3.12"
|
||||
],
|
||||
"title": "Python"
|
||||
},
|
||||
"dependencies": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Python packages to install with PEP 508 specifiers",
|
||||
"examples": [
|
||||
[
|
||||
"fastmcp>=2.0,<3",
|
||||
"httpx",
|
||||
"pandas>=2.0"
|
||||
]
|
||||
],
|
||||
"title": "Dependencies"
|
||||
},
|
||||
"requirements": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Path to requirements.txt file",
|
||||
"examples": [
|
||||
"requirements.txt",
|
||||
"../requirements/prod.txt"
|
||||
],
|
||||
"title": "Requirements"
|
||||
},
|
||||
"project": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Path to project directory containing pyproject.toml",
|
||||
"examples": [
|
||||
".",
|
||||
"../my-project"
|
||||
],
|
||||
"title": "Project"
|
||||
},
|
||||
"editable": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Directory to install in editable mode",
|
||||
"examples": [
|
||||
".",
|
||||
"../my-package"
|
||||
],
|
||||
"title": "Editable"
|
||||
}
|
||||
},
|
||||
"title": "Environment",
|
||||
"type": "object"
|
||||
},
|
||||
"FileSystemSource": {
|
||||
"description": "Source for local Python files.",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "filesystem",
|
||||
"default": "filesystem",
|
||||
"description": "Source type",
|
||||
"title": "Type",
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"description": "Path to Python file containing the server",
|
||||
"title": "Path",
|
||||
"type": "string"
|
||||
},
|
||||
"entrypoint": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Name of server instance or factory function (a no-arg function that returns a FastMCP server)",
|
||||
"title": "Entrypoint"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"title": "FileSystemSource",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"description": "Configuration file for FastMCP servers",
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"description": "JSON schema for IDE support and validation",
|
||||
"title": "$Schema"
|
||||
},
|
||||
"source": {
|
||||
"$ref": "#/$defs/FileSystemSource",
|
||||
"description": "Source configuration for the server",
|
||||
"examples": [
|
||||
{
|
||||
"path": "server.py"
|
||||
},
|
||||
{
|
||||
"entrypoint": "app",
|
||||
"path": "server.py"
|
||||
},
|
||||
{
|
||||
"entrypoint": "mcp",
|
||||
"path": "src/server.py",
|
||||
"type": "filesystem"
|
||||
}
|
||||
]
|
||||
},
|
||||
"environment": {
|
||||
"$ref": "#/$defs/Environment",
|
||||
"description": "Python environment setup configuration"
|
||||
},
|
||||
"deployment": {
|
||||
"$ref": "#/$defs/Deployment",
|
||||
"description": "Server deployment and runtime settings"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"source"
|
||||
],
|
||||
"title": "FastMCP Configuration",
|
||||
"type": "object",
|
||||
"$id": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json"
|
||||
}
|
||||
|
|
@ -1,16 +1,18 @@
|
|||
---
|
||||
title: Server Configuration with fastmcp.json
|
||||
sidebarTitle: Server Configuration
|
||||
description: Use fastmcp.json for declarative server configuration
|
||||
title: "Project Configuration"
|
||||
sidebarTitle: "Project Configuration"
|
||||
description: Use fastmcp.json for portable, declarative project configuration
|
||||
icon: file-code
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
||||
<VersionBadge version="2.11.4" />
|
||||
<VersionBadge version="2.12.0" />
|
||||
|
||||
FastMCP supports declarative configuration through `fastmcp.json` files. This is the canonical and preferred way to configure FastMCP projects, providing a single source of truth for server settings, dependencies, and deployment options that replaces complex command-line arguments.
|
||||
|
||||
The `fastmcp.json` file is designed to be a portable description of your server configuration that can be shared across environments and teams. When running from a `fastmcp.json` file, you can override any configuration values using CLI arguments.
|
||||
|
||||
## Overview
|
||||
|
||||
The `fastmcp.json` configuration file allows you to define all aspects of your FastMCP server in a structured, shareable format. Instead of remembering command-line arguments or writing shell scripts, you declare your server's configuration once and use it everywhere.
|
||||
|
|
@ -27,89 +29,110 @@ fastmcp run
|
|||
|
||||
This configuration approach ensures reproducible deployments across different environments, from local development to production servers. It works seamlessly with Claude Desktop, VS Code extensions, and any MCP-compatible client.
|
||||
|
||||
## JSON Schema Support
|
||||
## File Structure
|
||||
|
||||
The `fastmcp.json` configuration answers three fundamental questions about your server:
|
||||
|
||||
- **Source** = WHERE does your server code live?
|
||||
- **Environment** = WHAT environment setup does it require?
|
||||
- **Deployment** = HOW should the server run?
|
||||
|
||||
This conceptual model helps you understand the purpose of each configuration section and organize your settings effectively. The configuration file maps directly to these three concerns:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {
|
||||
// WHERE: Location of your server code
|
||||
"path": "server.py",
|
||||
"entrypoint": "mcp"
|
||||
},
|
||||
"environment": {
|
||||
// WHAT: Python environment and dependencies
|
||||
},
|
||||
"deployment": {
|
||||
// HOW: Runtime configuration
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Only the `source` field is required. The `environment` and `deployment` sections are optional and provide additional configuration when needed.
|
||||
|
||||
### JSON Schema Support
|
||||
|
||||
FastMCP provides JSON schemas for IDE autocomplete and validation. Add the schema reference to your `fastmcp.json` for enhanced developer experience:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
"entrypoint": {
|
||||
"file": "server.py",
|
||||
"object": "mcp"
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {
|
||||
"path": "server.py",
|
||||
"entrypoint": "mcp"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Two schema URLs are available:
|
||||
- **Version-specific**: `https://gofastmcp.com/schemas/fastmcp_config/v1.json`
|
||||
- **Latest version**: `https://gofastmcp.com/schemas/fastmcp_config/latest.json`
|
||||
- **Version-specific**: `https://gofastmcp.com/public/schemas/fastmcp.json/v1.json`
|
||||
- **Latest version**: `https://gofastmcp.com/public/schemas/fastmcp.json/latest.json`
|
||||
|
||||
Modern IDEs like VS Code will automatically provide autocomplete suggestions, validation, and inline documentation when the schema is specified.
|
||||
|
||||
## File Structure
|
||||
### Source Configuration
|
||||
|
||||
The `fastmcp.json` file has three main sections, each controlling a different aspect of your server:
|
||||
The source configuration determines **WHERE** your server code lives. It tells FastMCP how to find and load your server, whether it's a local Python file, a remote repository, or hosted in the cloud. This section is required and forms the foundation of your configuration.
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
"entrypoint": {
|
||||
"file": "server.py",
|
||||
"object": "mcp"
|
||||
},
|
||||
"environment": {
|
||||
// Python environment and dependencies
|
||||
},
|
||||
"deployment": {
|
||||
// Runtime configuration
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Only the `entrypoint` field is required. The `environment` and `deployment` sections are optional and provide additional configuration when needed.
|
||||
|
||||
## Configuration Fields
|
||||
|
||||
### Entrypoint
|
||||
|
||||
The entrypoint specifies which Python file and object contains your FastMCP server. This field is required and supports multiple formats to accommodate different project structures.
|
||||
|
||||
<Card icon="code" title="Entrypoint Configuration">
|
||||
<ParamField body="entrypoint" type="object | string" required>
|
||||
The server entry point. Can be specified in three formats:
|
||||
<Card icon="code" title="Source">
|
||||
<ParamField body="source" type="object" required>
|
||||
The server source configuration that determines where your server code lives.
|
||||
|
||||
**Object format** (recommended): Explicit file and object specification
|
||||
```json
|
||||
"entrypoint": {
|
||||
"file": "src/server.py",
|
||||
"object": "mcp"
|
||||
}
|
||||
```
|
||||
<ParamField body="type" type="string" default="filesystem">
|
||||
The source type identifier that determines which implementation to use. Currently supports `"filesystem"` for local files. Future releases will add support for `"git"` and `"cloud"` source types.
|
||||
</ParamField>
|
||||
|
||||
**String with object**: File path with colon and object name
|
||||
```json
|
||||
"entrypoint": "src/server.py:app"
|
||||
```
|
||||
|
||||
**String format**: Simple path to Python file (searches for common names: mcp, server, app)
|
||||
```json
|
||||
"entrypoint": "server.py"
|
||||
```
|
||||
|
||||
<Expandable title="Path Resolution">
|
||||
- File paths are resolved relative to the configuration file's location
|
||||
- If your `fastmcp.json` is in a project root and references `src/server.py`, FastMCP will look for the server at `<project_root>/src/server.py`
|
||||
- When no object is specified, FastMCP automatically searches for common server names: `mcp`, `server`, or `app`
|
||||
<Expandable title="FileSystemSource">
|
||||
When `type` is `"filesystem"` (or omitted), the source points to a local Python file containing your FastMCP server:
|
||||
|
||||
<ParamField body="path" type="string" required>
|
||||
Path to the Python file containing your FastMCP server.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="entrypoint" type="string">
|
||||
Name of the server instance or factory function within the module:
|
||||
- Can be a FastMCP server instance (e.g., `mcp = FastMCP("MyServer")`)
|
||||
- Can be a function with no arguments that returns a FastMCP server
|
||||
- If not specified, FastMCP searches for common names: `mcp`, `server`, or `app`
|
||||
</ParamField>
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
"source": {
|
||||
"type": "filesystem",
|
||||
"path": "src/server.py",
|
||||
"entrypoint": "mcp"
|
||||
}
|
||||
```
|
||||
|
||||
Note: File paths are resolved relative to the configuration file's location.
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
### Environment
|
||||
<Note>
|
||||
**Future Source Types**
|
||||
|
||||
The environment section configures Python dependencies and version requirements. When specified, FastMCP uses `uv` to create an isolated environment for your server, ensuring reproducible deployments across different systems.
|
||||
Future releases will support additional source types:
|
||||
- **Git repositories** (`type: "git"`) for loading server code directly from version control
|
||||
- **FastMCP Cloud** (`type: "cloud"`) for hosted servers with automatic scaling and management
|
||||
</Note>
|
||||
|
||||
<Card icon="code" title="Environment Configuration">
|
||||
### Environment Configuration
|
||||
|
||||
The environment configuration determines **WHAT** environment setup your server requires. It controls the build-time setup of your Python environment using `uv`'s powerful dependency management. This section ensures your server runs with the exact Python version and dependencies it requires, creating isolated, reproducible environments across different systems.
|
||||
|
||||
These settings leverage standard `uv` arguments for environment creation. When any environment field is specified, FastMCP automatically creates an isolated environment before running your server. This build-time configuration happens once when the server starts, not during runtime execution.
|
||||
|
||||
<Card icon="code" title="Environment Fields">
|
||||
<ParamField body="environment" type="object">
|
||||
Optional Python environment configuration. When any field is specified, FastMCP automatically creates an isolated environment using `uv`.
|
||||
|
||||
|
|
@ -143,9 +166,9 @@ The environment section configures Python dependencies and version requirements.
|
|||
</ParamField>
|
||||
|
||||
<ParamField body="editable" type="string">
|
||||
Path to a package to install in editable/development mode.
|
||||
Path to a package to install in editable/development mode. Useful for local development when you want changes to be reflected immediately.
|
||||
```json
|
||||
"editable": "./my-package"
|
||||
"editable": "."
|
||||
```
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
|
|
@ -157,11 +180,15 @@ When environment configuration is provided, FastMCP:
|
|||
2. Installs the specified dependencies
|
||||
3. Runs your server in this clean environment
|
||||
|
||||
### Deployment
|
||||
This build-time setup ensures your server always has the dependencies it needs, without polluting your system Python or conflicting with other projects.
|
||||
|
||||
The deployment section controls runtime configuration including transport protocol, networking, logging, and environment variables.
|
||||
### Deployment Configuration
|
||||
|
||||
<Card icon="code" title="Deployment Configuration">
|
||||
The deployment configuration controls **HOW** your server runs. It defines the runtime behavior including network settings, environment variables, and execution context. These settings determine how your server operates when it executes, from transport protocols to logging levels.
|
||||
|
||||
Environment variables are included in this section because they're runtime configuration that affects how your server behaves when it executes, not how its environment is built. The deployment configuration is applied every time your server starts, controlling its operational characteristics.
|
||||
|
||||
<Card icon="code" title="Deployment Fields">
|
||||
<ParamField body="deployment" type="object">
|
||||
Optional runtime configuration for the server.
|
||||
|
||||
|
|
@ -220,243 +247,7 @@ The deployment section controls runtime configuration including transport protoc
|
|||
</ParamField>
|
||||
</Card>
|
||||
|
||||
## Usage with CLI Commands
|
||||
|
||||
FastMCP automatically detects and uses `fastmcp.json` files, making server execution simple and consistent:
|
||||
|
||||
```bash
|
||||
# Auto-detect fastmcp.json in current directory
|
||||
cd my-project
|
||||
fastmcp run # No arguments needed!
|
||||
|
||||
# Or specify a configuration file explicitly
|
||||
fastmcp run prod.fastmcp.json
|
||||
```
|
||||
|
||||
The configuration file works with all FastMCP commands:
|
||||
- **`run`** - Start the server in production mode
|
||||
- **`dev`** - Launch with the Inspector UI for development
|
||||
- **`inspect`** - View server capabilities and configuration
|
||||
- **`install`** - Install to Claude Desktop, Cursor, or other MCP clients
|
||||
|
||||
When no file argument is provided, FastMCP searches the current directory for `fastmcp.json`. This means you can simply navigate to your project directory and run `fastmcp run` to start your server with all its configured settings.
|
||||
|
||||
### Custom Naming Patterns
|
||||
|
||||
You can use different configuration files for different environments:
|
||||
|
||||
- `fastmcp.json` - Default configuration
|
||||
- `dev.fastmcp.json` - Development settings
|
||||
- `prod.fastmcp.json` - Production settings
|
||||
- `test_fastmcp.json` - Test configuration
|
||||
|
||||
Any file with "fastmcp.json" in the name is recognized as a configuration file.
|
||||
|
||||
## Examples
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Basic Configuration">
|
||||
|
||||
A minimal configuration for a simple server:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
"entrypoint": {
|
||||
"file": "server.py",
|
||||
"object": "mcp"
|
||||
}
|
||||
}
|
||||
```
|
||||
This configuration explicitly specifies the server object name (`app`), making it clear which object contains your FastMCP server. Uses all defaults: STDIO transport, no special dependencies, standard logging.
|
||||
</Tab>
|
||||
<Tab title="Development Configuration">
|
||||
|
||||
A configuration optimized for local development:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
"entrypoint": "src/server.py:app",
|
||||
"environment": {
|
||||
"python": "3.12",
|
||||
"dependencies": ["fastmcp[dev]"],
|
||||
"editable": "."
|
||||
},
|
||||
"deployment": {
|
||||
"transport": "http",
|
||||
"host": "127.0.0.1",
|
||||
"port": 8000,
|
||||
"log_level": "DEBUG",
|
||||
"env": {
|
||||
"DEBUG": "true",
|
||||
"ENV": "development"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Production Configuration">
|
||||
|
||||
A production-ready configuration with full dependency management:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
"entrypoint": {
|
||||
"file": "app/main.py",
|
||||
"object": "mcp_server"
|
||||
},
|
||||
"environment": {
|
||||
"python": "3.11",
|
||||
"requirements": "requirements/production.txt",
|
||||
"project": "."
|
||||
},
|
||||
"deployment": {
|
||||
"transport": "http",
|
||||
"host": "0.0.0.0",
|
||||
"port": 3000,
|
||||
"path": "/api/mcp/",
|
||||
"log_level": "INFO",
|
||||
"env": {
|
||||
"ENV": "production",
|
||||
"API_BASE_URL": "https://api.example.com",
|
||||
"DATABASE_URL": "postgresql://user:pass@db.example.com/prod"
|
||||
},
|
||||
"cwd": "/app",
|
||||
"args": ["--workers", "4"]
|
||||
}
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Data Science Server">
|
||||
|
||||
Configuration for a data analysis server with scientific packages:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
"entrypoint": {
|
||||
"file": "analysis_server.py",
|
||||
"object": "mcp"
|
||||
},
|
||||
"environment": {
|
||||
"python": "3.11",
|
||||
"dependencies": [
|
||||
"pandas>=2.0",
|
||||
"numpy",
|
||||
"scikit-learn",
|
||||
"matplotlib",
|
||||
"jupyterlab"
|
||||
]
|
||||
},
|
||||
"deployment": {
|
||||
"transport": "stdio",
|
||||
"env": {
|
||||
"MATPLOTLIB_BACKEND": "Agg",
|
||||
"DATA_PATH": "./datasets"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Multi-Environment Setup">
|
||||
|
||||
You can maintain multiple configuration files for different environments:
|
||||
|
||||
**dev.fastmcp.json**:
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
"entrypoint": {
|
||||
"file": "server.py",
|
||||
"object": "mcp"
|
||||
},
|
||||
"deployment": {
|
||||
"transport": "http",
|
||||
"log_level": "DEBUG"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**prod.fastmcp.json**:
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
"entrypoint": {
|
||||
"file": "server.py",
|
||||
"object": "mcp"
|
||||
},
|
||||
"environment": {
|
||||
"requirements": "requirements/production.txt"
|
||||
},
|
||||
"deployment": {
|
||||
"transport": "http",
|
||||
"host": "0.0.0.0",
|
||||
"log_level": "WARNING"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Run different configurations:
|
||||
```bash
|
||||
fastmcp run dev.fastmcp.json # Development
|
||||
fastmcp run prod.fastmcp.json # Production
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
## CLI Override Behavior
|
||||
|
||||
Command-line arguments take precedence over configuration file values, allowing ad-hoc adjustments without modifying the file:
|
||||
|
||||
```bash
|
||||
# Config specifies port 3000, CLI overrides to 8080
|
||||
fastmcp run fastmcp.json --port 8080
|
||||
|
||||
# Config specifies stdio, CLI overrides to HTTP
|
||||
fastmcp run fastmcp.json --transport http
|
||||
|
||||
# Add extra dependencies not in config
|
||||
fastmcp run fastmcp.json --with requests --with httpx
|
||||
```
|
||||
|
||||
This precedence order enables:
|
||||
- Quick testing of different settings
|
||||
- Environment-specific overrides in deployment scripts
|
||||
- Debugging with increased log levels
|
||||
- Temporary configuration changes
|
||||
|
||||
## Best Practices
|
||||
|
||||
When using `fastmcp.json` for your projects, consider these recommendations:
|
||||
|
||||
**Version Control**: Always commit your `fastmcp.json` to version control. It's essential project documentation that ensures others can run your server correctly.
|
||||
|
||||
**Environment Variables**: Use the `env` field for configuration values instead of hardcoding them in your Python code. For sensitive values, consider using environment variable references or separate secret management.
|
||||
|
||||
**Dependency Management**: Specify exact versions for production dependencies to ensure reproducible builds:
|
||||
```json
|
||||
{
|
||||
"dependencies": [
|
||||
"pandas==2.1.0",
|
||||
"requests==2.31.0"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Path Resolution**: Remember that paths in the configuration are relative to the config file location. Use relative paths for portability:
|
||||
```json
|
||||
{
|
||||
"entrypoint": "./src/server.py",
|
||||
"environment": {
|
||||
"requirements": "./requirements.txt"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Development Workflow**: Use separate configuration files for different environments rather than constantly modifying a single file. The CLI's override behavior makes it easy to switch between configurations.
|
||||
|
||||
### Environment Variable Interpolation
|
||||
#### Environment Variable Interpolation
|
||||
|
||||
The `env` field in deployment configuration supports runtime interpolation of environment variables using `${VAR_NAME}` syntax. This enables dynamic configuration based on your deployment environment:
|
||||
|
||||
|
|
@ -499,6 +290,266 @@ This feature is particularly useful for:
|
|||
- Building dynamic URLs and connection strings
|
||||
- Creating environment-specific prefixes or suffixes
|
||||
|
||||
## Usage with CLI Commands
|
||||
|
||||
FastMCP automatically detects and uses a file specifically named `fastmcp.json` in the current directory, making server execution simple and consistent. Files with FastMCP configuration format but different names are not auto-detected and must be specified explicitly:
|
||||
|
||||
```bash
|
||||
# Auto-detect fastmcp.json in current directory
|
||||
cd my-project
|
||||
fastmcp run # No arguments needed!
|
||||
|
||||
# Or specify a configuration file explicitly
|
||||
fastmcp run prod.fastmcp.json
|
||||
|
||||
# Skip environment setup when already in a uv environment
|
||||
fastmcp run fastmcp.json --skip-env
|
||||
|
||||
# Skip source preparation when source is already prepared
|
||||
fastmcp run fastmcp.json --skip-source
|
||||
|
||||
# Skip both environment and source preparation
|
||||
fastmcp run fastmcp.json --skip-env --skip-source
|
||||
```
|
||||
|
||||
### Using an Existing Environment
|
||||
|
||||
By default, FastMCP creates an isolated environment with `uv` based on your configuration. When you already have a suitable Python environment, use the `--skip-env` flag to skip environment creation:
|
||||
|
||||
```bash
|
||||
fastmcp run fastmcp.json --skip-env
|
||||
```
|
||||
|
||||
**When you already have an environment:**
|
||||
- You're in an activated virtual environment with all dependencies installed
|
||||
- You're inside a Docker container with pre-installed dependencies
|
||||
- You're in a CI/CD pipeline that pre-builds the environment
|
||||
- You're using a system-wide installation with all required packages
|
||||
- You're in a uv-managed environment (prevents infinite recursion)
|
||||
|
||||
This flag tells FastMCP: "I already have everything installed, just run the server."
|
||||
|
||||
### Using an Existing Source
|
||||
|
||||
When working with source types that require preparation (future support for git repositories or cloud sources), use the `--skip-source` flag when you already have the source code available:
|
||||
|
||||
```bash
|
||||
fastmcp run fastmcp.json --skip-source
|
||||
```
|
||||
|
||||
**When you already have the source:**
|
||||
- You've previously cloned a git repository and don't need to re-fetch
|
||||
- You have a cached copy of a cloud-hosted server
|
||||
- You're in a CI/CD pipeline where source checkout is a separate step
|
||||
- You're iterating locally on already-downloaded code
|
||||
|
||||
This flag tells FastMCP: "I already have the source code, skip any download/clone steps."
|
||||
|
||||
Note: For filesystem sources (local Python files), this flag has no effect since they don't require preparation.
|
||||
|
||||
The configuration file works with all FastMCP commands:
|
||||
- **`run`** - Start the server in production mode
|
||||
- **`dev`** - Launch with the Inspector UI for development
|
||||
- **`inspect`** - View server capabilities and configuration
|
||||
- **`install`** - Install to Claude Desktop, Cursor, or other MCP clients
|
||||
|
||||
When no file argument is provided, FastMCP searches the current directory for `fastmcp.json`. This means you can simply navigate to your project directory and run `fastmcp run` to start your server with all its configured settings.
|
||||
|
||||
### CLI Override Behavior
|
||||
|
||||
Command-line arguments take precedence over configuration file values, allowing ad-hoc adjustments without modifying the file:
|
||||
|
||||
```bash
|
||||
# Config specifies port 3000, CLI overrides to 8080
|
||||
fastmcp run fastmcp.json --port 8080
|
||||
|
||||
# Config specifies stdio, CLI overrides to HTTP
|
||||
fastmcp run fastmcp.json --transport http
|
||||
|
||||
# Add extra dependencies not in config
|
||||
fastmcp run fastmcp.json --with requests --with httpx
|
||||
```
|
||||
|
||||
This precedence order enables:
|
||||
- Quick testing of different settings
|
||||
- Environment-specific overrides in deployment scripts
|
||||
- Debugging with increased log levels
|
||||
- Temporary configuration changes
|
||||
|
||||
### Custom Naming Patterns
|
||||
|
||||
You can use different configuration files for different environments:
|
||||
|
||||
- `fastmcp.json` - Default configuration
|
||||
- `dev.fastmcp.json` - Development settings
|
||||
- `prod.fastmcp.json` - Production settings
|
||||
- `test_fastmcp.json` - Test configuration
|
||||
|
||||
Any file with "fastmcp.json" in the name is recognized as a configuration file.
|
||||
|
||||
## Examples
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Basic Configuration">
|
||||
|
||||
A minimal configuration for a simple server:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {
|
||||
"path": "server.py",
|
||||
"entrypoint": "mcp"
|
||||
}
|
||||
}
|
||||
```
|
||||
This configuration explicitly specifies the server entrypoint (`mcp`), making it clear which server instance or factory function to use. Uses all defaults: STDIO transport, no special dependencies, standard logging.
|
||||
</Tab>
|
||||
<Tab title="Development Configuration">
|
||||
|
||||
A configuration optimized for local development:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
// WHERE does the server live?
|
||||
"source": {
|
||||
"path": "src/server.py",
|
||||
"entrypoint": "app"
|
||||
},
|
||||
// WHAT dependencies does it need?
|
||||
"environment": {
|
||||
"python": "3.12",
|
||||
"dependencies": ["fastmcp[dev]"],
|
||||
"editable": "."
|
||||
},
|
||||
// HOW should it run?
|
||||
"deployment": {
|
||||
"transport": "http",
|
||||
"host": "127.0.0.1",
|
||||
"port": 8000,
|
||||
"log_level": "DEBUG",
|
||||
"env": {
|
||||
"DEBUG": "true",
|
||||
"ENV": "development"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Production Configuration">
|
||||
|
||||
A production-ready configuration with full dependency management:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
// WHERE does the server live?
|
||||
"source": {
|
||||
"path": "app/main.py",
|
||||
"entrypoint": "mcp_server"
|
||||
},
|
||||
// WHAT dependencies does it need?
|
||||
"environment": {
|
||||
"python": "3.11",
|
||||
"requirements": "requirements/production.txt",
|
||||
"project": "."
|
||||
},
|
||||
// HOW should it run?
|
||||
"deployment": {
|
||||
"transport": "http",
|
||||
"host": "0.0.0.0",
|
||||
"port": 3000,
|
||||
"path": "/api/mcp/",
|
||||
"log_level": "INFO",
|
||||
"env": {
|
||||
"ENV": "production",
|
||||
"API_BASE_URL": "https://api.example.com",
|
||||
"DATABASE_URL": "postgresql://user:pass@db.example.com/prod"
|
||||
},
|
||||
"cwd": "/app",
|
||||
"args": ["--workers", "4"]
|
||||
}
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Data Science Server">
|
||||
|
||||
Configuration for a data analysis server with scientific packages:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {
|
||||
"path": "analysis_server.py",
|
||||
"entrypoint": "mcp"
|
||||
},
|
||||
"environment": {
|
||||
"python": "3.11",
|
||||
"dependencies": [
|
||||
"pandas>=2.0",
|
||||
"numpy",
|
||||
"scikit-learn",
|
||||
"matplotlib",
|
||||
"jupyterlab"
|
||||
]
|
||||
},
|
||||
"deployment": {
|
||||
"transport": "stdio",
|
||||
"env": {
|
||||
"MATPLOTLIB_BACKEND": "Agg",
|
||||
"DATA_PATH": "./datasets"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Multi-Environment Setup">
|
||||
|
||||
You can maintain multiple configuration files for different environments:
|
||||
|
||||
**dev.fastmcp.json**:
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {
|
||||
"path": "server.py",
|
||||
"entrypoint": "mcp"
|
||||
},
|
||||
"deployment": {
|
||||
"transport": "http",
|
||||
"log_level": "DEBUG"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**prod.fastmcp.json**:
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {
|
||||
"path": "server.py",
|
||||
"entrypoint": "mcp"
|
||||
},
|
||||
"environment": {
|
||||
"requirements": "requirements/production.txt"
|
||||
},
|
||||
"deployment": {
|
||||
"transport": "http",
|
||||
"host": "0.0.0.0",
|
||||
"log_level": "WARNING"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Run different configurations:
|
||||
```bash
|
||||
fastmcp run dev.fastmcp.json # Development
|
||||
fastmcp run prod.fastmcp.json # Production
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Migrating from CLI Arguments
|
||||
|
||||
If you're currently using command-line arguments or shell scripts, migrating to `fastmcp.json` simplifies your workflow. Here's how common CLI patterns map to configuration:
|
||||
|
|
@ -515,10 +566,10 @@ uv run --with pandas --with requests \
|
|||
**Equivalent fastmcp.json**:
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
"entrypoint": {
|
||||
"file": "server.py",
|
||||
"object": "mcp"
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {
|
||||
"path": "server.py",
|
||||
"entrypoint": "mcp"
|
||||
},
|
||||
"environment": {
|
||||
"dependencies": ["pandas", "requests"]
|
||||
|
|
|
|||
|
|
@ -119,7 +119,10 @@
|
|||
{
|
||||
"group": "Essentials",
|
||||
"icon": "cube",
|
||||
"pages": ["clients/client", "clients/transports"]
|
||||
"pages": [
|
||||
"clients/client",
|
||||
"clients/transports"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Core Operations",
|
||||
|
|
@ -145,7 +148,10 @@
|
|||
{
|
||||
"group": "Authentication",
|
||||
"icon": "user-shield",
|
||||
"pages": ["clients/auth/oauth", "clients/auth/bearer"]
|
||||
"pages": [
|
||||
"clients/auth/oauth",
|
||||
"clients/auth/bearer"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -224,12 +230,17 @@
|
|||
},
|
||||
{
|
||||
"anchor": "What's New",
|
||||
"pages": ["updates", "changelog"]
|
||||
"pages": [
|
||||
"updates",
|
||||
"changelog"
|
||||
]
|
||||
},
|
||||
{
|
||||
"anchor": "Community",
|
||||
"icon": "users",
|
||||
"pages": ["community/showcase"]
|
||||
"pages": [
|
||||
"community/showcase"
|
||||
]
|
||||
}
|
||||
],
|
||||
"tab": "Documentation"
|
||||
|
|
@ -327,6 +338,7 @@
|
|||
"python-sdk/fastmcp-server-auth-providers-workos"
|
||||
]
|
||||
},
|
||||
"python-sdk/fastmcp-server-auth-redirect_validation",
|
||||
"python-sdk/fastmcp-server-auth-registry"
|
||||
]
|
||||
},
|
||||
|
|
@ -367,6 +379,7 @@
|
|||
"python-sdk/fastmcp-utilities-cli",
|
||||
"python-sdk/fastmcp-utilities-components",
|
||||
"python-sdk/fastmcp-utilities-exceptions",
|
||||
"python-sdk/fastmcp-utilities-fastmcp_config",
|
||||
"python-sdk/fastmcp-utilities-http",
|
||||
"python-sdk/fastmcp-utilities-inspect",
|
||||
"python-sdk/fastmcp-utilities-json_schema",
|
||||
|
|
|
|||
|
|
@ -85,10 +85,10 @@ Alternatively, you can use a `fastmcp.json` configuration file (recommended):
|
|||
|
||||
```json fastmcp.json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
"entrypoint": {
|
||||
"file": "server.py",
|
||||
"object": "mcp"
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {
|
||||
"path": "server.py",
|
||||
"entrypoint": "mcp"
|
||||
},
|
||||
"environment": {
|
||||
"dependencies": ["pandas", "requests"]
|
||||
|
|
|
|||
|
|
@ -102,10 +102,10 @@ Alternatively, you can use a `fastmcp.json` configuration file (recommended):
|
|||
|
||||
```json fastmcp.json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
"entrypoint": {
|
||||
"file": "server.py",
|
||||
"object": "mcp"
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {
|
||||
"path": "server.py",
|
||||
"entrypoint": "mcp"
|
||||
},
|
||||
"environment": {
|
||||
"dependencies": ["pandas", "requests"]
|
||||
|
|
|
|||
|
|
@ -103,10 +103,10 @@ Alternatively, you can use a `fastmcp.json` configuration file (recommended):
|
|||
|
||||
```json fastmcp.json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
"entrypoint": {
|
||||
"file": "server.py",
|
||||
"object": "mcp"
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {
|
||||
"path": "server.py",
|
||||
"entrypoint": "mcp"
|
||||
},
|
||||
"environment": {
|
||||
"dependencies": ["pandas", "requests"]
|
||||
|
|
|
|||
|
|
@ -178,10 +178,10 @@ You can also use a `fastmcp.json` configuration file (recommended):
|
|||
|
||||
```json fastmcp.json
|
||||
{
|
||||
"$schema": "https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
"entrypoint": {
|
||||
"file": "server.py",
|
||||
"object": "mcp"
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {
|
||||
"path": "server.py",
|
||||
"entrypoint": "mcp"
|
||||
},
|
||||
"environment": {
|
||||
"dependencies": ["pandas", "matplotlib", "seaborn"]
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ By default, this command runs the server directly in your current Python environ
|
|||
| Path | `--path` | Path to bind to when using http transport (default: `/mcp/` or `/sse/` for SSE) |
|
||||
| Log Level | `--log-level`, `-l` | Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL) |
|
||||
| No Banner | `--no-banner` | Disable the startup banner display |
|
||||
| No Environment | `--skip-env` | Skip environment setup with uv (use when already in a uv environment) |
|
||||
| Python Version | `--python` | Python version to use (e.g., 3.10, 3.11) |
|
||||
| Additional Packages | `--with` | Additional packages to install (can be used multiple times) |
|
||||
| Project Directory | `--project` | Run the command within the given project directory |
|
||||
|
|
@ -57,8 +58,8 @@ By default, this command runs the server directly in your current Python environ
|
|||
|
||||
The `fastmcp run` command supports the following entrypoints:
|
||||
|
||||
1. **[Inferred server instance](#inferred-server-instance)**: `server.py` - imports the module and looks for a FastMCP object named `mcp`, `server`, or `app`. Errors if no such object is found.
|
||||
2. **[Explicit server object](#explicit-server-object)**: `server.py:custom_name` - imports and uses the specified server object
|
||||
1. **[Inferred server instance](#inferred-server-instance)**: `server.py` - imports the module and looks for a FastMCP server instance named `mcp`, `server`, or `app`. Errors if no such object is found.
|
||||
2. **[Explicit server entrypoint](#explicit-server-entrypoint)**: `server.py:custom_name` - imports and uses the specified server entrypoint
|
||||
3. **[Factory function](#factory-function)**: `server.py:create_server` - calls the specified function (sync or async) to create a server instance
|
||||
4. **[Remote server proxy](#remote-server-proxy)**: `https://example.com/mcp-server` - connects to a remote server and creates a **local proxy server**
|
||||
5. **[FastMCP configuration file](#fastmcp-configuration)**: `fastmcp.json` - runs servers using FastMCP's declarative configuration format (auto-detects files in current directory)
|
||||
|
|
@ -68,7 +69,7 @@ The `fastmcp run` command supports the following entrypoints:
|
|||
Note: When using `fastmcp run` with a local file, it **completely ignores** the `if __name__ == "__main__"` block. This means:
|
||||
- Any setup code in `__main__` will NOT run
|
||||
- Server configuration in `__main__` is bypassed
|
||||
- `fastmcp run` finds your server object/factory and runs it with its own transport settings
|
||||
- `fastmcp run` finds your server entrypoint/factory and runs it with its own transport settings
|
||||
|
||||
If you need setup code to run, use the **factory pattern** instead.
|
||||
</Warning>
|
||||
|
|
@ -91,9 +92,9 @@ You can run it with:
|
|||
fastmcp run server.py
|
||||
```
|
||||
|
||||
#### Explicit Server Object
|
||||
#### Explicit Server Entrypoint
|
||||
|
||||
If your server is stored as a variable with a custom name, or you want to be explicit about which server to run, you can use the following syntax to load a specific server object:
|
||||
If your server is stored as a variable with a custom name, or you want to be explicit about which server to run, you can use the following syntax to load a specific server entrypoint:
|
||||
|
||||
```bash
|
||||
fastmcp run server.py:custom_name
|
||||
|
|
@ -122,7 +123,7 @@ fastmcp run server.py:custom_name
|
|||
|
||||
Since `fastmcp run` ignores the `if __name__ == "__main__"` block, you can use a factory function to run setup code before your server starts. Factory functions are called without any arguments and must return a FastMCP server instance. Both sync and async factory functions are supported.
|
||||
|
||||
The syntax for using a factory function is the same as for an explicit server object: `fastmcp run server.py:factory_fn`. FastMCP will automatically detect that you have identified a function rather than a server Instance
|
||||
The syntax for using a factory function is the same as for an explicit server entrypoint: `fastmcp run server.py:factory_fn`. FastMCP will automatically detect that you have identified a function rather than a server Instance
|
||||
|
||||
For example, if you have a file called `server.py` with the following content:
|
||||
|
||||
|
|
@ -177,8 +178,19 @@ The configuration file handles dependencies, environment variables, and transpor
|
|||
```bash
|
||||
# Override port from config file
|
||||
fastmcp run fastmcp.json --port 8080
|
||||
|
||||
# Skip environment setup when already in a uv environment
|
||||
fastmcp run fastmcp.json --skip-env
|
||||
```
|
||||
|
||||
<Note>
|
||||
The `--skip-env` flag is useful when:
|
||||
- You're already in an activated virtual environment
|
||||
- You're inside a Docker container with pre-installed dependencies
|
||||
- You're in a uv-managed environment (prevents infinite recursion)
|
||||
- You want to test the server without environment setup
|
||||
</Note>
|
||||
|
||||
See [Server Configuration](/deployment/server-configuration) for detailed documentation on fastmcp.json.
|
||||
|
||||
#### MCP Configuration
|
||||
|
|
@ -244,8 +256,8 @@ This command does not support HTTP testing. To test a server over Streamable HTT
|
|||
|
||||
The `dev` command supports local FastMCP server files and configuration:
|
||||
|
||||
1. **Inferred server instance**: `server.py` - imports the module and looks for a FastMCP object named `mcp`, `server`, or `app`. Errors if no such object is found.
|
||||
2. **Explicit server object**: `server.py:custom_name` - imports and uses the specified server object
|
||||
1. **Inferred server instance**: `server.py` - imports the module and looks for a FastMCP server instance named `mcp`, `server`, or `app`. Errors if no such object is found.
|
||||
2. **Explicit server entrypoint**: `server.py:custom_name` - imports and uses the specified server entrypoint
|
||||
3. **Factory function**: `server.py:create_server` - calls the specified function (sync or async) to create a server instance
|
||||
4. **FastMCP configuration**: `fastmcp.json` - uses FastMCP's declarative configuration (auto-detects in current directory)
|
||||
|
||||
|
|
@ -323,8 +335,8 @@ Note that for security reasons, MCP clients usually run every server in a comple
|
|||
|
||||
The `install` command supports local FastMCP server files and configuration:
|
||||
|
||||
1. **Inferred server instance**: `server.py` - imports the module and looks for a FastMCP object named `mcp`, `server`, or `app`. Errors if no such object is found.
|
||||
2. **Explicit server object**: `server.py:custom_name` - imports and uses the specified server object
|
||||
1. **Inferred server instance**: `server.py` - imports the module and looks for a FastMCP server instance named `mcp`, `server`, or `app`. Errors if no such object is found.
|
||||
2. **Explicit server entrypoint**: `server.py:custom_name` - imports and uses the specified server entrypoint
|
||||
3. **Factory function**: `server.py:create_server` - calls the specified function (sync or async) to create a server instance
|
||||
4. **FastMCP configuration**: `fastmcp.json` - uses FastMCP's declarative configuration with dependencies and settings
|
||||
|
||||
|
|
@ -339,7 +351,7 @@ The `install` command **only supports local files and fastmcp.json** - no URLs,
|
|||
**Examples**
|
||||
|
||||
```bash
|
||||
# Auto-detects server object (looks for 'mcp', 'server', or 'app')
|
||||
# Auto-detects server entrypoint (looks for 'mcp', 'server', or 'app')
|
||||
fastmcp install claude-desktop server.py
|
||||
|
||||
# Install with fastmcp.json configuration (auto-detects)
|
||||
|
|
@ -348,7 +360,7 @@ fastmcp install claude-desktop
|
|||
# Install with explicit fastmcp.json file
|
||||
fastmcp install claude-desktop my-config.fastmcp.json
|
||||
|
||||
# Uses specific server object
|
||||
# Uses specific server entrypoint
|
||||
fastmcp install claude-desktop server.py:my_server
|
||||
|
||||
# With custom name and dependencies
|
||||
|
|
@ -439,8 +451,8 @@ fastmcp inspect server.py
|
|||
|
||||
The `inspect` command supports local FastMCP server files and configuration:
|
||||
|
||||
1. **Inferred server instance**: `server.py` - imports the module and looks for a FastMCP object named `mcp`, `server`, or `app`. Errors if no such object is found.
|
||||
2. **Explicit server object**: `server.py:custom_name` - imports and uses the specified server object
|
||||
1. **Inferred server instance**: `server.py` - imports the module and looks for a FastMCP server instance named `mcp`, `server`, or `app`. Errors if no such object is found.
|
||||
2. **Explicit server entrypoint**: `server.py:custom_name` - imports and uses the specified server entrypoint
|
||||
3. **Factory function**: `server.py:create_server` - calls the specified function (sync or async) to create a server instance
|
||||
4. **FastMCP configuration**: `fastmcp.json` - inspects servers defined with FastMCP's declarative configuration
|
||||
|
||||
|
|
@ -451,10 +463,10 @@ The `inspect` command **only supports local files and fastmcp.json** - no URLs,
|
|||
**Examples**
|
||||
|
||||
```bash
|
||||
# Auto-detect server object
|
||||
# Auto-detect server entrypoint
|
||||
fastmcp inspect server.py
|
||||
|
||||
# Specify server object
|
||||
# Specify server entrypoint
|
||||
fastmcp inspect server.py:my_server
|
||||
|
||||
# Custom output location
|
||||
|
|
|
|||
348
docs/public/schemas/fastmcp.json/latest.json
Normal file
348
docs/public/schemas/fastmcp.json/latest.json
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
{
|
||||
"$defs": {
|
||||
"Deployment": {
|
||||
"description": "Configuration for server deployment and runtime settings.",
|
||||
"properties": {
|
||||
"transport": {
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"stdio",
|
||||
"http",
|
||||
"sse"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Transport protocol to use",
|
||||
"title": "Transport"
|
||||
},
|
||||
"host": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Host to bind to when using HTTP transport",
|
||||
"examples": [
|
||||
"127.0.0.1",
|
||||
"0.0.0.0",
|
||||
"localhost"
|
||||
],
|
||||
"title": "Host"
|
||||
},
|
||||
"port": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Port to bind to when using HTTP transport",
|
||||
"examples": [
|
||||
8000,
|
||||
3000,
|
||||
5000
|
||||
],
|
||||
"title": "Port"
|
||||
},
|
||||
"path": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "URL path for the server endpoint",
|
||||
"examples": [
|
||||
"/mcp/",
|
||||
"/api/mcp/",
|
||||
"/sse/"
|
||||
],
|
||||
"title": "Path"
|
||||
},
|
||||
"log_level": {
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"DEBUG",
|
||||
"INFO",
|
||||
"WARNING",
|
||||
"ERROR",
|
||||
"CRITICAL"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Log level for the server",
|
||||
"title": "Log Level"
|
||||
},
|
||||
"cwd": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Working directory for the server process",
|
||||
"examples": [
|
||||
".",
|
||||
"./src",
|
||||
"/app"
|
||||
],
|
||||
"title": "Cwd"
|
||||
},
|
||||
"env": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Environment variables to set when running the server",
|
||||
"examples": [
|
||||
{
|
||||
"API_KEY": "secret",
|
||||
"DEBUG": "true"
|
||||
}
|
||||
],
|
||||
"title": "Env"
|
||||
},
|
||||
"args": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Arguments to pass to the server (after --)",
|
||||
"examples": [
|
||||
[
|
||||
"--config",
|
||||
"config.json",
|
||||
"--debug"
|
||||
]
|
||||
],
|
||||
"title": "Args"
|
||||
}
|
||||
},
|
||||
"title": "Deployment",
|
||||
"type": "object"
|
||||
},
|
||||
"Environment": {
|
||||
"description": "Configuration for Python environment setup.",
|
||||
"properties": {
|
||||
"python": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Python version constraint",
|
||||
"examples": [
|
||||
"3.10",
|
||||
"3.11",
|
||||
"3.12"
|
||||
],
|
||||
"title": "Python"
|
||||
},
|
||||
"dependencies": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Python packages to install with PEP 508 specifiers",
|
||||
"examples": [
|
||||
[
|
||||
"fastmcp>=2.0,<3",
|
||||
"httpx",
|
||||
"pandas>=2.0"
|
||||
]
|
||||
],
|
||||
"title": "Dependencies"
|
||||
},
|
||||
"requirements": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Path to requirements.txt file",
|
||||
"examples": [
|
||||
"requirements.txt",
|
||||
"../requirements/prod.txt"
|
||||
],
|
||||
"title": "Requirements"
|
||||
},
|
||||
"project": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Path to project directory containing pyproject.toml",
|
||||
"examples": [
|
||||
".",
|
||||
"../my-project"
|
||||
],
|
||||
"title": "Project"
|
||||
},
|
||||
"editable": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Directory to install in editable mode",
|
||||
"examples": [
|
||||
".",
|
||||
"../my-package"
|
||||
],
|
||||
"title": "Editable"
|
||||
}
|
||||
},
|
||||
"title": "Environment",
|
||||
"type": "object"
|
||||
},
|
||||
"FileSystemSource": {
|
||||
"description": "Source for local Python files.",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "filesystem",
|
||||
"default": "filesystem",
|
||||
"description": "Source type",
|
||||
"title": "Type",
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"description": "Path to Python file containing the server",
|
||||
"title": "Path",
|
||||
"type": "string"
|
||||
},
|
||||
"entrypoint": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Name of server instance or factory function (a no-arg function that returns a FastMCP server)",
|
||||
"title": "Entrypoint"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"title": "FileSystemSource",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"description": "Configuration file for FastMCP servers",
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"description": "JSON schema for IDE support and validation",
|
||||
"title": "$Schema"
|
||||
},
|
||||
"source": {
|
||||
"$ref": "#/$defs/FileSystemSource",
|
||||
"description": "Source configuration for the server",
|
||||
"examples": [
|
||||
{
|
||||
"path": "server.py"
|
||||
},
|
||||
{
|
||||
"entrypoint": "app",
|
||||
"path": "server.py"
|
||||
},
|
||||
{
|
||||
"entrypoint": "mcp",
|
||||
"path": "src/server.py",
|
||||
"type": "filesystem"
|
||||
}
|
||||
]
|
||||
},
|
||||
"environment": {
|
||||
"$ref": "#/$defs/Environment",
|
||||
"description": "Python environment setup configuration"
|
||||
},
|
||||
"deployment": {
|
||||
"$ref": "#/$defs/Deployment",
|
||||
"description": "Server deployment and runtime settings"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"source"
|
||||
],
|
||||
"title": "FastMCP Configuration",
|
||||
"type": "object",
|
||||
"$id": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json"
|
||||
}
|
||||
348
docs/public/schemas/fastmcp.json/v1.json
Normal file
348
docs/public/schemas/fastmcp.json/v1.json
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
{
|
||||
"$defs": {
|
||||
"Deployment": {
|
||||
"description": "Configuration for server deployment and runtime settings.",
|
||||
"properties": {
|
||||
"transport": {
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"stdio",
|
||||
"http",
|
||||
"sse"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Transport protocol to use",
|
||||
"title": "Transport"
|
||||
},
|
||||
"host": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Host to bind to when using HTTP transport",
|
||||
"examples": [
|
||||
"127.0.0.1",
|
||||
"0.0.0.0",
|
||||
"localhost"
|
||||
],
|
||||
"title": "Host"
|
||||
},
|
||||
"port": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Port to bind to when using HTTP transport",
|
||||
"examples": [
|
||||
8000,
|
||||
3000,
|
||||
5000
|
||||
],
|
||||
"title": "Port"
|
||||
},
|
||||
"path": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "URL path for the server endpoint",
|
||||
"examples": [
|
||||
"/mcp/",
|
||||
"/api/mcp/",
|
||||
"/sse/"
|
||||
],
|
||||
"title": "Path"
|
||||
},
|
||||
"log_level": {
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"DEBUG",
|
||||
"INFO",
|
||||
"WARNING",
|
||||
"ERROR",
|
||||
"CRITICAL"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Log level for the server",
|
||||
"title": "Log Level"
|
||||
},
|
||||
"cwd": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Working directory for the server process",
|
||||
"examples": [
|
||||
".",
|
||||
"./src",
|
||||
"/app"
|
||||
],
|
||||
"title": "Cwd"
|
||||
},
|
||||
"env": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Environment variables to set when running the server",
|
||||
"examples": [
|
||||
{
|
||||
"API_KEY": "secret",
|
||||
"DEBUG": "true"
|
||||
}
|
||||
],
|
||||
"title": "Env"
|
||||
},
|
||||
"args": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Arguments to pass to the server (after --)",
|
||||
"examples": [
|
||||
[
|
||||
"--config",
|
||||
"config.json",
|
||||
"--debug"
|
||||
]
|
||||
],
|
||||
"title": "Args"
|
||||
}
|
||||
},
|
||||
"title": "Deployment",
|
||||
"type": "object"
|
||||
},
|
||||
"Environment": {
|
||||
"description": "Configuration for Python environment setup.",
|
||||
"properties": {
|
||||
"python": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Python version constraint",
|
||||
"examples": [
|
||||
"3.10",
|
||||
"3.11",
|
||||
"3.12"
|
||||
],
|
||||
"title": "Python"
|
||||
},
|
||||
"dependencies": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Python packages to install with PEP 508 specifiers",
|
||||
"examples": [
|
||||
[
|
||||
"fastmcp>=2.0,<3",
|
||||
"httpx",
|
||||
"pandas>=2.0"
|
||||
]
|
||||
],
|
||||
"title": "Dependencies"
|
||||
},
|
||||
"requirements": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Path to requirements.txt file",
|
||||
"examples": [
|
||||
"requirements.txt",
|
||||
"../requirements/prod.txt"
|
||||
],
|
||||
"title": "Requirements"
|
||||
},
|
||||
"project": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Path to project directory containing pyproject.toml",
|
||||
"examples": [
|
||||
".",
|
||||
"../my-project"
|
||||
],
|
||||
"title": "Project"
|
||||
},
|
||||
"editable": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Directory to install in editable mode",
|
||||
"examples": [
|
||||
".",
|
||||
"../my-package"
|
||||
],
|
||||
"title": "Editable"
|
||||
}
|
||||
},
|
||||
"title": "Environment",
|
||||
"type": "object"
|
||||
},
|
||||
"FileSystemSource": {
|
||||
"description": "Source for local Python files.",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "filesystem",
|
||||
"default": "filesystem",
|
||||
"description": "Source type",
|
||||
"title": "Type",
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"description": "Path to Python file containing the server",
|
||||
"title": "Path",
|
||||
"type": "string"
|
||||
},
|
||||
"entrypoint": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Name of server instance or factory function (a no-arg function that returns a FastMCP server)",
|
||||
"title": "Entrypoint"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"title": "FileSystemSource",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"description": "Configuration file for FastMCP servers",
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"description": "JSON schema for IDE support and validation",
|
||||
"title": "$Schema"
|
||||
},
|
||||
"source": {
|
||||
"$ref": "#/$defs/FileSystemSource",
|
||||
"description": "Source configuration for the server",
|
||||
"examples": [
|
||||
{
|
||||
"path": "server.py"
|
||||
},
|
||||
{
|
||||
"entrypoint": "app",
|
||||
"path": "server.py"
|
||||
},
|
||||
{
|
||||
"entrypoint": "mcp",
|
||||
"path": "src/server.py",
|
||||
"type": "filesystem"
|
||||
}
|
||||
]
|
||||
},
|
||||
"environment": {
|
||||
"$ref": "#/$defs/Environment",
|
||||
"description": "Python environment setup configuration"
|
||||
},
|
||||
"deployment": {
|
||||
"$ref": "#/$defs/Deployment",
|
||||
"description": "Server deployment and runtime settings"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"source"
|
||||
],
|
||||
"title": "FastMCP Configuration",
|
||||
"type": "object",
|
||||
"$id": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json"
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ Claude app integration utilities.
|
|||
|
||||
## Functions
|
||||
|
||||
### `get_claude_config_path` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/claude.py#L14" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `get_claude_config_path` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/claude.py#L15" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_claude_config_path() -> Path | None
|
||||
|
|
@ -20,7 +20,7 @@ get_claude_config_path() -> Path | None
|
|||
Get the Claude config directory based on platform.
|
||||
|
||||
|
||||
### `update_claude_config` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/claude.py#L32" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `update_claude_config` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/claude.py#L33" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
update_claude_config(file_spec: str, server_name: str) -> bool
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ FastMCP CLI tools using Cyclopts.
|
|||
|
||||
## Functions
|
||||
|
||||
### `version` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L103" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `version` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L63" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
version()
|
||||
|
|
@ -20,45 +20,47 @@ version()
|
|||
Display version information and platform details.
|
||||
|
||||
|
||||
### `dev` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L141" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `dev` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L101" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
dev(server_spec: str) -> None
|
||||
dev(server_spec: str | None = None) -> None
|
||||
```
|
||||
|
||||
|
||||
Run an MCP server with the MCP Inspector for development.
|
||||
|
||||
**Args:**
|
||||
- `server_spec`: Python file to run, optionally with \:object suffix
|
||||
- `server_spec`: Python file to run, optionally with \:object suffix, or None to auto-detect fastmcp.json
|
||||
|
||||
|
||||
### `run` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L286" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `run` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L319" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
run(server_spec: str, *server_args: str) -> None
|
||||
run(server_spec: str | None = None, *server_args: str) -> None
|
||||
```
|
||||
|
||||
|
||||
Run an MCP server or connect to a remote one.
|
||||
|
||||
The server can be specified in four ways:
|
||||
The server can be specified in several ways:
|
||||
1. Module approach: "server.py" - runs the module directly, looking for an object named 'mcp', 'server', or 'app'
|
||||
2. Import approach: "server.py:app" - imports and runs the specified server object
|
||||
3. URL approach: "http://server-url" - connects to a remote server and creates a proxy
|
||||
4. MCPConfig file: "mcp.json" - runs as a proxy server for the MCP Servers in the MCPConfig file
|
||||
5. FastMCP config: "fastmcp.json" - runs server using FastMCP configuration
|
||||
6. No argument: looks for fastmcp.json in current directory
|
||||
|
||||
Server arguments can be passed after -- :
|
||||
fastmcp run server.py -- --config config.json --debug
|
||||
|
||||
**Args:**
|
||||
- `server_spec`: Python file, object specification (file\:obj), MCPConfig file, or URL
|
||||
- `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#L439" 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#L582" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
inspect(server_spec: str) -> None
|
||||
inspect(server_spec: str | None = None) -> None
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -74,7 +76,9 @@ fastmcp inspect server.py
|
|||
fastmcp inspect server.py -o report.json
|
||||
fastmcp inspect server.py:mcp -o analysis.json
|
||||
fastmcp inspect path/to/server.py:app -o /tmp/server-info.json
|
||||
fastmcp inspect fastmcp.json
|
||||
fastmcp inspect # auto-detect fastmcp.json
|
||||
|
||||
**Args:**
|
||||
- `server_spec`: Python file to inspect, optionally with \:object suffix
|
||||
- `server_spec`: Python file to inspect, optionally with \:object suffix, or fastmcp.json
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Claude Code integration for FastMCP install using Cyclopts.
|
|||
|
||||
## Functions
|
||||
|
||||
### `find_claude_command` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/claude_code.py#L19" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `find_claude_command` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/claude_code.py#L20" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
find_claude_command() -> str | None
|
||||
|
|
@ -23,7 +23,7 @@ Checks common installation locations since 'claude' is often a shell alias
|
|||
that doesn't work with subprocess calls.
|
||||
|
||||
|
||||
### `check_claude_code_available` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/claude_code.py#L67" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `check_claude_code_available` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/claude_code.py#L68" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
check_claude_code_available() -> bool
|
||||
|
|
@ -33,7 +33,7 @@ check_claude_code_available() -> bool
|
|||
Check if Claude Code CLI is available.
|
||||
|
||||
|
||||
### `install_claude_code` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/claude_code.py#L72" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `install_claude_code` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/claude_code.py#L73" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
install_claude_code(file: Path, server_object: str | None, name: str) -> bool
|
||||
|
|
@ -57,7 +57,7 @@ Install FastMCP server in Claude Code.
|
|||
- True if installation was successful, False otherwise
|
||||
|
||||
|
||||
### `claude_code_command` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/claude_code.py#L170" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `claude_code_command` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/claude_code.py#L163" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
claude_code_command(server_spec: str) -> None
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Claude Desktop integration for FastMCP install using Cyclopts.
|
|||
|
||||
## Functions
|
||||
|
||||
### `get_claude_config_path` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/claude_desktop.py#L19" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `get_claude_config_path` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/claude_desktop.py#L20" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_claude_config_path() -> Path | None
|
||||
|
|
@ -20,7 +20,7 @@ get_claude_config_path() -> Path | None
|
|||
Get the Claude config directory based on platform.
|
||||
|
||||
|
||||
### `install_claude_desktop` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/claude_desktop.py#L37" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `install_claude_desktop` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/claude_desktop.py#L38" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
install_claude_desktop(file: Path, server_object: str | None, name: str) -> bool
|
||||
|
|
@ -44,7 +44,7 @@ Install FastMCP server in Claude Desktop.
|
|||
- True if installation was successful, False otherwise
|
||||
|
||||
|
||||
### `claude_desktop_command` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/claude_desktop.py#L143" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `claude_desktop_command` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/claude_desktop.py#L135" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
claude_desktop_command(server_spec: str) -> None
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Cursor integration for FastMCP install using Cyclopts.
|
|||
|
||||
## Functions
|
||||
|
||||
### `generate_cursor_deeplink` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/cursor.py#L20" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `generate_cursor_deeplink` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/cursor.py#L21" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
generate_cursor_deeplink(server_name: str, server_config: StdioMCPServer) -> str
|
||||
|
|
@ -27,7 +27,7 @@ Generate a Cursor deeplink for installing the MCP server.
|
|||
- Deeplink URL that can be clicked to install the server
|
||||
|
||||
|
||||
### `open_deeplink` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/cursor.py#L44" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `open_deeplink` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/cursor.py#L45" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
open_deeplink(deeplink: str) -> bool
|
||||
|
|
@ -43,7 +43,32 @@ Attempt to open a deeplink URL using the system's default handler.
|
|||
- True if the command succeeded, False otherwise
|
||||
|
||||
|
||||
### `install_cursor` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/cursor.py#L67" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `install_cursor_workspace` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/cursor.py#L68" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
install_cursor_workspace(file: Path, server_object: str | None, name: str, workspace_path: Path) -> bool
|
||||
```
|
||||
|
||||
|
||||
Install FastMCP server to workspace-specific Cursor configuration.
|
||||
|
||||
**Args:**
|
||||
- `file`: Path to the server file
|
||||
- `server_object`: Optional server object name (for \:object suffix)
|
||||
- `name`: Name for the server in Cursor
|
||||
- `workspace_path`: Path to the workspace directory
|
||||
- `with_editable`: Optional directory to install in editable mode
|
||||
- `with_packages`: Optional list of additional packages to install
|
||||
- `env_vars`: Optional dictionary of environment variables
|
||||
- `python_version`: Optional Python version to use
|
||||
- `with_requirements`: Optional requirements file to install from
|
||||
- `project`: Optional project directory to run within
|
||||
|
||||
**Returns:**
|
||||
- True if installation was successful, False otherwise
|
||||
|
||||
|
||||
### `install_cursor` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/cursor.py#L159" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
install_cursor(file: Path, server_object: str | None, name: str) -> bool
|
||||
|
|
@ -62,12 +87,13 @@ Install FastMCP server in Cursor.
|
|||
- `python_version`: Optional Python version to use
|
||||
- `with_requirements`: Optional requirements file to install from
|
||||
- `project`: Optional project directory to run within
|
||||
- `workspace`: Optional workspace directory for project-specific installation
|
||||
|
||||
**Returns:**
|
||||
- True if installation was successful, False otherwise
|
||||
|
||||
|
||||
### `cursor_command` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/cursor.py#L153" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `cursor_command` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/cursor.py#L254" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
cursor_command(server_spec: str) -> None
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ MCP configuration JSON generation for FastMCP install using Cyclopts.
|
|||
|
||||
## Functions
|
||||
|
||||
### `install_mcp_json` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/mcp_json.py#L19" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `install_mcp_json` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/mcp_json.py#L20" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
install_mcp_json(file: Path, server_object: str | None, name: str) -> bool
|
||||
|
|
@ -35,7 +35,7 @@ Generate MCP configuration JSON for manual installation.
|
|||
- True if generation was successful, False otherwise
|
||||
|
||||
|
||||
### `mcp_json_command` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/mcp_json.py#L116" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `mcp_json_command` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/mcp_json.py#L108" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
mcp_json_command(server_spec: str) -> None
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Shared utilities for install commands.
|
|||
|
||||
## Functions
|
||||
|
||||
### `parse_env_var` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/shared.py#L15" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `parse_env_var` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/shared.py#L18" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
parse_env_var(env_var: str) -> tuple[str, str]
|
||||
|
|
@ -20,7 +20,7 @@ parse_env_var(env_var: str) -> tuple[str, str]
|
|||
Parse environment variable string in format KEY=VALUE.
|
||||
|
||||
|
||||
### `process_common_args` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/shared.py#L26" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `process_common_args` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/shared.py#L29" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
process_common_args(server_spec: str, server_name: str | None, with_packages: list[str], env_vars: list[str], env_file: Path | None) -> tuple[Path, str | None, str, list[str], dict[str, str] | None]
|
||||
|
|
@ -29,3 +29,5 @@ process_common_args(server_spec: str, server_name: str | None, with_packages: li
|
|||
|
||||
Process common arguments shared by all install commands.
|
||||
|
||||
Handles both fastmcp.json config files and traditional file.py:object syntax.
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ FastMCP run command implementation with enhanced type hints.
|
|||
|
||||
## Functions
|
||||
|
||||
### `is_url` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L25" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `is_url` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L32" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
is_url(path: str) -> bool
|
||||
|
|
@ -20,7 +20,7 @@ is_url(path: str) -> bool
|
|||
Check if a string is a URL.
|
||||
|
||||
|
||||
### `parse_file_path` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L31" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `parse_file_path` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L38" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
parse_file_path(server_spec: str) -> tuple[Path, str | None]
|
||||
|
|
@ -36,7 +36,7 @@ Parse a file path that may include a server object specification.
|
|||
- Tuple of (file_path, server_object)
|
||||
|
||||
|
||||
### `import_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L62" 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/cli/run.py#L69" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
import_server(file: Path, server_or_factory: str | None = None) -> Any
|
||||
|
|
@ -53,17 +53,17 @@ Import a MCP server from a file.
|
|||
- The server object (or result of calling a factory function)
|
||||
|
||||
|
||||
### `run_with_uv` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L174" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `run_with_uv` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L182" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
run_with_uv(server_spec: str, python_version: str | None = None, with_packages: list[str] | None = None, with_requirements: Path | None = None, project: Path | None = None, transport: TransportType | None = None, host: str | None = None, port: int | None = None, path: str | None = None, log_level: LogLevelType | None = None, show_banner: bool = True) -> None
|
||||
run_with_uv(server_spec: str, python_version: str | None = None, with_packages: list[str] | None = None, with_requirements: Path | None = None, project: Path | None = None, transport: TransportType | None = None, host: str | None = None, port: int | None = None, path: str | None = None, log_level: LogLevelType | None = None, show_banner: bool = True, editable: str | None = None) -> None
|
||||
```
|
||||
|
||||
|
||||
Run a MCP server using uv run subprocess.
|
||||
|
||||
**Args:**
|
||||
- `server_spec`: Python file, object specification (file\:obj), or URL
|
||||
- `server_spec`: Python file, object specification (file\:obj), config file, or URL
|
||||
- `python_version`: Python version to use (e.g. "3.10")
|
||||
- `with_packages`: Additional packages to install
|
||||
- `with_requirements`: Requirements file to use
|
||||
|
|
@ -76,7 +76,7 @@ Run a MCP server using uv run subprocess.
|
|||
- `show_banner`: Whether to show the server banner
|
||||
|
||||
|
||||
### `create_client_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L252" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `create_client_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L313" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
create_client_server(url: str) -> Any
|
||||
|
|
@ -92,7 +92,7 @@ Create a FastMCP server from a client URL.
|
|||
- A FastMCP server instance
|
||||
|
||||
|
||||
### `create_mcp_config_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L272" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `create_mcp_config_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L333" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
create_mcp_config_server(mcp_config_path: Path) -> FastMCP[None]
|
||||
|
|
@ -102,7 +102,23 @@ create_mcp_config_server(mcp_config_path: Path) -> FastMCP[None]
|
|||
Create a FastMCP server from a MCPConfig.
|
||||
|
||||
|
||||
### `import_server_with_args` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L283" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `load_fastmcp_config` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L344" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
load_fastmcp_config(config_path: Path) -> FastMCPConfig
|
||||
```
|
||||
|
||||
|
||||
Load a FastMCP configuration from a fastmcp.json file.
|
||||
|
||||
**Args:**
|
||||
- `config_path`: Path to fastmcp.json file
|
||||
|
||||
**Returns:**
|
||||
- FastMCPConfig object
|
||||
|
||||
|
||||
### `import_server_with_args` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L362" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
import_server_with_args(file: Path, server_or_factory: str | None = None, server_args: list[str] | None = None) -> Any
|
||||
|
|
@ -120,7 +136,7 @@ Import a server with optional command line arguments.
|
|||
- The imported server object
|
||||
|
||||
|
||||
### `run_command` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L309" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `run_command` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L388" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
run_command(server_spec: str, transport: TransportType | None = None, host: str | None = None, port: int | None = None, path: str | None = None, log_level: LogLevelType | None = None, server_args: list[str] | None = None, show_banner: bool = True, use_direct_import: bool = False) -> None
|
||||
|
|
@ -130,7 +146,7 @@ run_command(server_spec: str, transport: TransportType | None = None, host: str
|
|||
Run a MCP server or connect to a remote one.
|
||||
|
||||
**Args:**
|
||||
- `server_spec`: Python file, object specification (file\:obj), MCPConfig file, or URL
|
||||
- `server_spec`: Python file, object specification (file\:obj), config file, or URL
|
||||
- `transport`: Transport protocol to use
|
||||
- `host`: Host to bind to when using http transport
|
||||
- `port`: Port to bind to when using http transport
|
||||
|
|
@ -141,7 +157,7 @@ Run a MCP server or connect to a remote one.
|
|||
- `use_direct_import`: Whether to use direct import instead of subprocess
|
||||
|
||||
|
||||
### `run_v1_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L374" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `run_v1_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L496" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
run_v1_server(server: FastMCP1x, host: str | None = None, port: int | None = None, transport: TransportType | None = None) -> None
|
||||
|
|
|
|||
|
|
@ -122,7 +122,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#L250" 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#L255" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
redirect_handler(self, authorization_url: str) -> None
|
||||
|
|
@ -131,7 +131,7 @@ redirect_handler(self, authorization_url: str) -> None
|
|||
Open browser for authorization.
|
||||
|
||||
|
||||
#### `callback_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L255" 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#L260" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
callback_handler(self) -> tuple[str, str | None]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ sidebarTitle: client
|
|||
|
||||
## Classes
|
||||
|
||||
### `ClientSessionState` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L74" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ClientSessionState` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L80" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Holds all session-related state for a Client instance.
|
||||
|
|
@ -16,7 +16,7 @@ This allows clean separation of configuration (which is copied) from
|
|||
session state (which should be fresh for each new client instance).
|
||||
|
||||
|
||||
### `Client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L90" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `Client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L96" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
MCP client that delegates connection management to a Transport instance.
|
||||
|
|
@ -79,7 +79,7 @@ async with client:
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L273" 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/client/client.py#L282" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
session(self) -> ClientSession
|
||||
|
|
@ -88,7 +88,7 @@ session(self) -> ClientSession
|
|||
Get the current active session. Raises RuntimeError if not connected.
|
||||
|
||||
|
||||
#### `initialize_result` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L283" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `initialize_result` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L292" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
initialize_result(self) -> mcp.types.InitializeResult
|
||||
|
|
@ -97,7 +97,7 @@ initialize_result(self) -> mcp.types.InitializeResult
|
|||
Get the result of the initialization request.
|
||||
|
||||
|
||||
#### `set_roots` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L291" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_roots` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L300" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_roots(self, roots: RootsList | RootsHandler) -> None
|
||||
|
|
@ -106,16 +106,16 @@ set_roots(self, roots: RootsList | RootsHandler) -> None
|
|||
Set the roots for the client. This does not automatically call `send_roots_list_changed`.
|
||||
|
||||
|
||||
#### `set_sampling_callback` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L295" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_sampling_callback` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L304" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_sampling_callback(self, sampling_callback: SamplingHandler) -> None
|
||||
set_sampling_callback(self, sampling_callback: ClientSamplingHandler) -> None
|
||||
```
|
||||
|
||||
Set the sampling callback for the client.
|
||||
|
||||
|
||||
#### `set_elicitation_callback` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L301" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_elicitation_callback` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L310" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_elicitation_callback(self, elicitation_callback: ElicitationHandler) -> None
|
||||
|
|
@ -124,7 +124,7 @@ set_elicitation_callback(self, elicitation_callback: ElicitationHandler) -> None
|
|||
Set the elicitation callback for the client.
|
||||
|
||||
|
||||
#### `is_connected` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L309" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `is_connected` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L318" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
is_connected(self) -> bool
|
||||
|
|
@ -133,7 +133,7 @@ is_connected(self) -> bool
|
|||
Check if the client is currently connected.
|
||||
|
||||
|
||||
#### `new` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L313" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `new` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L322" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
new(self) -> Client[ClientTransportT]
|
||||
|
|
@ -149,13 +149,13 @@ share state with the original client.
|
|||
- A new Client instance with the same configuration but disconnected state.
|
||||
|
||||
|
||||
#### `close` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L476" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `close` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L487" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
close(self)
|
||||
```
|
||||
|
||||
#### `ping` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L482" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `ping` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L493" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
ping(self) -> bool
|
||||
|
|
@ -164,7 +164,7 @@ ping(self) -> bool
|
|||
Send a ping request.
|
||||
|
||||
|
||||
#### `cancel` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L487" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `cancel` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L498" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
cancel(self, request_id: str | int, reason: str | None = None) -> None
|
||||
|
|
@ -173,7 +173,7 @@ cancel(self, request_id: str | int, reason: str | None = None) -> None
|
|||
Send a cancellation notification for an in-progress request.
|
||||
|
||||
|
||||
#### `progress` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L504" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `progress` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L515" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
progress(self, progress_token: str | int, progress: float, total: float | None = None, message: str | None = None) -> None
|
||||
|
|
@ -182,7 +182,7 @@ progress(self, progress_token: str | int, progress: float, total: float | None =
|
|||
Send a progress notification.
|
||||
|
||||
|
||||
#### `set_logging_level` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L516" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_logging_level` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L527" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_logging_level(self, level: mcp.types.LoggingLevel) -> None
|
||||
|
|
@ -191,7 +191,7 @@ set_logging_level(self, level: mcp.types.LoggingLevel) -> None
|
|||
Send a logging/setLevel request.
|
||||
|
||||
|
||||
#### `send_roots_list_changed` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L520" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `send_roots_list_changed` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L531" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
send_roots_list_changed(self) -> None
|
||||
|
|
@ -200,7 +200,7 @@ send_roots_list_changed(self) -> None
|
|||
Send a roots/list_changed notification.
|
||||
|
||||
|
||||
#### `list_resources_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L526" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_resources_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L537" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_resources_mcp(self) -> mcp.types.ListResourcesResult
|
||||
|
|
@ -216,7 +216,7 @@ containing the list of resources and any additional metadata.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `list_resources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L539" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_resources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L552" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_resources(self) -> list[mcp.types.Resource]
|
||||
|
|
@ -231,7 +231,7 @@ Retrieve a list of resources available on the server.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `list_resource_templates_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L551" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_resource_templates_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L564" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_resource_templates_mcp(self) -> mcp.types.ListResourceTemplatesResult
|
||||
|
|
@ -247,7 +247,7 @@ containing the list of resource templates and any additional metadata.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `list_resource_templates` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L566" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_resource_templates` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L581" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_resource_templates(self) -> list[mcp.types.ResourceTemplate]
|
||||
|
|
@ -262,7 +262,7 @@ Retrieve a list of resource templates available on the server.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `read_resource_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L580" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `read_resource_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L595" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
read_resource_mcp(self, uri: AnyUrl | str) -> mcp.types.ReadResourceResult
|
||||
|
|
@ -281,7 +281,7 @@ containing the resource contents and any additional metadata.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `read_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L600" 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/client/client.py#L617" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
read_resource(self, uri: AnyUrl | str) -> list[mcp.types.TextResourceContents | mcp.types.BlobResourceContents]
|
||||
|
|
@ -300,7 +300,7 @@ objects, typically containing either text or binary data.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `list_prompts_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L639" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_prompts_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L656" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_prompts_mcp(self) -> mcp.types.ListPromptsResult
|
||||
|
|
@ -316,7 +316,7 @@ containing the list of prompts and any additional metadata.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `list_prompts` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L652" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_prompts` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L671" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_prompts(self) -> list[mcp.types.Prompt]
|
||||
|
|
@ -331,7 +331,7 @@ Retrieve a list of prompts available on the server.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `get_prompt_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L665" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_prompt_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L684" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_prompt_mcp(self, name: str, arguments: dict[str, Any] | None = None) -> mcp.types.GetPromptResult
|
||||
|
|
@ -351,7 +351,7 @@ containing the prompt messages and any additional metadata.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `get_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L699" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L720" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> mcp.types.GetPromptResult
|
||||
|
|
@ -371,7 +371,7 @@ containing the prompt messages and any additional metadata.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `complete_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L720" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `complete_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L741" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
complete_mcp(self, ref: mcp.types.ResourceReference | mcp.types.PromptReference, argument: dict[str, str]) -> mcp.types.CompleteResult
|
||||
|
|
@ -391,7 +391,7 @@ 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#L741" 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#L764" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
complete(self, ref: mcp.types.ResourceReference | mcp.types.PromptReference, argument: dict[str, str]) -> mcp.types.Completion
|
||||
|
|
@ -410,7 +410,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#L763" 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#L786" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_tools_mcp(self) -> mcp.types.ListToolsResult
|
||||
|
|
@ -426,7 +426,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#L776" 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#L801" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_tools(self) -> list[mcp.types.Tool]
|
||||
|
|
@ -441,7 +441,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#L790" 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#L815" 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 +466,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#L826" 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#L852" 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,4 +496,10 @@ raw result object.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
### `CallToolResult` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L898" 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#L923" 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#L932" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ sidebarTitle: sampling
|
|||
|
||||
## Functions
|
||||
|
||||
### `create_sampling_callback` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/sampling.py#L25" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `create_sampling_callback` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/sampling.py#L31" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
create_sampling_callback(sampling_handler: SamplingHandler) -> SamplingFnT
|
||||
create_sampling_callback(sampling_handler: ClientSamplingHandler[LifespanContextT]) -> SamplingFnT
|
||||
```
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ sidebarTitle: transports
|
|||
|
||||
## Functions
|
||||
|
||||
### `infer_transport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L950" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `infer_transport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L971" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
infer_transport(transport: ClientTransport | FastMCP | FastMCP1Server | AnyUrl | Path | MCPConfig | dict[str, Any] | str) -> ClientTransport
|
||||
|
|
@ -57,13 +57,13 @@ transport = infer_transport(config)
|
|||
|
||||
## Classes
|
||||
|
||||
### `SessionKwargs` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L59" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `SessionKwargs` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L63" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Keyword arguments for the MCP ClientSession constructor.
|
||||
|
||||
|
||||
### `ClientTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L71" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ClientTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L75" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Abstract base class for different MCP client transport mechanisms.
|
||||
|
|
@ -74,7 +74,7 @@ to an MCP server, and providing a ClientSession within an async context.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L82" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L86" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
|
||||
|
|
@ -93,7 +93,7 @@ within this context.
|
|||
constructor (e.g., callbacks, timeouts).
|
||||
|
||||
|
||||
#### `close` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L108" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `close` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L112" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
close(self)
|
||||
|
|
@ -102,7 +102,7 @@ close(self)
|
|||
Close the transport.
|
||||
|
||||
|
||||
### `WSTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L117" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `WSTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L121" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport implementation that connects to an MCP server via WebSockets.
|
||||
|
|
@ -110,13 +110,13 @@ Transport implementation that connects to an MCP server via WebSockets.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L135" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L139" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
|
||||
```
|
||||
|
||||
### `SSETransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L156" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `SSETransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L160" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport implementation that connects to an MCP server via Server-Sent Events.
|
||||
|
|
@ -124,13 +124,13 @@ Transport implementation that connects to an MCP server via Server-Sent Events.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L192" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L196" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
|
||||
```
|
||||
|
||||
### `StreamableHttpTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L228" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `StreamableHttpTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L230" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport implementation that connects to an MCP server via Streamable HTTP Requests.
|
||||
|
|
@ -138,7 +138,7 @@ Transport implementation that connects to an MCP server via Streamable HTTP Requ
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L264" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L266" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
|
||||
|
|
@ -179,43 +179,43 @@ disconnect(self)
|
|||
close(self)
|
||||
```
|
||||
|
||||
### `PythonStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L465" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `PythonStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L463" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport for running Python scripts.
|
||||
|
||||
|
||||
### `FastMCPStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L511" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `FastMCPStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L509" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport for running FastMCP servers using the FastMCP CLI.
|
||||
|
||||
|
||||
### `NodeStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L538" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `NodeStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L536" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport for running Node.js scripts.
|
||||
|
||||
|
||||
### `UvStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L580" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `UvStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L578" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport for running commands via the uv tool.
|
||||
|
||||
|
||||
### `UvxStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L637" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `UvxStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L657" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport for running commands via the uvx tool.
|
||||
|
||||
|
||||
### `NpxStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L701" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `NpxStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L721" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport for running commands via the npx tool.
|
||||
|
||||
|
||||
### `FastMCPTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L763" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `FastMCPTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L783" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
In-memory transport for FastMCP servers.
|
||||
|
|
@ -228,13 +228,13 @@ tests or scenarios where client and server run in the same runtime.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L782" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L802" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
|
||||
```
|
||||
|
||||
### `MCPConfigTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L817" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `MCPConfigTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L837" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport for connecting to one or more MCP servers defined in an MCPConfig.
|
||||
|
|
@ -287,13 +287,13 @@ async with client:
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L898" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L919" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
|
||||
```
|
||||
|
||||
#### `close` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L904" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `close` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L925" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
close(self)
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ Example configuration:
|
|||
|
||||
## Functions
|
||||
|
||||
### `infer_transport_type_from_url` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L57" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `infer_transport_type_from_url` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L56" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
infer_transport_type_from_url(url: str | AnyUrl) -> Literal['http', 'sse']
|
||||
|
|
@ -42,7 +42,7 @@ infer_transport_type_from_url(url: str | AnyUrl) -> Literal['http', 'sse']
|
|||
Infer the appropriate transport type from the given URL.
|
||||
|
||||
|
||||
### `update_config_file` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L300" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `update_config_file` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L313" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
update_config_file(file_path: Path, server_name: str, server_config: CanonicalMCPServerTypes) -> None
|
||||
|
|
@ -57,7 +57,7 @@ worry about transforming server objects here.
|
|||
|
||||
## Classes
|
||||
|
||||
### `StdioMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L117" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `StdioMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L126" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
MCP server configuration for stdio transport.
|
||||
|
|
@ -67,19 +67,19 @@ This is the canonical configuration format for MCP servers using stdio transport
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `to_transport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L147" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `to_transport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L156" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
to_transport(self) -> StdioTransport
|
||||
```
|
||||
|
||||
### `TransformingStdioMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L158" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `TransformingStdioMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L167" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
A Stdio server with tool transforms.
|
||||
|
||||
|
||||
### `RemoteMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L162" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `RemoteMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L171" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
MCP server configuration for HTTP/SSE transport.
|
||||
|
|
@ -89,19 +89,19 @@ This is the canonical configuration format for MCP servers using remote transpor
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `to_transport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L198" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `to_transport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L207" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
to_transport(self) -> StreamableHttpTransport | SSETransport
|
||||
```
|
||||
|
||||
### `TransformingRemoteMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L223" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `TransformingRemoteMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L232" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
A Remote server with tool transforms.
|
||||
|
||||
|
||||
### `MCPConfig` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L234" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `MCPConfig` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L243" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
A configuration object for MCP Servers that conforms to the canonical MCP configuration format
|
||||
|
|
@ -113,16 +113,16 @@ For an MCPConfig that is strictly canonical, see the `CanonicalMCPConfig` class.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `validate_mcp_servers` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L247" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `wrap_servers_at_root` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L257" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
validate_mcp_servers(self, info: ValidationInfo) -> dict[str, Any]
|
||||
wrap_servers_at_root(cls, values: dict[str, Any]) -> dict[str, Any]
|
||||
```
|
||||
|
||||
Validate the MCP servers.
|
||||
If there's no mcpServers key but there are server configs at root, wrap them.
|
||||
|
||||
|
||||
#### `add_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L257" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `add_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L270" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
add_server(self, name: str, server: MCPServerTypes) -> None
|
||||
|
|
@ -131,7 +131,7 @@ add_server(self, name: str, server: MCPServerTypes) -> None
|
|||
Add or update a server in the configuration.
|
||||
|
||||
|
||||
#### `from_dict` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L262" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `from_dict` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L275" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
from_dict(cls, config: dict[str, Any]) -> Self
|
||||
|
|
@ -140,7 +140,7 @@ from_dict(cls, config: dict[str, Any]) -> Self
|
|||
Parse MCP configuration from dictionary format.
|
||||
|
||||
|
||||
#### `to_dict` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L266" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `to_dict` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L279" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
to_dict(self) -> dict[str, Any]
|
||||
|
|
@ -149,7 +149,7 @@ to_dict(self) -> dict[str, Any]
|
|||
Convert MCPConfig to dictionary format, preserving all fields.
|
||||
|
||||
|
||||
#### `write_to_file` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L270" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `write_to_file` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L283" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
write_to_file(self, file_path: Path) -> None
|
||||
|
|
@ -158,7 +158,7 @@ write_to_file(self, file_path: Path) -> None
|
|||
Write configuration to JSON file.
|
||||
|
||||
|
||||
#### `from_file` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L276" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `from_file` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L289" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
from_file(cls, file_path: Path) -> Self
|
||||
|
|
@ -167,7 +167,7 @@ from_file(cls, file_path: Path) -> Self
|
|||
Load configuration from JSON file.
|
||||
|
||||
|
||||
### `CanonicalMCPConfig` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L285" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `CanonicalMCPConfig` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L298" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Canonical MCP configuration format.
|
||||
|
|
@ -178,7 +178,7 @@ The format is designed to be client-agnostic and extensible for future use cases
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `add_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L295" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `add_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L308" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
add_server(self, name: str, server: CanonicalMCPServerTypes) -> None
|
||||
|
|
|
|||
|
|
@ -26,10 +26,10 @@ 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#L54" 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#L55" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Client for DCR proxy that accepts any localhost redirect URI.
|
||||
Client for DCR proxy with configurable redirect URI validation.
|
||||
|
||||
This special client class is critical for the OAuth proxy to work correctly
|
||||
with Dynamic Client Registration (DCR). Here's why it exists:
|
||||
|
|
@ -38,39 +38,39 @@ Problem:
|
|||
--------
|
||||
When MCP clients use OAuth, they dynamically register with random localhost
|
||||
ports (e.g., http://localhost:55454/callback). The OAuth proxy needs to:
|
||||
1. Accept these dynamic redirect URIs from clients
|
||||
1. Accept these dynamic redirect URIs from clients based on configured patterns
|
||||
2. Use its own fixed redirect URI with the upstream provider (Google, GitHub, etc.)
|
||||
3. Forward the authorization code back to the client's dynamic URI
|
||||
|
||||
Solution:
|
||||
---------
|
||||
This class overrides redirect_uri validation to accept ANY localhost URI,
|
||||
This class validates redirect URIs against configurable patterns,
|
||||
while the proxy internally uses its own fixed redirect URI with the upstream
|
||||
provider. This allows the flow to work even when clients reconnect with
|
||||
different ports or when tokens are cached.
|
||||
|
||||
Without this class, clients would get "Redirect URI not registered" errors
|
||||
when trying to authenticate with cached tokens, because the stored client
|
||||
would have fixed redirect URIs that don't match the new dynamic port.
|
||||
Without proper validation, clients could get "Redirect URI not registered" errors
|
||||
when trying to authenticate with cached tokens, or security vulnerabilities could
|
||||
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#L80" 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#L94" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
validate_redirect_uri(self, redirect_uri: AnyUrl | None) -> AnyUrl
|
||||
```
|
||||
|
||||
Accept any localhost redirect URI for DCR clients.
|
||||
Validate redirect URI against allowed patterns.
|
||||
|
||||
Since we're acting as a proxy and clients register dynamically,
|
||||
we need to accept their localhost redirect URIs even though they're
|
||||
not pre-registered with us. This is essential for cached token
|
||||
scenarios where the client may reconnect with a different port.
|
||||
we validate their redirect URIs against configurable patterns.
|
||||
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#L107" 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#L120" 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.
|
||||
|
|
@ -182,7 +182,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#L333" 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#L354" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_client(self, client_id: str) -> OAuthClientInformationFull | None
|
||||
|
|
@ -199,7 +199,7 @@ handles the case where a client with cached tokens reconnects
|
|||
on a different port.
|
||||
|
||||
|
||||
#### `register_client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L364" 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#L386" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
register_client(self, client_info: OAuthClientInformationFull) -> None
|
||||
|
|
@ -226,7 +226,7 @@ The flow:
|
|||
4. When client reconnects with a different port, ProxyDCRClient accepts it
|
||||
|
||||
|
||||
#### `authorize` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L421" 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#L444" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str
|
||||
|
|
@ -240,7 +240,7 @@ This implements the DCR-compliant proxy pattern:
|
|||
3. Redirect to IdP with our fixed callback URL
|
||||
|
||||
|
||||
#### `load_authorization_code` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L478" 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#L501" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
load_authorization_code(self, client: OAuthClientInformationFull, authorization_code: str) -> AuthorizationCode | None
|
||||
|
|
@ -252,7 +252,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#L520" 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#L543" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
exchange_authorization_code(self, client: OAuthClientInformationFull, authorization_code: AuthorizationCode) -> OAuthToken
|
||||
|
|
@ -264,7 +264,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#L587" 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#L610" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
load_refresh_token(self, client: OAuthClientInformationFull, refresh_token: str) -> RefreshToken | None
|
||||
|
|
@ -273,7 +273,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#L595" 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#L618" 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
|
||||
|
|
@ -282,7 +282,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#L670" 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#L693" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
load_access_token(self, token: str) -> AccessToken | None
|
||||
|
|
@ -294,7 +294,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#L687" 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#L710" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
revoke_token(self, token: AccessToken | RefreshToken) -> None
|
||||
|
|
@ -306,7 +306,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#L880" 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#L903" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_routes(self) -> list[Route]
|
||||
|
|
|
|||
|
|
@ -69,23 +69,26 @@ Settings for JWT token verification.
|
|||
### `JWTVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L160" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
JWT token verifier using public key or JWKS.
|
||||
JWT token verifier supporting both asymmetric (RSA/ECDSA) and symmetric (HMAC) algorithms.
|
||||
|
||||
This verifier validates JWT tokens signed by an external issuer. It's ideal for
|
||||
scenarios where you have a centralized identity provider (like Auth0, Okta, or
|
||||
your own OAuth server) that issues JWTs, and your FastMCP server acts as a
|
||||
resource server validating those tokens.
|
||||
This verifier validates JWT tokens using various signing algorithms:
|
||||
- **Asymmetric algorithms** (RS256/384/512, ES256/384/512, PS256/384/512):
|
||||
Uses public/private key pairs. Ideal for external clients and services where
|
||||
only the authorization server has the private key.
|
||||
- **Symmetric algorithms** (HS256/384/512): Uses a shared secret for both
|
||||
signing and verification. Perfect for internal microservices and trusted
|
||||
environments where the secret can be securely shared.
|
||||
|
||||
Use this when:
|
||||
- You have JWT tokens issued by an external service
|
||||
- You want asymmetric key verification (public/private key pairs)
|
||||
- You need JWKS support for automatic key rotation
|
||||
- You have JWT tokens issued by an external service (asymmetric)
|
||||
- You need JWKS support for automatic key rotation (asymmetric)
|
||||
- You have internal microservices sharing a secret key (symmetric)
|
||||
- Your tokens contain standard OAuth scopes and claims
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `load_access_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L356" 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/providers/jwt.py#L362" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
load_access_token(self, token: str) -> AccessToken | None
|
||||
|
|
@ -100,7 +103,7 @@ Validates the provided JWT bearer token.
|
|||
- AccessToken object if valid, None if invalid or expired
|
||||
|
||||
|
||||
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L458" 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/jwt.py#L464" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
verify_token(self, token: str) -> AccessToken | None
|
||||
|
|
@ -118,7 +121,7 @@ to our existing load_access_token method.
|
|||
- AccessToken object if valid, None if invalid or expired
|
||||
|
||||
|
||||
### `StaticTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L474" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `StaticTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L480" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Simple static token verifier for testing and development.
|
||||
|
|
@ -139,7 +142,7 @@ WARNING: Never use this in production - tokens are stored in plain text!
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L508" 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/jwt.py#L514" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
verify_token(self, token: str) -> AccessToken | None
|
||||
|
|
|
|||
52
docs/python-sdk/fastmcp-server-auth-redirect_validation.mdx
Normal file
52
docs/python-sdk/fastmcp-server-auth-redirect_validation.mdx
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
---
|
||||
title: redirect_validation
|
||||
sidebarTitle: redirect_validation
|
||||
---
|
||||
|
||||
# `fastmcp.server.auth.redirect_validation`
|
||||
|
||||
|
||||
Utilities for validating client redirect URIs in OAuth flows.
|
||||
|
||||
## Functions
|
||||
|
||||
### `matches_allowed_pattern` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/redirect_validation.py#L8" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
matches_allowed_pattern(uri: str, pattern: str) -> bool
|
||||
```
|
||||
|
||||
|
||||
Check if a URI matches an allowed pattern with wildcard support.
|
||||
|
||||
Patterns support * wildcard matching:
|
||||
- http://localhost:* matches any localhost port
|
||||
- http://127.0.0.1:* matches any 127.0.0.1 port
|
||||
- https://*.example.com/* matches any subdomain of example.com
|
||||
- https://app.example.com/auth/* matches any path under /auth/
|
||||
|
||||
**Args:**
|
||||
- `uri`: The redirect URI to validate
|
||||
- `pattern`: The allowed pattern (may contain wildcards)
|
||||
|
||||
**Returns:**
|
||||
- True if the URI matches the pattern
|
||||
|
||||
|
||||
### `validate_redirect_uri` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/redirect_validation.py#L28" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
validate_redirect_uri(redirect_uri: str | AnyUrl | None, allowed_patterns: list[str] | None) -> bool
|
||||
```
|
||||
|
||||
|
||||
Validate a redirect URI against allowed patterns.
|
||||
|
||||
**Args:**
|
||||
- `redirect_uri`: The redirect URI to validate
|
||||
- `allowed_patterns`: List of allowed patterns. If None, defaults to localhost.
|
||||
If empty list, all URIs are allowed.
|
||||
|
||||
**Returns:**
|
||||
- True if the redirect URI is allowed
|
||||
|
||||
|
|
@ -7,7 +7,7 @@ sidebarTitle: context
|
|||
|
||||
## Functions
|
||||
|
||||
### `set_context` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L64" 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#L68" 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#L52" 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#L56" 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#L73" 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#L77" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Context object providing access to MCP capabilities.
|
||||
|
|
@ -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#L125" 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#L129" 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#L154" 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#L158" 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#L164" 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#L168" 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#L191" 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#L195" 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#L204" 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#L208" 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#L231" 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#L235" 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#L240" 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#L244" 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#L245" 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#L249" 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#L289" 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#L293" 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#L294" 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#L298" 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#L305" 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#L309" 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#L316" 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#L320" 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#L327" 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#L331" 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#L338" 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#L342" 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#L343" 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#L347" 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#L347" 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#L351" 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#L351" 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#L355" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
send_prompt_list_changed(self) -> None
|
||||
|
|
@ -252,7 +252,7 @@ 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#L355" 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#L359" 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
|
||||
|
|
@ -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#L402" 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#L443" 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#L414" 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#L455" 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#L424" 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#L465" 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#L433" 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#L474" 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#L526" 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#L567" 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#L541" 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#L582" 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#L545" 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#L586" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_state(self, key: str) -> Any
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ Supports forwarding roots, sampling, elicitation, logging, and progress.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `default_sampling_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L562" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `default_sampling_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L564" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
default_sampling_handler(cls, messages: list[mcp.types.SamplingMessage], params: mcp.types.CreateMessageRequestParams, context: RequestContext[ClientSession, LifespanContextT]) -> mcp.types.CreateMessageResult
|
||||
|
|
@ -277,7 +277,7 @@ default_sampling_handler(cls, messages: list[mcp.types.SamplingMessage], params:
|
|||
A handler that forwards the sampling request from the remote server to the proxy's connected clients and relays the response back to the remote server.
|
||||
|
||||
|
||||
#### `default_elicitation_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L588" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `default_elicitation_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L590" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
default_elicitation_handler(cls, message: str, response_type: type, params: mcp.types.ElicitRequestParams, context: RequestContext[ClientSession, LifespanContextT]) -> ElicitResult
|
||||
|
|
@ -286,7 +286,7 @@ default_elicitation_handler(cls, message: str, response_type: type, params: mcp.
|
|||
A handler that forwards the elicitation request from the remote server to the proxy's connected clients and relays the response back to the remote server.
|
||||
|
||||
|
||||
#### `default_log_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L607" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `default_log_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L609" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
default_log_handler(cls, message: LogMessage) -> None
|
||||
|
|
@ -295,7 +295,7 @@ default_log_handler(cls, message: LogMessage) -> None
|
|||
A handler that forwards the log notification from the remote server to the proxy's connected clients.
|
||||
|
||||
|
||||
#### `default_progress_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L617" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `default_progress_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L619" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
default_progress_handler(cls, progress: float, total: float | None, message: str | None) -> None
|
||||
|
|
@ -304,7 +304,7 @@ default_progress_handler(cls, progress: float, total: float | None, message: str
|
|||
A handler that forwards the progress notification from the remote server to the proxy's connected clients.
|
||||
|
||||
|
||||
### `StatefulProxyClient` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L630" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `StatefulProxyClient` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L632" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
A proxy client that provides a stateful client factory for the proxy server.
|
||||
|
|
@ -318,7 +318,7 @@ Note that it is essential to ensure that the proxy server itself is also statefu
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `clear` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L652" 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/server/proxy.py#L654" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
clear(self)
|
||||
|
|
@ -327,7 +327,7 @@ clear(self)
|
|||
Clear all cached clients and force disconnect them.
|
||||
|
||||
|
||||
#### `new_stateful` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L660" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `new_stateful` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L662" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
new_stateful(self) -> Client[ClientTransportT]
|
||||
|
|
|
|||
|
|
@ -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#L96" 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#L99" 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#L2182" 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#L2223" 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#L2242" 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#L2283" 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#L2309" 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#L2350" 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,35 +143,35 @@ False
|
|||
|
||||
## Classes
|
||||
|
||||
### `FastMCP` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L127" 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#L130" 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#L289" 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#L315" 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#L300" 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#L326" 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#L304" 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#L330" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
instructions(self) -> str | None
|
||||
```
|
||||
|
||||
#### `version` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L308" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `version` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L334" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
version(self) -> str | None
|
||||
```
|
||||
|
||||
#### `run_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L311" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `run_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L337" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
run_async(self, transport: Transport | None = None, show_banner: bool = True, **transport_kwargs: Any) -> None
|
||||
|
|
@ -183,7 +183,7 @@ Run the FastMCP server asynchronously.
|
|||
- `transport`: Transport protocol to use ("stdio", "sse", or "streamable-http")
|
||||
|
||||
|
||||
#### `run` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L341" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `run` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L367" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
run(self, transport: Transport | None = None, show_banner: bool = True, **transport_kwargs: Any) -> None
|
||||
|
|
@ -195,13 +195,13 @@ Run the FastMCP server. Note this is a synchronous function.
|
|||
- `transport`: Transport protocol to use ("stdio", "sse", or "streamable-http")
|
||||
|
||||
|
||||
#### `add_middleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L383" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `add_middleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L409" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
add_middleware(self, middleware: Middleware) -> None
|
||||
```
|
||||
|
||||
#### `get_tools` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L386" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_tools` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L412" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_tools(self) -> dict[str, Tool]
|
||||
|
|
@ -210,13 +210,13 @@ get_tools(self) -> dict[str, Tool]
|
|||
Get all registered tools, indexed by registered key.
|
||||
|
||||
|
||||
#### `get_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L390" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L416" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_tool(self, key: str) -> Tool
|
||||
```
|
||||
|
||||
#### `get_resources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L396" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_resources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L422" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_resources(self) -> dict[str, Resource]
|
||||
|
|
@ -225,13 +225,13 @@ get_resources(self) -> dict[str, Resource]
|
|||
Get all registered resources, indexed by registered key.
|
||||
|
||||
|
||||
#### `get_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L400" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L426" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_resource(self, key: str) -> Resource
|
||||
```
|
||||
|
||||
#### `get_resource_templates` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L406" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_resource_templates` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L432" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_resource_templates(self) -> dict[str, ResourceTemplate]
|
||||
|
|
@ -240,7 +240,7 @@ get_resource_templates(self) -> dict[str, ResourceTemplate]
|
|||
Get all registered resource templates, indexed by registered key.
|
||||
|
||||
|
||||
#### `get_resource_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L410" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_resource_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L436" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_resource_template(self, key: str) -> ResourceTemplate
|
||||
|
|
@ -249,7 +249,7 @@ get_resource_template(self, key: str) -> ResourceTemplate
|
|||
Get a registered resource template by key.
|
||||
|
||||
|
||||
#### `get_prompts` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L417" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_prompts` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L443" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_prompts(self) -> dict[str, Prompt]
|
||||
|
|
@ -258,13 +258,13 @@ get_prompts(self) -> dict[str, Prompt]
|
|||
List all available prompts.
|
||||
|
||||
|
||||
#### `get_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L423" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L449" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_prompt(self, key: str) -> Prompt
|
||||
```
|
||||
|
||||
#### `custom_route` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L429" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `custom_route` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L455" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
custom_route(self, path: str, methods: list[str], name: str | None = None, include_in_schema: bool = True) -> Callable[[Callable[[Request], Awaitable[Response]]], Callable[[Request], Awaitable[Response]]]
|
||||
|
|
@ -285,7 +285,7 @@ Starlette's reverse URL lookup feature)
|
|||
- `include_in_schema`: Whether to include in OpenAPI schema, defaults to True
|
||||
|
||||
|
||||
#### `add_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L827" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `add_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L857" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
add_tool(self, tool: Tool) -> Tool
|
||||
|
|
@ -303,7 +303,7 @@ with the Context type annotation. See the @tool decorator for examples.
|
|||
- The tool instance that was added to the server.
|
||||
|
||||
|
||||
#### `remove_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L852" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `remove_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L882" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
remove_tool(self, name: str) -> None
|
||||
|
|
@ -318,7 +318,7 @@ Remove a tool from the server.
|
|||
- `NotFoundError`: If the tool is not found
|
||||
|
||||
|
||||
#### `add_tool_transformation` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L872" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `add_tool_transformation` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L902" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
add_tool_transformation(self, tool_name: str, transformation: ToolTransformConfig) -> None
|
||||
|
|
@ -327,7 +327,7 @@ add_tool_transformation(self, tool_name: str, transformation: ToolTransformConfi
|
|||
Add a tool transformation.
|
||||
|
||||
|
||||
#### `remove_tool_transformation` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L878" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `remove_tool_transformation` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L908" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
remove_tool_transformation(self, tool_name: str) -> None
|
||||
|
|
@ -336,19 +336,19 @@ remove_tool_transformation(self, tool_name: str) -> None
|
|||
Remove a tool transformation.
|
||||
|
||||
|
||||
#### `tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L883" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L913" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
tool(self, name_or_fn: AnyFunction) -> FunctionTool
|
||||
```
|
||||
|
||||
#### `tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L899" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L929" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
tool(self, name_or_fn: str | None = None) -> Callable[[AnyFunction], FunctionTool]
|
||||
```
|
||||
|
||||
#### `tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L914" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L944" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
tool(self, name_or_fn: str | AnyFunction | None = None) -> Callable[[AnyFunction], FunctionTool] | FunctionTool
|
||||
|
|
@ -404,7 +404,7 @@ server.tool(my_function, name="custom_name")
|
|||
```
|
||||
|
||||
|
||||
#### `add_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1045" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `add_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1075" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
add_resource(self, resource: Resource) -> Resource
|
||||
|
|
@ -419,7 +419,7 @@ Add a resource to the server.
|
|||
- The resource instance that was added to the server.
|
||||
|
||||
|
||||
#### `add_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1067" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `add_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1097" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
add_template(self, template: ResourceTemplate) -> ResourceTemplate
|
||||
|
|
@ -434,7 +434,7 @@ Add a resource template to the server.
|
|||
- The template instance that was added to the server.
|
||||
|
||||
|
||||
#### `add_resource_fn` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1089" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `add_resource_fn` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1119" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
add_resource_fn(self, fn: AnyFunction, uri: str, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None) -> None
|
||||
|
|
@ -454,7 +454,7 @@ has parameters, it will be registered as a template resource.
|
|||
- `tags`: Optional set of tags for categorizing the resource
|
||||
|
||||
|
||||
#### `resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1127" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1157" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
resource(self, uri: str) -> Callable[[AnyFunction], Resource | ResourceTemplate]
|
||||
|
|
@ -514,7 +514,7 @@ async def get_weather(city: str) -> str:
|
|||
```
|
||||
|
||||
|
||||
#### `add_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1264" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `add_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1294" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
add_prompt(self, prompt: Prompt) -> Prompt
|
||||
|
|
@ -529,19 +529,19 @@ Add a prompt to the server.
|
|||
- The prompt instance that was added to the server.
|
||||
|
||||
|
||||
#### `prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1287" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1317" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
prompt(self, name_or_fn: AnyFunction) -> FunctionPrompt
|
||||
```
|
||||
|
||||
#### `prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1300" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1330" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
prompt(self, name_or_fn: str | None = None) -> Callable[[AnyFunction], FunctionPrompt]
|
||||
```
|
||||
|
||||
#### `prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1312" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1342" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
prompt(self, name_or_fn: str | AnyFunction | None = None) -> Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt
|
||||
|
|
@ -619,7 +619,7 @@ Decorator to register a prompt.
|
|||
```
|
||||
|
||||
|
||||
#### `run_stdio_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1453" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `run_stdio_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1483" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
run_stdio_async(self, show_banner: bool = True) -> None
|
||||
|
|
@ -628,7 +628,7 @@ run_stdio_async(self, show_banner: bool = True) -> None
|
|||
Run the server using stdio transport.
|
||||
|
||||
|
||||
#### `run_http_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1473" 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#L1503" 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 +647,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#L1547" 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#L1577" 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 +656,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#L1575" 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#L1605" 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 +670,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#L1606" 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#L1636" 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 +683,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#L1627" 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#L1657" 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 +700,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#L1676" 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#L1706" 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#L1701" 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#L1731" 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 +760,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#L1823" 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#L1853" 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 +801,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#L1950" 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#L1982" 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 +810,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#L1999" 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#L2031" 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 +819,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#L2062" 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#L2094" 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 +833,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#L2123" 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#L2155" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
from_client(cls, client: Client[ClientTransportT], **settings: Any) -> FastMCPProxy
|
||||
|
|
@ -842,4 +842,10 @@ from_client(cls, client: Client[ClientTransportT], **settings: Any) -> FastMCPPr
|
|||
Create a FastMCP proxy server from a FastMCP client.
|
||||
|
||||
|
||||
### `MountedServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2176" 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#L2207" 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#L2217" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ 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#L153" 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#L154" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
normalize_log_level(cls, v)
|
||||
|
|
|
|||
|
|
@ -260,7 +260,7 @@ Tool.from_tool(parent, output_schema={
|
|||
})
|
||||
|
||||
# Disable structured outputs
|
||||
Tool.from_tool(parent, output_schema=False)
|
||||
Tool.from_tool(parent, output_schema=None)
|
||||
|
||||
# Return ToolResult for full control
|
||||
async def custom_output(**kwargs) -> ToolResult:
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ sidebarTitle: cli
|
|||
|
||||
## Functions
|
||||
|
||||
### `log_server_banner` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/cli.py#L26" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `log_server_banner` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/cli.py#L27" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
log_server_banner(server: FastMCP[Any], transport: Literal['stdio', 'http', 'sse', 'streamable-http']) -> None
|
||||
|
|
|
|||
13
docs/python-sdk/fastmcp-utilities-fastmcp_config.mdx
Normal file
13
docs/python-sdk/fastmcp-utilities-fastmcp_config.mdx
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
title: fastmcp_config
|
||||
sidebarTitle: fastmcp_config
|
||||
---
|
||||
|
||||
# `fastmcp.utilities.fastmcp_config`
|
||||
|
||||
|
||||
FastMCP Configuration module.
|
||||
|
||||
This module provides versioned configuration support for FastMCP servers.
|
||||
The current version is v1, which is re-exported here for convenience.
|
||||
|
||||
|
|
@ -7,7 +7,7 @@ sidebarTitle: mcp_config
|
|||
|
||||
## Functions
|
||||
|
||||
### `mcp_config_to_servers_and_transports` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_config.py#L11" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `mcp_config_to_servers_and_transports` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_config.py#L17" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
mcp_config_to_servers_and_transports(config: MCPConfig) -> list[tuple[str, FastMCP[Any], ClientTransport]]
|
||||
|
|
@ -17,7 +17,7 @@ mcp_config_to_servers_and_transports(config: MCPConfig) -> list[tuple[str, FastM
|
|||
A utility function to convert each entry of an MCP Config into a transport and server.
|
||||
|
||||
|
||||
### `mcp_server_type_to_servers_and_transports` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_config.py#L21" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `mcp_server_type_to_servers_and_transports` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_config.py#L27" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
mcp_server_type_to_servers_and_transports(name: str, mcp_server: MCPServerTypes) -> tuple[str, FastMCP[Any], ClientTransport]
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Common types used across FastMCP.
|
|||
|
||||
## Functions
|
||||
|
||||
### `get_cached_typeadapter` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L39" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `get_cached_typeadapter` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L41" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_cached_typeadapter(cls: T) -> TypeAdapter[T]
|
||||
|
|
@ -23,7 +23,7 @@ However, this isn't feasible for user-generated functions. Instead, we use a
|
|||
cache to minimize the cost of creating them as much as possible.
|
||||
|
||||
|
||||
### `issubclass_safe` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L114" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `issubclass_safe` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L116" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
issubclass_safe(cls: type, base: type) -> bool
|
||||
|
|
@ -33,10 +33,10 @@ issubclass_safe(cls: type, base: type) -> bool
|
|||
Check if cls is a subclass of base, even if cls is a type variable.
|
||||
|
||||
|
||||
### `is_class_member_of_type` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L124" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `is_class_member_of_type` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L126" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
is_class_member_of_type(cls: type, base: type) -> bool
|
||||
is_class_member_of_type(cls: Any, base: type) -> bool
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -46,7 +46,7 @@ Base can be a type, a UnionType, or an Annotated type. Generic types are not
|
|||
considered members (e.g. T is not a member of list\[T]).
|
||||
|
||||
|
||||
### `find_kwarg_by_type` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L146" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `find_kwarg_by_type` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L148" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
find_kwarg_by_type(fn: Callable, kwarg_type: type) -> str | None
|
||||
|
|
@ -58,7 +58,7 @@ Find the name of the kwarg that is of type kwarg_type.
|
|||
Includes union types that contain the kwarg_type, as well as Annotated types.
|
||||
|
||||
|
||||
### `replace_type` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L375" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `replace_type` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L377" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
replace_type(type_, type_map: dict[type, type])
|
||||
|
|
@ -87,13 +87,13 @@ list[list[str]]
|
|||
|
||||
## Classes
|
||||
|
||||
### `FastMCPBaseModel` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L32" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `FastMCPBaseModel` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L34" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Base model for FastMCP models.
|
||||
|
||||
|
||||
### `Image` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L172" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `Image` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L174" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Helper class for returning images from tools.
|
||||
|
|
@ -101,7 +101,7 @@ Helper class for returning images from tools.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `to_image_content` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L209" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `to_image_content` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L211" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
to_image_content(self, mime_type: str | None = None, annotations: Annotations | None = None) -> mcp.types.ImageContent
|
||||
|
|
@ -110,7 +110,7 @@ to_image_content(self, mime_type: str | None = None, annotations: Annotations |
|
|||
Convert to MCP ImageContent.
|
||||
|
||||
|
||||
### `Audio` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L231" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `Audio` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L233" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Helper class for returning audio from tools.
|
||||
|
|
@ -118,13 +118,13 @@ Helper class for returning audio from tools.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `to_audio_content` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L268" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `to_audio_content` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L270" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
to_audio_content(self, mime_type: str | None = None, annotations: Annotations | None = None) -> mcp.types.AudioContent
|
||||
```
|
||||
|
||||
### `File` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L289" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `File` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L291" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Helper class for returning audio from tools.
|
||||
|
|
@ -132,8 +132,10 @@ Helper class for returning audio from tools.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `to_resource_content` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L328" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `to_resource_content` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L330" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
to_resource_content(self, mime_type: str | None = None, annotations: Annotations | None = None) -> mcp.types.EmbeddedResource
|
||||
```
|
||||
|
||||
### `ContextSamplingFallbackProtocol` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L414" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
v1.json
|
||||
|
|
@ -1 +0,0 @@
|
|||
../../../src/fastmcp/utilities/fastmcp_config/v1/schema.json
|
||||
|
|
@ -161,6 +161,15 @@ The `OAuthProxy` class provides the complete proxy implementation:
|
|||
<ParamField body="resource_server_url" type="AnyHttpUrl | str | None">
|
||||
Resource server URL (defaults to base_url)
|
||||
</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): Only localhost redirect URIs allowed (`http://localhost:*`, `http://127.0.0.1:*`)
|
||||
- Empty list `[]`: All redirect URIs allowed (not recommended for production)
|
||||
- Custom list: Only matching patterns allowed
|
||||
|
||||
These patterns apply to MCP client loopback redirects, NOT the upstream OAuth app redirect URI.
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
```python
|
||||
|
|
@ -219,6 +228,32 @@ The proxy automatically:
|
|||
- Validates tokens using your provider's public keys or API
|
||||
- Maintains PKCE security throughout the flow
|
||||
|
||||
## Client Redirect URI Security
|
||||
|
||||
<Warning>
|
||||
By default, OAuth Proxy only accepts localhost redirect URIs from MCP clients for security. You can customize this with the `allowed_client_redirect_uris` parameter:
|
||||
|
||||
```python
|
||||
# Default: localhost only (secure)
|
||||
auth = OAuthProxy(...)
|
||||
|
||||
# Custom patterns with wildcards
|
||||
auth = OAuthProxy(
|
||||
...,
|
||||
allowed_client_redirect_uris=[
|
||||
"http://localhost:*",
|
||||
"https://app.example.com/auth/*"
|
||||
]
|
||||
)
|
||||
|
||||
# Allow all (NOT recommended for production)
|
||||
auth = OAuthProxy(
|
||||
...,
|
||||
allowed_client_redirect_uris=[]
|
||||
)
|
||||
```
|
||||
</Warning>
|
||||
|
||||
## Client Compatibility
|
||||
|
||||
<Tip>
|
||||
|
|
|
|||
|
|
@ -111,7 +111,9 @@ token_verifier = JWTVerifier(
|
|||
auth = RemoteAuthProvider(
|
||||
token_verifier=token_verifier,
|
||||
authorization_servers=[AnyHttpUrl("https://auth.yourcompany.com")],
|
||||
resource_server_url="https://api.yourcompany.com"
|
||||
resource_server_url="https://api.yourcompany.com",
|
||||
# Optional: customize allowed client redirect URIs (defaults to localhost only)
|
||||
allowed_client_redirect_uris=["http://localhost:*", "http://127.0.0.1:*"]
|
||||
)
|
||||
|
||||
mcp = FastMCP(name="Company API", auth=auth)
|
||||
|
|
@ -192,6 +194,18 @@ WorkOS's support for Dynamic Client Registration makes it particularly well-suit
|
|||
|
||||
→ **Complete WorkOS tutorial**: [AuthKit Integration Guide](/integrations/authkit)
|
||||
|
||||
## Client Redirect URI Security
|
||||
|
||||
<Note>
|
||||
`RemoteAuthProvider` also supports the `allowed_client_redirect_uris` parameter for controlling which redirect URIs are accepted from MCP clients during DCR:
|
||||
|
||||
- `None` (default): Only localhost patterns allowed
|
||||
- Custom list: Specify allowed patterns with wildcard support
|
||||
- Empty list `[]`: Allow all (not recommended)
|
||||
|
||||
This provides defense-in-depth even though DCR providers typically validate redirect URIs themselves.
|
||||
</Note>
|
||||
|
||||
## Implementation Considerations
|
||||
|
||||
Remote OAuth integration requires careful attention to several technical details that affect reliability and security.
|
||||
|
|
|
|||
|
|
@ -80,9 +80,48 @@ This configuration creates a server that validates JWTs issued by `auth.yourcomp
|
|||
|
||||
The `issuer` parameter ensures tokens come from your trusted authentication system, while `audience` validation prevents tokens intended for other services from being accepted by your MCP server.
|
||||
|
||||
#### Symmetric Key Verification (HMAC)
|
||||
|
||||
Symmetric key verification uses a shared secret for both signing and validation, making it ideal for internal microservices and trusted environments where the same secret can be securely distributed to both token issuers and validators.
|
||||
|
||||
This approach is commonly used in microservices architectures where services share a secret key, or when your authentication service and MCP server are both managed by the same organization. The HMAC algorithms (HS256, HS384, HS512) provide strong security when the shared secret is properly managed.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.jwt import JWTVerifier
|
||||
|
||||
# Use a shared secret for symmetric key verification
|
||||
verifier = JWTVerifier(
|
||||
public_key="your-shared-secret-key-minimum-32-chars", # Despite the name, this accepts symmetric secrets
|
||||
issuer="internal-auth-service",
|
||||
audience="mcp-internal-api",
|
||||
algorithm="HS256" # or HS384, HS512 for stronger security
|
||||
)
|
||||
|
||||
mcp = FastMCP(name="Internal API", auth=verifier)
|
||||
```
|
||||
|
||||
The verifier will validate tokens signed with the same secret using the specified HMAC algorithm. This approach offers several advantages for internal systems:
|
||||
|
||||
- **Simplicity**: No key pair management or certificate distribution
|
||||
- **Performance**: HMAC operations are typically faster than RSA
|
||||
- **Compatibility**: Works well with existing microservice authentication patterns
|
||||
|
||||
<Note>
|
||||
The parameter is named `public_key` for backwards compatibility, but when using HMAC algorithms (HS256/384/512), it accepts the symmetric secret string.
|
||||
</Note>
|
||||
|
||||
<Warning>
|
||||
**Security Considerations for Symmetric Keys:**
|
||||
- Use a strong, randomly generated secret (minimum 32 characters recommended)
|
||||
- Never expose the secret in logs, error messages, or version control
|
||||
- Implement secure key distribution and rotation mechanisms
|
||||
- Consider using asymmetric keys (RSA/ECDSA) for external-facing APIs
|
||||
</Warning>
|
||||
|
||||
#### Static Public Key Verification
|
||||
|
||||
Static public key verification works when you have a fixed signing key and don't need automatic key rotation. This approach simplifies deployment in environments where JWKS endpoints aren't available.
|
||||
Static public key verification works when you have a fixed RSA or ECDSA signing key and don't need automatic key rotation. This approach is primarily useful for development environments or controlled deployments where JWKS endpoints aren't available.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
|
|
@ -102,7 +141,7 @@ verifier = JWTVerifier(
|
|||
mcp = FastMCP(name="Protected API", auth=verifier)
|
||||
```
|
||||
|
||||
This configuration validates tokens using a specific public key. The key must correspond to the private key used by your token issuer. While less flexible than JWKS endpoints, this approach works well for controlled environments or when using dedicated signing keys.
|
||||
This configuration validates tokens using a specific RSA or ECDSA public key. The key must correspond to the private key used by your token issuer. While less flexible than JWKS endpoints, this approach can be useful in development environments or when testing with fixed keys.
|
||||
|
||||
### Development and Testing
|
||||
|
||||
|
|
@ -180,11 +219,17 @@ Environment-based configuration separates authentication settings from applicati
|
|||
# Enable JWT verification
|
||||
export FASTMCP_SERVER_AUTH=JWT
|
||||
|
||||
# Configure JWT verification parameters
|
||||
# For asymmetric verification with JWKS endpoint:
|
||||
export FASTMCP_SERVER_AUTH_JWT_JWKS_URI="https://auth.company.com/.well-known/jwks.json"
|
||||
export FASTMCP_SERVER_AUTH_JWT_ISSUER="https://auth.company.com"
|
||||
export FASTMCP_SERVER_AUTH_JWT_AUDIENCE="mcp-production-api"
|
||||
export FASTMCP_SERVER_AUTH_JWT_REQUIRED_SCOPES="read:data,write:data"
|
||||
|
||||
# OR for symmetric key verification (HMAC):
|
||||
export FASTMCP_SERVER_AUTH_JWT_PUBLIC_KEY="your-shared-secret-key-minimum-32-chars"
|
||||
export FASTMCP_SERVER_AUTH_JWT_ALGORITHM="HS256" # or HS384, HS512
|
||||
export FASTMCP_SERVER_AUTH_JWT_ISSUER="internal-auth-service"
|
||||
export FASTMCP_SERVER_AUTH_JWT_AUDIENCE="mcp-internal-api"
|
||||
```
|
||||
|
||||
With these environment variables configured, your FastMCP server automatically enables JWT verification:
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
{
|
||||
"$schema": "https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
"entrypoint": "src/atproto_mcp/server.py",
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {
|
||||
"path": "src/atproto_mcp/server.py"
|
||||
},
|
||||
"environment": {
|
||||
"dependencies": [
|
||||
"atproto_mcp@git+https://github.com/jlowin/fastmcp.git#subdirectory=examples/atproto_mcp"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"$schema": "https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"entrypoint": "src/server.py:app",
|
||||
"environment": {
|
||||
"python": "3.12",
|
||||
|
|
@ -17,4 +17,4 @@
|
|||
"FEATURE_FLAGS": "${FEATURE_FLAGS}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"$schema": "https://gofastmcp.com/schemas/fastmcp/v1.json",
|
||||
"entrypoint": "server.py",
|
||||
"source": {
|
||||
"path": "server.py"
|
||||
},
|
||||
"environment": {
|
||||
"python": "3.12",
|
||||
"dependencies": ["requests"]
|
||||
|
|
|
|||
|
|
@ -1,15 +1,12 @@
|
|||
{
|
||||
"$schema": "https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
"entrypoint": {
|
||||
"file": "server.py",
|
||||
"object": "mcp"
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {
|
||||
"path": "server.py",
|
||||
"entrypoint": "mcp"
|
||||
},
|
||||
"environment": {
|
||||
"python": "3.12",
|
||||
"dependencies": [
|
||||
"requests>=2.31.0",
|
||||
"httpx"
|
||||
],
|
||||
"dependencies": ["requests>=2.31.0", "httpx"],
|
||||
"requirements": null,
|
||||
"project": null,
|
||||
"editable": null
|
||||
|
|
@ -27,4 +24,4 @@
|
|||
"cwd": null,
|
||||
"args": null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
{
|
||||
"$schema": "https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
"entrypoint": "server.py",
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {
|
||||
"path": "server.py"
|
||||
},
|
||||
"environment": {
|
||||
"python": "3.11",
|
||||
"dependencies": [
|
||||
"pyautogui",
|
||||
"Pillow"
|
||||
]
|
||||
"dependencies": ["pyautogui", "Pillow"]
|
||||
},
|
||||
"deployment": {
|
||||
"transport": "stdio",
|
||||
"log_level": "INFO"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,7 @@
|
|||
{
|
||||
"$schema": "https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"entrypoint": "memory.py",
|
||||
"environment": {
|
||||
"dependencies": [
|
||||
"pydantic-ai-slim[openai]",
|
||||
"asyncpg",
|
||||
"numpy",
|
||||
"pgvector"
|
||||
]
|
||||
"dependencies": ["pydantic-ai-slim[openai]", "asyncpg", "numpy", "pgvector"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"$schema": "https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"entrypoint": "mount_example.py"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"entrypoint": "screenshot.py",
|
||||
"environment": {
|
||||
"dependencies": ["pyautogui", "Pillow"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"$schema": "https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"entrypoint": "src/smart_home/hub.py",
|
||||
"environment": {
|
||||
"dependencies": [
|
||||
"smart_home@git+https://github.com/jlowin/fastmcp.git#subdirectory=examples/smart_home"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"$schema": "https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"entrypoint": "src/smart_home/lights/server.py",
|
||||
"environment": {
|
||||
"dependencies": [
|
||||
"smart_home@git+https://github.com/jlowin/fastmcp.git#subdirectory=examples/smart_home"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ dev = [
|
|||
"copychat>=0.5.2",
|
||||
"dirty-equals>=0.9.0",
|
||||
"fastapi>=0.115.12",
|
||||
"inline-snapshot[dirty-equals]>=0.27.2",
|
||||
"ipython>=8.12.3",
|
||||
"pdbpp>=0.10.3",
|
||||
"pre-commit",
|
||||
|
|
@ -124,7 +125,6 @@ python-version = "3.10"
|
|||
|
||||
[tool.ty.rules]
|
||||
# Rules with too many errors to fix right now (40+ each)
|
||||
invalid-argument-type = "ignore" # 40 errors
|
||||
no-matching-overload = "ignore" # 126 errors
|
||||
unknown-argument = "ignore" # 61 errors
|
||||
unresolved-attribute = "ignore" # 60 errors
|
||||
|
|
|
|||
|
|
@ -6,10 +6,11 @@ from fastmcp.settings import Settings
|
|||
from fastmcp.utilities.logging import configure_logging as _configure_logging
|
||||
|
||||
settings = Settings()
|
||||
_configure_logging(
|
||||
level=settings.log_level,
|
||||
enable_rich_tracebacks=settings.enable_rich_tracebacks,
|
||||
)
|
||||
if settings.log_enabled:
|
||||
_configure_logging(
|
||||
level=settings.log_level,
|
||||
enable_rich_tracebacks=settings.enable_rich_tracebacks,
|
||||
)
|
||||
|
||||
from fastmcp.server.server import FastMCP
|
||||
from fastmcp.server.context import Context
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import sys
|
|||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from fastmcp.utilities.fastmcp_config import Environment
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
|
@ -89,20 +90,20 @@ def update_claude_config(
|
|||
else:
|
||||
env_vars = existing_env
|
||||
|
||||
# Build uv run command
|
||||
args = ["run"]
|
||||
|
||||
# Collect all packages in a set to deduplicate
|
||||
packages = {"fastmcp"}
|
||||
# Deduplicate packages and exclude 'fastmcp' since Environment adds it automatically
|
||||
deduplicated_packages = None
|
||||
if with_packages:
|
||||
packages.update(pkg for pkg in with_packages if pkg)
|
||||
deduplicated = list(dict.fromkeys(with_packages))
|
||||
deduplicated_packages = [pkg for pkg in deduplicated if pkg != "fastmcp"]
|
||||
if not deduplicated_packages:
|
||||
deduplicated_packages = None
|
||||
|
||||
# Add all packages with --with
|
||||
for pkg in sorted(packages):
|
||||
args.extend(["--with", pkg])
|
||||
|
||||
if with_editable:
|
||||
args.extend(["--with-editable", str(with_editable)])
|
||||
# Build uv run command using Environment.build_uv_args()
|
||||
env_config = Environment(
|
||||
dependencies=deduplicated_packages,
|
||||
editable=str(with_editable) if with_editable else None,
|
||||
)
|
||||
args = env_config.build_uv_args()
|
||||
|
||||
# Convert file path to absolute before adding to command
|
||||
# Split off any :object suffix first
|
||||
|
|
|
|||
|
|
@ -2,16 +2,18 @@
|
|||
|
||||
import importlib.metadata
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
from typing import Annotated, Literal
|
||||
|
||||
import cyclopts
|
||||
import pyperclip
|
||||
from pydantic import TypeAdapter
|
||||
from pydantic import TypeAdapter, ValidationError
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
|
||||
|
|
@ -21,6 +23,7 @@ from fastmcp.cli.install import install_app
|
|||
from fastmcp.server.server import FastMCP
|
||||
from fastmcp.utilities.inspect import FastMCPInfo, inspect_fastmcp
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
from fastmcp.utilities.types import get_cached_typeadapter
|
||||
|
||||
logger = get_logger("cli")
|
||||
console = Console()
|
||||
|
|
@ -57,46 +60,27 @@ def _parse_env_var(env_var: str) -> tuple[str, str]:
|
|||
return key.strip(), value.strip()
|
||||
|
||||
|
||||
def _build_uv_command(
|
||||
server_spec: str,
|
||||
with_editable: Path | None = None,
|
||||
with_packages: list[str] | None = None,
|
||||
no_banner: bool = False,
|
||||
python_version: str | None = None,
|
||||
with_requirements: Path | None = None,
|
||||
project: Path | None = None,
|
||||
) -> list[str]:
|
||||
"""Build the uv run command that runs a MCP server through mcp run."""
|
||||
cmd = ["uv", "run"]
|
||||
@contextmanager
|
||||
def with_argv(args: list[str] | None):
|
||||
"""Temporarily replace sys.argv if args provided.
|
||||
|
||||
# Add Python version if specified
|
||||
if python_version:
|
||||
cmd.extend(["--python", python_version])
|
||||
This context manager is used at the CLI boundary to inject
|
||||
server arguments when needed, without mutating sys.argv deep
|
||||
in the source loading logic.
|
||||
|
||||
# Add project if specified
|
||||
if project:
|
||||
cmd.extend(["--project", str(project)])
|
||||
|
||||
cmd.extend(["--with", "fastmcp"])
|
||||
|
||||
if with_editable:
|
||||
cmd.extend(["--with-editable", str(with_editable)])
|
||||
|
||||
if with_packages:
|
||||
for pkg in with_packages:
|
||||
if pkg:
|
||||
cmd.extend(["--with", pkg])
|
||||
|
||||
if with_requirements:
|
||||
cmd.extend(["--with-requirements", str(with_requirements)])
|
||||
|
||||
# Add mcp run command
|
||||
cmd.extend(["fastmcp", "run", server_spec])
|
||||
|
||||
if no_banner:
|
||||
cmd.append("--no-banner")
|
||||
|
||||
return cmd
|
||||
Args are provided without the script name, so we preserve sys.argv[0]
|
||||
and replace the rest.
|
||||
"""
|
||||
if args is not None:
|
||||
original = sys.argv[:]
|
||||
try:
|
||||
# Preserve the script name (sys.argv[0]) and replace the rest
|
||||
sys.argv = [sys.argv[0]] + args
|
||||
yield
|
||||
finally:
|
||||
sys.argv = original
|
||||
else:
|
||||
yield
|
||||
|
||||
|
||||
@app.command
|
||||
|
|
@ -107,7 +91,7 @@ def version(
|
|||
cyclopts.Parameter(
|
||||
"--copy",
|
||||
help="Copy version information to clipboard",
|
||||
negative=False,
|
||||
negative="",
|
||||
),
|
||||
] = False,
|
||||
):
|
||||
|
|
@ -153,7 +137,7 @@ async def dev(
|
|||
cyclopts.Parameter(
|
||||
"--with",
|
||||
help="Additional packages to install",
|
||||
negative=False,
|
||||
negative="",
|
||||
),
|
||||
] = [],
|
||||
inspector_version: Annotated[
|
||||
|
|
@ -204,12 +188,16 @@ async def dev(
|
|||
Args:
|
||||
server_spec: Python file to run, optionally with :object suffix, or None to auto-detect fastmcp.json
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
from fastmcp.utilities.fastmcp_config import FastMCPConfig
|
||||
from fastmcp.utilities.fastmcp_config.v1.sources.filesystem import FileSystemSource
|
||||
|
||||
config = None
|
||||
config_path = None
|
||||
|
||||
# Auto-detect fastmcp.json if no server_spec provided
|
||||
if server_spec is None:
|
||||
from pathlib import Path
|
||||
|
||||
from fastmcp.utilities.fastmcp_config import FastMCPConfig
|
||||
|
||||
config_path = Path("fastmcp.json")
|
||||
if not config_path.exists():
|
||||
# Check if fastmcp.json exists in current directory
|
||||
|
|
@ -222,44 +210,50 @@ async def dev(
|
|||
"Please specify a server file or create a fastmcp.json configuration."
|
||||
)
|
||||
sys.exit(1)
|
||||
server_spec = str(config_path)
|
||||
logger.info(f"Using configuration from {config_path}")
|
||||
|
||||
# Load the config to get settings
|
||||
config = FastMCPConfig.from_file(config_path)
|
||||
entrypoint = config.get_entrypoint(config_path)
|
||||
|
||||
# Convert entrypoint to string format for dev command
|
||||
if entrypoint.object:
|
||||
server_spec = f"{entrypoint.file}:{entrypoint.object}"
|
||||
else:
|
||||
server_spec = entrypoint.file
|
||||
# Create FastMCPConfig from server_spec
|
||||
if server_spec.endswith(".json"):
|
||||
# Load existing config
|
||||
config = FastMCPConfig.from_file(Path(server_spec))
|
||||
|
||||
# Merge environment settings with CLI args (CLI takes precedence)
|
||||
if config.environment:
|
||||
merged_env = config.environment.merge_with_cli_args(
|
||||
python=python,
|
||||
with_packages=with_packages,
|
||||
with_requirements=with_requirements,
|
||||
project=project,
|
||||
with_editable=with_editable,
|
||||
python = python or config.environment.python
|
||||
project = project or (
|
||||
Path(config.environment.project) if config.environment.project else None
|
||||
)
|
||||
python = merged_env["python"]
|
||||
with_packages = merged_env["with_packages"]
|
||||
with_requirements = merged_env["with_requirements"]
|
||||
project = merged_env["project"]
|
||||
with_editable = merged_env["with_editable"]
|
||||
with_requirements = with_requirements or (
|
||||
Path(config.environment.requirements)
|
||||
if config.environment.requirements
|
||||
else None
|
||||
)
|
||||
with_editable = with_editable or (
|
||||
Path(config.environment.editable)
|
||||
if config.environment.editable
|
||||
else None
|
||||
)
|
||||
|
||||
# Merge packages from both sources
|
||||
if config.environment.dependencies:
|
||||
packages = list(config.environment.dependencies)
|
||||
if with_packages:
|
||||
packages.extend(with_packages)
|
||||
with_packages = packages
|
||||
|
||||
# Get server port from deployment config if not specified
|
||||
if config.deployment and config.deployment.port:
|
||||
server_port = server_port or config.deployment.port
|
||||
|
||||
logger.info(f"Using configuration from {config_path}")
|
||||
file, server_object = run_module.parse_file_path(server_spec)
|
||||
else:
|
||||
# Create config from file path
|
||||
source = FileSystemSource(path=server_spec)
|
||||
config = FastMCPConfig(source=source)
|
||||
|
||||
logger.debug(
|
||||
"Starting dev server",
|
||||
extra={
|
||||
"file": str(file),
|
||||
"server_object": server_object,
|
||||
"server_spec": server_spec,
|
||||
"with_editable": str(with_editable) if with_editable else None,
|
||||
"with_packages": with_packages,
|
||||
"ui_port": ui_port,
|
||||
|
|
@ -268,9 +262,8 @@ async def dev(
|
|||
)
|
||||
|
||||
try:
|
||||
# Import server to get dependencies
|
||||
# TODO: Remove dependencies handling (deprecated in v2.11.4)
|
||||
server: FastMCP = await run_module.import_server(file, server_object)
|
||||
# Load server to check for deprecated dependencies
|
||||
server: FastMCP = await config.source.load_server()
|
||||
if server.dependencies:
|
||||
import warnings
|
||||
|
||||
|
|
@ -302,15 +295,20 @@ async def dev(
|
|||
if inspector_version:
|
||||
inspector_cmd += f"@{inspector_version}"
|
||||
|
||||
uv_cmd = _build_uv_command(
|
||||
server_spec,
|
||||
with_editable,
|
||||
with_packages,
|
||||
no_banner=True,
|
||||
python_version=python,
|
||||
with_requirements=with_requirements,
|
||||
project=project,
|
||||
# Create Environment object from CLI args
|
||||
from fastmcp.utilities.fastmcp_config import Environment
|
||||
|
||||
env_config = Environment(
|
||||
python=python,
|
||||
dependencies=with_packages if with_packages else None,
|
||||
requirements=str(with_requirements) if with_requirements else None,
|
||||
project=str(project) if project else None,
|
||||
editable=str(with_editable) if with_editable else None,
|
||||
)
|
||||
uv_cmd = ["uv"] + env_config.build_uv_args(["fastmcp", "run", server_spec])
|
||||
|
||||
# Add --no-banner flag for dev command
|
||||
uv_cmd.append("--no-banner")
|
||||
|
||||
# Run the MCP Inspector command with shell=True on Windows
|
||||
shell = sys.platform == "win32"
|
||||
|
|
@ -325,7 +323,7 @@ async def dev(
|
|||
logger.error(
|
||||
"Dev server failed",
|
||||
extra={
|
||||
"file": str(file),
|
||||
"file": str(server_spec),
|
||||
"error": str(e),
|
||||
"returncode": e.returncode,
|
||||
},
|
||||
|
|
@ -336,7 +334,7 @@ async def dev(
|
|||
"npx not found. Please ensure Node.js and npm are properly installed "
|
||||
"and added to your system PATH. You may need to restart your terminal "
|
||||
"after installation.",
|
||||
extra={"file": str(file)},
|
||||
extra={"file": str(server_spec)},
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
|
@ -385,7 +383,7 @@ async def run(
|
|||
cyclopts.Parameter(
|
||||
"--no-banner",
|
||||
help="Don't show the server banner",
|
||||
negative=False,
|
||||
negative="",
|
||||
),
|
||||
] = False,
|
||||
python: Annotated[
|
||||
|
|
@ -400,7 +398,7 @@ async def run(
|
|||
cyclopts.Parameter(
|
||||
"--with",
|
||||
help="Additional packages to install (can be used multiple times)",
|
||||
negative=False,
|
||||
negative="",
|
||||
),
|
||||
] = [],
|
||||
project: Annotated[
|
||||
|
|
@ -417,6 +415,22 @@ async def run(
|
|||
help="Requirements file to install dependencies from",
|
||||
),
|
||||
] = None,
|
||||
skip_env: Annotated[
|
||||
bool,
|
||||
cyclopts.Parameter(
|
||||
"--skip-env",
|
||||
help="Skip environment setup with uv (use when already in a uv environment)",
|
||||
negative="",
|
||||
),
|
||||
] = False,
|
||||
skip_source: Annotated[
|
||||
bool,
|
||||
cyclopts.Parameter(
|
||||
"--skip-source",
|
||||
help="Skip source preparation step (use when source is already prepared)",
|
||||
negative="",
|
||||
),
|
||||
] = False,
|
||||
) -> None:
|
||||
"""Run an MCP server or connect to a remote one.
|
||||
|
||||
|
|
@ -441,6 +455,7 @@ async def run(
|
|||
|
||||
config = None
|
||||
config_path = None
|
||||
editable = None # Initialize editable variable
|
||||
|
||||
# Auto-detect fastmcp.json if no server_spec provided
|
||||
if server_spec is None:
|
||||
|
|
@ -460,41 +475,69 @@ async def run(
|
|||
server_spec = str(config_path)
|
||||
logger.info(f"Using configuration from {config_path}")
|
||||
|
||||
# Load config if server_spec is a fastmcp.json file
|
||||
if server_spec.endswith("fastmcp.json"):
|
||||
# Load config if server_spec is a .json file
|
||||
if server_spec.endswith(".json"):
|
||||
config_path = Path(server_spec)
|
||||
if config_path.exists():
|
||||
config = FastMCPConfig.from_file(config_path)
|
||||
# Try to load as JSON and discriminate between FastMCPConfig and MCPConfig
|
||||
try:
|
||||
with open(config_path) as f:
|
||||
data = json.load(f)
|
||||
|
||||
# Merge deployment config with CLI values (CLI takes precedence)
|
||||
if config.deployment:
|
||||
merged_deploy = config.deployment.merge_with_cli_args(
|
||||
transport=transport,
|
||||
host=host,
|
||||
port=port,
|
||||
path=path,
|
||||
log_level=log_level,
|
||||
server_args=list(server_args) if server_args else None,
|
||||
)
|
||||
transport = merged_deploy["transport"]
|
||||
host = merged_deploy["host"]
|
||||
port = merged_deploy["port"]
|
||||
path = merged_deploy["path"]
|
||||
log_level = merged_deploy["log_level"]
|
||||
server_args = merged_deploy["server_args"] or ()
|
||||
# Check if it's an MCPConfig first (has canonical mcpServers key)
|
||||
if "mcpServers" in data:
|
||||
# It's an MCPConfig, we don't process these in the run command
|
||||
# They should be handled through different code paths
|
||||
config = None
|
||||
else:
|
||||
# Try to parse as FastMCPConfig
|
||||
try:
|
||||
adapter = get_cached_typeadapter(FastMCPConfig)
|
||||
config = adapter.validate_python(data)
|
||||
|
||||
# Merge environment config with CLI values (CLI takes precedence)
|
||||
if config.environment:
|
||||
merged_env = config.environment.merge_with_cli_args(
|
||||
python=python,
|
||||
with_packages=with_packages,
|
||||
with_requirements=with_requirements,
|
||||
project=project,
|
||||
)
|
||||
python = merged_env["python"]
|
||||
with_packages = merged_env["with_packages"]
|
||||
with_requirements = merged_env["with_requirements"]
|
||||
project = merged_env["project"]
|
||||
# Merge deployment config with CLI values (CLI takes precedence)
|
||||
if config.deployment:
|
||||
transport = transport or config.deployment.transport
|
||||
host = host or config.deployment.host
|
||||
port = port or config.deployment.port
|
||||
path = path or config.deployment.path
|
||||
log_level = log_level or config.deployment.log_level
|
||||
server_args = (
|
||||
tuple(server_args)
|
||||
if server_args
|
||||
else tuple(config.deployment.args or ())
|
||||
)
|
||||
|
||||
# Merge environment config with CLI values (CLI takes precedence)
|
||||
if config.environment:
|
||||
python = python or config.environment.python
|
||||
project = project or (
|
||||
Path(config.environment.project)
|
||||
if config.environment.project
|
||||
else None
|
||||
)
|
||||
with_requirements = with_requirements or (
|
||||
Path(config.environment.requirements)
|
||||
if config.environment.requirements
|
||||
else None
|
||||
)
|
||||
# Extract editable from config (no CLI override for this)
|
||||
editable = config.environment.editable
|
||||
|
||||
# Merge packages from both sources
|
||||
if config.environment.dependencies:
|
||||
packages = list(config.environment.dependencies)
|
||||
if with_packages:
|
||||
packages.extend(with_packages)
|
||||
with_packages = packages
|
||||
except ValidationError:
|
||||
# Not a valid FastMCPConfig, treat as regular server spec
|
||||
config = None
|
||||
except (json.JSONDecodeError, FileNotFoundError):
|
||||
# Not a valid JSON file, treat as regular server spec
|
||||
config = None
|
||||
else:
|
||||
config = None
|
||||
logger.debug(
|
||||
"Running server or client",
|
||||
extra={
|
||||
|
|
@ -509,8 +552,11 @@ async def run(
|
|||
)
|
||||
|
||||
# Check if we need to use uv run (either from CLI args or config)
|
||||
needs_uv = python or with_packages or with_requirements or project
|
||||
if not needs_uv and config and config.environment:
|
||||
# Skip if --skip-env flag is set (we're already in a uv environment)
|
||||
needs_uv = not skip_env and (
|
||||
python or with_packages or with_requirements or project or editable
|
||||
)
|
||||
if not skip_env and not needs_uv and config and config.environment:
|
||||
# Check if config's environment needs uv
|
||||
needs_uv = config.environment.needs_uv()
|
||||
|
||||
|
|
@ -529,6 +575,7 @@ async def run(
|
|||
path=path,
|
||||
log_level=log_level,
|
||||
show_banner=not no_banner,
|
||||
editable=editable,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
|
|
@ -551,6 +598,7 @@ async def run(
|
|||
log_level=log_level,
|
||||
server_args=list(server_args),
|
||||
show_banner=not no_banner,
|
||||
skip_source=skip_source,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
|
|
@ -586,7 +634,7 @@ async def inspect(
|
|||
cyclopts.Parameter(
|
||||
"--with",
|
||||
help="Additional packages to install (can be used multiple times)",
|
||||
negative=False,
|
||||
negative="",
|
||||
),
|
||||
] = [],
|
||||
project: Annotated[
|
||||
|
|
@ -621,10 +669,10 @@ async def inspect(
|
|||
Args:
|
||||
server_spec: Python file to inspect, optionally with :object suffix, or fastmcp.json
|
||||
"""
|
||||
# Load configuration if needed
|
||||
from pathlib import Path
|
||||
|
||||
from fastmcp.utilities.fastmcp_config import FastMCPConfig
|
||||
from fastmcp.utilities.fastmcp_config.v1.sources.filesystem import FileSystemSource
|
||||
|
||||
config = None
|
||||
config_path = None
|
||||
|
|
@ -647,31 +695,53 @@ async def inspect(
|
|||
server_spec = str(config_path)
|
||||
logger.info(f"Using configuration from {config_path}")
|
||||
|
||||
# Load config if server_spec is a fastmcp.json file
|
||||
if server_spec.endswith("fastmcp.json"):
|
||||
# Create FastMCPConfig from server_spec
|
||||
if server_spec.endswith(".json"):
|
||||
config_path = Path(server_spec)
|
||||
if config_path.exists():
|
||||
config = FastMCPConfig.from_file(config_path)
|
||||
# Get the actual entrypoint with resolved paths
|
||||
entrypoint = config.get_entrypoint(config_path)
|
||||
try:
|
||||
with open(config_path) as f:
|
||||
data = json.load(f)
|
||||
|
||||
if entrypoint.object:
|
||||
server_spec = f"{entrypoint.file}:{entrypoint.object}"
|
||||
else:
|
||||
server_spec = entrypoint.file
|
||||
# Check if it's an MCPConfig (has mcpServers key)
|
||||
if "mcpServers" in data:
|
||||
# MCPConfig - we don't process these in inspect
|
||||
logger.error("MCPConfig files are not supported by inspect command")
|
||||
sys.exit(1)
|
||||
else:
|
||||
# It's a FastMCPConfig
|
||||
config = FastMCPConfig.from_file(config_path)
|
||||
|
||||
# Merge environment settings from config with CLI (CLI takes precedence)
|
||||
if config.environment:
|
||||
merged_env = config.environment.merge_with_cli_args(
|
||||
python=python,
|
||||
with_packages=with_packages,
|
||||
with_requirements=with_requirements,
|
||||
project=project,
|
||||
)
|
||||
python = merged_env["python"]
|
||||
with_packages = merged_env["with_packages"]
|
||||
with_requirements = merged_env["with_requirements"]
|
||||
project = merged_env["project"]
|
||||
# Merge environment settings from config with CLI (CLI takes precedence)
|
||||
if config.environment:
|
||||
python = python or config.environment.python
|
||||
project = project or (
|
||||
Path(config.environment.project)
|
||||
if config.environment.project
|
||||
else None
|
||||
)
|
||||
with_requirements = with_requirements or (
|
||||
Path(config.environment.requirements)
|
||||
if config.environment.requirements
|
||||
else None
|
||||
)
|
||||
|
||||
# Merge packages from both sources
|
||||
if config.environment.dependencies:
|
||||
packages = list(config.environment.dependencies)
|
||||
if with_packages:
|
||||
packages.extend(with_packages)
|
||||
with_packages = packages
|
||||
except (json.JSONDecodeError, ValidationError) as e:
|
||||
logger.error(f"Invalid configuration file: {e}")
|
||||
sys.exit(1)
|
||||
else:
|
||||
logger.error(f"Configuration file not found: {config_path}")
|
||||
sys.exit(1)
|
||||
else:
|
||||
# Create config from file path
|
||||
source = FileSystemSource(path=server_spec)
|
||||
config = FastMCPConfig(source=source)
|
||||
|
||||
# Check if we need to use uv run
|
||||
needs_uv = python or with_packages or with_requirements or project
|
||||
|
|
@ -680,54 +750,37 @@ async def inspect(
|
|||
|
||||
if needs_uv:
|
||||
# Build and run uv command
|
||||
if config and config.environment:
|
||||
# Use environment config's run_with_uv method
|
||||
inspect_command = [
|
||||
"fastmcp",
|
||||
"inspect",
|
||||
server_spec,
|
||||
"--output",
|
||||
str(output),
|
||||
]
|
||||
config.environment.run_with_uv(inspect_command)
|
||||
else:
|
||||
# Build an EnvironmentConfig from CLI args for consistency
|
||||
from fastmcp.utilities.fastmcp_config import (
|
||||
EnvironmentConfig,
|
||||
)
|
||||
from fastmcp.utilities.fastmcp_config import Environment
|
||||
|
||||
env_config = EnvironmentConfig(
|
||||
python=python,
|
||||
dependencies=with_packages,
|
||||
requirements=str(with_requirements) if with_requirements else None,
|
||||
project=str(project) if project else None,
|
||||
)
|
||||
# Create or update environment config
|
||||
env_config = Environment(
|
||||
python=python,
|
||||
dependencies=with_packages if with_packages else None,
|
||||
requirements=str(with_requirements) if with_requirements else None,
|
||||
project=str(project) if project else None,
|
||||
)
|
||||
|
||||
inspect_command = [
|
||||
"fastmcp",
|
||||
"inspect",
|
||||
server_spec,
|
||||
"--output",
|
||||
str(output),
|
||||
]
|
||||
env_config.run_with_uv(inspect_command)
|
||||
|
||||
# Direct import path (no uv needed)
|
||||
# Parse the server specification
|
||||
file, server_object = run_module.parse_file_path(server_spec)
|
||||
inspect_command = [
|
||||
"fastmcp",
|
||||
"inspect",
|
||||
server_spec,
|
||||
"--output",
|
||||
str(output),
|
||||
]
|
||||
env_config.run_with_uv(inspect_command)
|
||||
return # run_with_uv exits the process
|
||||
|
||||
logger.debug(
|
||||
"Inspecting server",
|
||||
extra={
|
||||
"file": str(file),
|
||||
"server_object": server_object,
|
||||
"server_spec": server_spec,
|
||||
"output": str(output),
|
||||
},
|
||||
)
|
||||
|
||||
try:
|
||||
# Import the server
|
||||
server = await run_module.import_server(file, server_object)
|
||||
# Load the server using the config
|
||||
server = await config.source.load_server()
|
||||
|
||||
# Get server information - using native async support
|
||||
info = await inspect_fastmcp(server)
|
||||
|
|
@ -765,43 +818,6 @@ async def inspect(
|
|||
sys.exit(1)
|
||||
|
||||
|
||||
@app.command
|
||||
def generate_schema(
|
||||
*,
|
||||
output: Annotated[
|
||||
Path | None,
|
||||
cyclopts.Parameter(
|
||||
name=["--output", "-o"],
|
||||
help="Output file path for the JSON schema",
|
||||
),
|
||||
] = None,
|
||||
) -> None:
|
||||
"""Generate JSON schema for fastmcp.json configuration files.
|
||||
|
||||
This generates a JSON schema that can be used by IDEs and validators
|
||||
to provide auto-completion and validation for fastmcp.json files.
|
||||
|
||||
Examples:
|
||||
fastmcp generate-schema
|
||||
fastmcp generate-schema -o schema.json
|
||||
"""
|
||||
import json
|
||||
|
||||
from fastmcp.utilities.fastmcp_config import (
|
||||
generate_schema as gen_schema,
|
||||
)
|
||||
|
||||
schema = gen_schema()
|
||||
schema_json = json.dumps(schema, indent=2)
|
||||
|
||||
if output:
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_text(schema_json)
|
||||
logger.info(f"Schema written to {output}")
|
||||
else:
|
||||
console.print(schema_json)
|
||||
|
||||
|
||||
# Add install subcommands using proper Cyclopts pattern
|
||||
app.command(install_app)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from typing import Annotated
|
|||
import cyclopts
|
||||
from rich import print
|
||||
|
||||
from fastmcp.utilities.fastmcp_config import Environment
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
|
||||
from .shared import process_common_args
|
||||
|
|
@ -106,31 +107,23 @@ def install_claude_code(
|
|||
)
|
||||
return False
|
||||
|
||||
# Build uv run command
|
||||
args = ["run"]
|
||||
|
||||
# Add Python version if specified
|
||||
if python_version:
|
||||
args.extend(["--python", python_version])
|
||||
|
||||
# Add project if specified
|
||||
if project:
|
||||
args.extend(["--project", str(project)])
|
||||
|
||||
# Collect all packages in a set to deduplicate
|
||||
packages = {"fastmcp"}
|
||||
# Deduplicate packages and exclude 'fastmcp' since Environment adds it automatically
|
||||
deduplicated_packages = None
|
||||
if with_packages:
|
||||
packages.update(pkg for pkg in with_packages if pkg)
|
||||
deduplicated = list(dict.fromkeys(with_packages))
|
||||
deduplicated_packages = [pkg for pkg in deduplicated if pkg != "fastmcp"]
|
||||
if not deduplicated_packages:
|
||||
deduplicated_packages = None
|
||||
|
||||
# Add all packages with --with
|
||||
for pkg in sorted(packages):
|
||||
args.extend(["--with", pkg])
|
||||
|
||||
if with_editable:
|
||||
args.extend(["--with-editable", str(with_editable)])
|
||||
|
||||
if with_requirements:
|
||||
args.extend(["--with-requirements", str(with_requirements)])
|
||||
# Build uv run command using Environment.build_uv_args()
|
||||
env_config = Environment(
|
||||
python=python_version,
|
||||
dependencies=deduplicated_packages,
|
||||
requirements=str(with_requirements) if with_requirements else None,
|
||||
project=str(project) if project else None,
|
||||
editable=str(with_editable) if with_editable else None,
|
||||
)
|
||||
args = env_config.build_uv_args()
|
||||
|
||||
# Build server spec from parsed components
|
||||
if server_object:
|
||||
|
|
@ -189,7 +182,7 @@ async def claude_code_command(
|
|||
cyclopts.Parameter(
|
||||
"--with",
|
||||
help="Additional packages to install",
|
||||
negative=False,
|
||||
negative="",
|
||||
),
|
||||
] = [],
|
||||
env_vars: Annotated[
|
||||
|
|
@ -197,7 +190,7 @@ async def claude_code_command(
|
|||
cyclopts.Parameter(
|
||||
"--env",
|
||||
help="Environment variables in KEY=VALUE format",
|
||||
negative=False,
|
||||
negative="",
|
||||
),
|
||||
] = [],
|
||||
env_file: Annotated[
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import cyclopts
|
|||
from rich import print
|
||||
|
||||
from fastmcp.mcp_config import StdioMCPServer, update_config_file
|
||||
from fastmcp.utilities.fastmcp_config import Environment
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
|
||||
from .shared import process_common_args
|
||||
|
|
@ -72,31 +73,22 @@ def install_claude_desktop(
|
|||
|
||||
config_file = config_dir / "claude_desktop_config.json"
|
||||
|
||||
# Build uv run command
|
||||
args = ["run"]
|
||||
|
||||
# Add Python version if specified
|
||||
if python_version:
|
||||
args.extend(["--python", python_version])
|
||||
|
||||
# Add project if specified
|
||||
if project:
|
||||
args.extend(["--project", str(project)])
|
||||
|
||||
# Collect all packages in a set to deduplicate
|
||||
packages = {"fastmcp"}
|
||||
# Deduplicate packages and exclude 'fastmcp' since Environment adds it automatically
|
||||
deduplicated_packages = None
|
||||
if with_packages:
|
||||
packages.update(pkg for pkg in with_packages if pkg)
|
||||
deduplicated = list(dict.fromkeys(with_packages))
|
||||
deduplicated_packages = [pkg for pkg in deduplicated if pkg != "fastmcp"]
|
||||
if not deduplicated_packages:
|
||||
deduplicated_packages = None
|
||||
|
||||
# Add all packages with --with
|
||||
for pkg in sorted(packages):
|
||||
args.extend(["--with", pkg])
|
||||
|
||||
if with_editable:
|
||||
args.extend(["--with-editable", str(with_editable)])
|
||||
|
||||
if with_requirements:
|
||||
args.extend(["--with-requirements", str(with_requirements)])
|
||||
env_config = Environment(
|
||||
python=python_version,
|
||||
dependencies=deduplicated_packages,
|
||||
requirements=str(with_requirements) if with_requirements else None,
|
||||
project=str(project) if project else None,
|
||||
editable=str(with_editable) if with_editable else None,
|
||||
)
|
||||
args = env_config.build_uv_args()
|
||||
|
||||
# Build server spec from parsed components
|
||||
if server_object:
|
||||
|
|
@ -162,7 +154,7 @@ async def claude_desktop_command(
|
|||
cyclopts.Parameter(
|
||||
"--with",
|
||||
help="Additional packages to install",
|
||||
negative=False,
|
||||
negative="",
|
||||
),
|
||||
] = [],
|
||||
env_vars: Annotated[
|
||||
|
|
@ -170,7 +162,7 @@ async def claude_desktop_command(
|
|||
cyclopts.Parameter(
|
||||
"--env",
|
||||
help="Environment variables in KEY=VALUE format",
|
||||
negative=False,
|
||||
negative="",
|
||||
),
|
||||
] = [],
|
||||
env_file: Annotated[
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import cyclopts
|
|||
from rich import print
|
||||
|
||||
from fastmcp.mcp_config import StdioMCPServer, update_config_file
|
||||
from fastmcp.utilities.fastmcp_config import Environment
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
|
||||
from .shared import process_common_args
|
||||
|
|
@ -106,31 +107,22 @@ def install_cursor_workspace(
|
|||
|
||||
config_file = cursor_dir / "mcp.json"
|
||||
|
||||
# Build uv run command
|
||||
args = ["run"]
|
||||
|
||||
# Add Python version if specified
|
||||
if python_version:
|
||||
args.extend(["--python", python_version])
|
||||
|
||||
# Add project if specified
|
||||
if project:
|
||||
args.extend(["--project", str(project)])
|
||||
|
||||
# Collect all packages in a set to deduplicate
|
||||
packages = {"fastmcp"}
|
||||
# Deduplicate packages and exclude 'fastmcp' since Environment adds it automatically
|
||||
deduplicated_packages = None
|
||||
if with_packages:
|
||||
packages.update(pkg for pkg in with_packages if pkg)
|
||||
deduplicated = list(dict.fromkeys(with_packages))
|
||||
deduplicated_packages = [pkg for pkg in deduplicated if pkg != "fastmcp"]
|
||||
if not deduplicated_packages:
|
||||
deduplicated_packages = None
|
||||
|
||||
# Add all packages with --with
|
||||
for pkg in sorted(packages):
|
||||
args.extend(["--with", pkg])
|
||||
|
||||
if with_editable:
|
||||
args.extend(["--with-editable", str(with_editable)])
|
||||
|
||||
if with_requirements:
|
||||
args.extend(["--with-requirements", str(with_requirements)])
|
||||
env_config = Environment(
|
||||
python=python_version,
|
||||
dependencies=deduplicated_packages,
|
||||
requirements=str(with_requirements.resolve()) if with_requirements else None,
|
||||
project=str(project.resolve()) if project else None,
|
||||
editable=str(with_editable.resolve()) if with_editable else None,
|
||||
)
|
||||
args = env_config.build_uv_args()
|
||||
|
||||
# Build server spec from parsed components
|
||||
if server_object:
|
||||
|
|
@ -194,31 +186,23 @@ def install_cursor(
|
|||
Returns:
|
||||
True if installation was successful, False otherwise
|
||||
"""
|
||||
# Build uv run command
|
||||
args = ["run"]
|
||||
|
||||
# Add Python version if specified
|
||||
if python_version:
|
||||
args.extend(["--python", python_version])
|
||||
|
||||
# Add project if specified
|
||||
if project:
|
||||
args.extend(["--project", str(project)])
|
||||
|
||||
# Collect all packages in a set to deduplicate
|
||||
packages = {"fastmcp"}
|
||||
# Deduplicate packages and exclude 'fastmcp' since Environment adds it automatically
|
||||
deduplicated_packages = None
|
||||
if with_packages:
|
||||
packages.update(pkg for pkg in with_packages if pkg)
|
||||
deduplicated = list(dict.fromkeys(with_packages))
|
||||
deduplicated_packages = [pkg for pkg in deduplicated if pkg != "fastmcp"]
|
||||
if not deduplicated_packages:
|
||||
deduplicated_packages = None
|
||||
|
||||
# Add all packages with --with
|
||||
for pkg in sorted(packages):
|
||||
args.extend(["--with", pkg])
|
||||
|
||||
if with_editable:
|
||||
args.extend(["--with-editable", str(with_editable)])
|
||||
|
||||
if with_requirements:
|
||||
args.extend(["--with-requirements", str(with_requirements)])
|
||||
env_config = Environment(
|
||||
python=python_version,
|
||||
dependencies=deduplicated_packages,
|
||||
requirements=str(with_requirements.resolve()) if with_requirements else None,
|
||||
project=str(project.resolve()) if project else None,
|
||||
editable=str(with_editable.resolve()) if with_editable else None,
|
||||
)
|
||||
args = env_config.build_uv_args()
|
||||
|
||||
# Build server spec from parsed components
|
||||
if server_object:
|
||||
|
|
@ -289,7 +273,7 @@ async def cursor_command(
|
|||
cyclopts.Parameter(
|
||||
"--with",
|
||||
help="Additional packages to install",
|
||||
negative=False,
|
||||
negative="",
|
||||
),
|
||||
] = [],
|
||||
env_vars: Annotated[
|
||||
|
|
@ -297,7 +281,7 @@ async def cursor_command(
|
|||
cyclopts.Parameter(
|
||||
"--env",
|
||||
help="Environment variables in KEY=VALUE format",
|
||||
negative=False,
|
||||
negative="",
|
||||
),
|
||||
] = [],
|
||||
env_file: Annotated[
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import cyclopts
|
|||
import pyperclip
|
||||
from rich import print
|
||||
|
||||
from fastmcp.utilities.fastmcp_config import Environment
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
|
||||
from .shared import process_common_args
|
||||
|
|
@ -47,31 +48,22 @@ def install_mcp_json(
|
|||
True if generation was successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
# Build uv run command
|
||||
args = ["run"]
|
||||
|
||||
# Add Python version if specified
|
||||
if python_version:
|
||||
args.extend(["--python", python_version])
|
||||
|
||||
# Add project if specified
|
||||
if project:
|
||||
args.extend(["--project", str(project)])
|
||||
|
||||
# Collect all packages in a set to deduplicate
|
||||
packages = {"fastmcp"}
|
||||
# Deduplicate packages and exclude 'fastmcp' since Environment adds it automatically
|
||||
deduplicated_packages = None
|
||||
if with_packages:
|
||||
packages.update(pkg for pkg in with_packages if pkg)
|
||||
deduplicated = list(dict.fromkeys(with_packages))
|
||||
deduplicated_packages = [pkg for pkg in deduplicated if pkg != "fastmcp"]
|
||||
if not deduplicated_packages:
|
||||
deduplicated_packages = None
|
||||
|
||||
# Add all packages with --with
|
||||
for pkg in sorted(packages):
|
||||
args.extend(["--with", pkg])
|
||||
|
||||
if with_editable:
|
||||
args.extend(["--with-editable", str(with_editable)])
|
||||
|
||||
if with_requirements:
|
||||
args.extend(["--with-requirements", str(with_requirements)])
|
||||
env_config = Environment(
|
||||
python=python_version,
|
||||
dependencies=deduplicated_packages,
|
||||
requirements=str(with_requirements) if with_requirements else None,
|
||||
project=str(project) if project else None,
|
||||
editable=str(with_editable) if with_editable else None,
|
||||
)
|
||||
args = env_config.build_uv_args()
|
||||
|
||||
# Build server spec from parsed components
|
||||
if server_object:
|
||||
|
|
@ -135,7 +127,7 @@ async def mcp_json_command(
|
|||
cyclopts.Parameter(
|
||||
"--with",
|
||||
help="Additional packages to install",
|
||||
negative=False,
|
||||
negative="",
|
||||
),
|
||||
] = [],
|
||||
env_vars: Annotated[
|
||||
|
|
@ -143,7 +135,7 @@ async def mcp_json_command(
|
|||
cyclopts.Parameter(
|
||||
"--env",
|
||||
help="Environment variables in KEY=VALUE format",
|
||||
negative=False,
|
||||
negative="",
|
||||
),
|
||||
] = [],
|
||||
env_file: Annotated[
|
||||
|
|
@ -158,7 +150,7 @@ async def mcp_json_command(
|
|||
cyclopts.Parameter(
|
||||
"--copy",
|
||||
help="Copy configuration to clipboard instead of printing to stdout",
|
||||
negative=False,
|
||||
negative="",
|
||||
),
|
||||
] = False,
|
||||
python: Annotated[
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
"""Shared utilities for install commands."""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import dotenv_values
|
||||
from pydantic import ValidationError
|
||||
from rich import print
|
||||
|
||||
from fastmcp.cli.run import import_server, parse_file_path
|
||||
from fastmcp.utilities.fastmcp_config import FastMCPConfig
|
||||
from fastmcp.utilities.fastmcp_config.v1.sources.filesystem import FileSystemSource
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
|
@ -34,31 +37,46 @@ async def process_common_args(
|
|||
|
||||
Handles both fastmcp.json config files and traditional file.py:object syntax.
|
||||
"""
|
||||
# Check if server_spec is a fastmcp.json file
|
||||
if server_spec.endswith("fastmcp.json") or "fastmcp.json" in Path(server_spec).name:
|
||||
from fastmcp.utilities.fastmcp_config import FastMCPConfig
|
||||
|
||||
# Create FastMCPConfig from server_spec
|
||||
config = None
|
||||
if server_spec.endswith(".json"):
|
||||
config_path = Path(server_spec).resolve()
|
||||
if not config_path.exists():
|
||||
print(f"[red]Configuration file not found: {config_path}[/red]")
|
||||
sys.exit(1)
|
||||
|
||||
# Load config and get entrypoint
|
||||
config = FastMCPConfig.from_file(config_path)
|
||||
entrypoint = config.get_entrypoint(config_path)
|
||||
try:
|
||||
with open(config_path) as f:
|
||||
data = json.load(f)
|
||||
|
||||
# Convert to file and server_object
|
||||
file = Path(entrypoint.file)
|
||||
server_object = entrypoint.object
|
||||
# Check if it's an MCPConfig (has mcpServers key)
|
||||
if "mcpServers" in data:
|
||||
# MCPConfig files aren't supported for install
|
||||
print("[red]MCPConfig files are not supported for installation[/red]")
|
||||
sys.exit(1)
|
||||
else:
|
||||
# It's a FastMCPConfig
|
||||
config = FastMCPConfig.from_file(config_path)
|
||||
|
||||
# Merge packages from config if not overridden
|
||||
if config.environment and config.environment.dependencies:
|
||||
# Merge with CLI packages (CLI takes precedence)
|
||||
config_packages = config.environment.dependencies or []
|
||||
with_packages = list(set(with_packages + config_packages))
|
||||
# Merge packages from config if not overridden
|
||||
if config.environment and config.environment.dependencies:
|
||||
# Merge with CLI packages (CLI takes precedence)
|
||||
config_packages = list(config.environment.dependencies) or []
|
||||
with_packages = list(set(with_packages + config_packages))
|
||||
except (json.JSONDecodeError, ValidationError) as e:
|
||||
print(f"[red]Invalid configuration file: {e}[/red]")
|
||||
sys.exit(1)
|
||||
else:
|
||||
# Parse traditional server spec
|
||||
file, server_object = parse_file_path(server_spec)
|
||||
# Create config from file path
|
||||
source = FileSystemSource(path=server_spec)
|
||||
config = FastMCPConfig(source=source)
|
||||
|
||||
# Extract file and server_object from the source
|
||||
# The FileSystemSource handles parsing path:object syntax
|
||||
file = Path(config.source.path).resolve()
|
||||
server_object = (
|
||||
config.source.entrypoint if hasattr(config.source, "entrypoint") else None
|
||||
)
|
||||
|
||||
logger.debug(
|
||||
"Installing server",
|
||||
|
|
@ -75,7 +93,7 @@ async def process_common_args(
|
|||
server = None
|
||||
if not name:
|
||||
try:
|
||||
server = await import_server(file, server_object)
|
||||
server = await config.source.load_server()
|
||||
name = server.name
|
||||
except (ImportError, ModuleNotFoundError) as e:
|
||||
logger.debug(
|
||||
|
|
|
|||
|
|
@ -1,25 +1,23 @@
|
|||
"""FastMCP run command implementation with enhanced type hints."""
|
||||
|
||||
import importlib.util
|
||||
import inspect
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
from typing import Any, Literal
|
||||
|
||||
from mcp.server.fastmcp import FastMCP as FastMCP1x
|
||||
from pydantic import ValidationError
|
||||
|
||||
from fastmcp.server.server import FastMCP
|
||||
from fastmcp.utilities.fastmcp_config import (
|
||||
DeploymentConfig,
|
||||
EntrypointConfig,
|
||||
EnvironmentConfig,
|
||||
Environment,
|
||||
FastMCPConfig,
|
||||
)
|
||||
from fastmcp.utilities.fastmcp_config.v1.sources.filesystem import FileSystemSource
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
from fastmcp.utilities.types import get_cached_typeadapter
|
||||
|
||||
logger = get_logger("cli.run")
|
||||
|
||||
|
|
@ -34,150 +32,6 @@ def is_url(path: str) -> bool:
|
|||
return bool(url_pattern.match(path))
|
||||
|
||||
|
||||
def parse_file_path(server_spec: str) -> tuple[Path, str | None]:
|
||||
"""Parse a file path that may include a server object specification.
|
||||
|
||||
Args:
|
||||
server_spec: Path to file, optionally with :object suffix
|
||||
|
||||
Returns:
|
||||
Tuple of (file_path, server_object)
|
||||
"""
|
||||
# First check if we have a Windows path (e.g., C:\...)
|
||||
has_windows_drive = len(server_spec) > 1 and server_spec[1] == ":"
|
||||
|
||||
# Split on the last colon, but only if it's not part of the Windows drive letter
|
||||
# and there's actually another colon in the string after the drive letter
|
||||
if ":" in (server_spec[2:] if has_windows_drive else server_spec):
|
||||
file_str, server_object = server_spec.rsplit(":", 1)
|
||||
else:
|
||||
file_str, server_object = server_spec, None
|
||||
|
||||
# Resolve the file path
|
||||
file_path = Path(file_str).expanduser().resolve()
|
||||
if not file_path.exists():
|
||||
logger.error(f"File not found: {file_path}")
|
||||
sys.exit(1)
|
||||
if not file_path.is_file():
|
||||
logger.error(f"Not a file: {file_path}")
|
||||
sys.exit(1)
|
||||
|
||||
return file_path, server_object
|
||||
|
||||
|
||||
async def import_server(file: Path, server_or_factory: str | None = None) -> Any:
|
||||
"""Import a MCP server from a file.
|
||||
|
||||
Args:
|
||||
file: Path to the file
|
||||
server_or_factory: Optional object name in format "module:object" or just "object"
|
||||
|
||||
Returns:
|
||||
The server object (or result of calling a factory function)
|
||||
"""
|
||||
# Add parent directory to Python path so imports can be resolved
|
||||
file_dir = str(file.parent)
|
||||
if file_dir not in sys.path:
|
||||
sys.path.insert(0, file_dir)
|
||||
|
||||
# Import the module
|
||||
spec = importlib.util.spec_from_file_location("server_module", file)
|
||||
if not spec or not spec.loader:
|
||||
logger.error("Could not load module", extra={"file": str(file)})
|
||||
sys.exit(1)
|
||||
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
# If no object specified, try common server names
|
||||
if not server_or_factory:
|
||||
# Look for common server instance names
|
||||
for name in ["mcp", "server", "app"]:
|
||||
if hasattr(module, name):
|
||||
obj = getattr(module, name)
|
||||
if isinstance(obj, FastMCP | FastMCP1x):
|
||||
return await _resolve_server_or_factory(obj, file, name)
|
||||
|
||||
logger.error(
|
||||
f"No server object found in {file}. Please either:\n"
|
||||
"1. Use a standard variable name (mcp, server, or app)\n"
|
||||
"2. Specify the object name in fastmcp.json or use `file.py:object` syntax as your path.",
|
||||
extra={"file": str(file)},
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# Handle module:object syntax
|
||||
if server_or_factory and ":" in server_or_factory:
|
||||
module_name, object_name = server_or_factory.split(":", 1)
|
||||
try:
|
||||
server_module = importlib.import_module(module_name)
|
||||
obj = getattr(server_module, object_name, None)
|
||||
except ImportError:
|
||||
logger.error(
|
||||
f"Could not import module '{module_name}'",
|
||||
extra={"file": str(file)},
|
||||
)
|
||||
sys.exit(1)
|
||||
else:
|
||||
# Just object name
|
||||
obj = getattr(module, server_or_factory, None)
|
||||
|
||||
if obj is None:
|
||||
logger.error(
|
||||
f"Server object '{server_or_factory}' not found",
|
||||
extra={"file": str(file)},
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
return await _resolve_server_or_factory(obj, file, server_or_factory)
|
||||
|
||||
|
||||
async def _resolve_server_or_factory(obj: Any, file: Path, name: str) -> Any:
|
||||
"""Resolve a server object or factory function to a server instance.
|
||||
|
||||
Args:
|
||||
obj: The object that might be a server or factory function
|
||||
file: Path to the file for error messages
|
||||
name: Name of the object for error messages
|
||||
|
||||
Returns:
|
||||
A server instance
|
||||
"""
|
||||
# Check if it's a function or coroutine function
|
||||
if inspect.isfunction(obj) or inspect.iscoroutinefunction(obj):
|
||||
logger.debug(f"Found factory function '{name}' in {file}")
|
||||
|
||||
try:
|
||||
if inspect.iscoroutinefunction(obj):
|
||||
# Async factory function
|
||||
server = await obj()
|
||||
else:
|
||||
# Sync factory function
|
||||
server = obj()
|
||||
|
||||
# Validate the result is a FastMCP server
|
||||
if not isinstance(server, FastMCP | FastMCP1x):
|
||||
logger.error(
|
||||
f"Factory function '{name}' must return a FastMCP server instance, "
|
||||
f"got {type(server).__name__}",
|
||||
extra={"file": str(file)},
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
logger.debug(f"Factory function '{name}' created server: {server.name}")
|
||||
return server
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Failed to call factory function '{name}': {e}",
|
||||
extra={"file": str(file)},
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# Not a function, return as-is (should be a server instance)
|
||||
return obj
|
||||
|
||||
|
||||
def run_with_uv(
|
||||
server_spec: str,
|
||||
python_version: str | None = None,
|
||||
|
|
@ -190,6 +44,7 @@ def run_with_uv(
|
|||
path: str | None = None,
|
||||
log_level: LogLevelType | None = None,
|
||||
show_banner: bool = True,
|
||||
editable: str | None = None,
|
||||
) -> None:
|
||||
"""Run a MCP server using uv run subprocess.
|
||||
|
||||
|
|
@ -206,80 +61,83 @@ def run_with_uv(
|
|||
log_level: Log level
|
||||
show_banner: Whether to show the server banner
|
||||
"""
|
||||
# Check if server_spec is a fastmcp.json file
|
||||
if server_spec.endswith("fastmcp.json") or "fastmcp.json" in Path(server_spec).name:
|
||||
# Check if server_spec is a .json file
|
||||
if server_spec.endswith(".json"):
|
||||
config_path = Path(server_spec).resolve() # Get absolute path
|
||||
if config_path.exists():
|
||||
# Load config
|
||||
config = FastMCPConfig.from_file(config_path)
|
||||
# Try to load as JSON and discriminate between FastMCPConfig and MCPConfig
|
||||
try:
|
||||
with open(config_path) as f:
|
||||
data = json.load(f)
|
||||
|
||||
# Get entrypoint with resolved paths
|
||||
entrypoint = config.get_entrypoint(config_path)
|
||||
if entrypoint.object:
|
||||
server_spec = f"{entrypoint.file}:{entrypoint.object}"
|
||||
else:
|
||||
server_spec = entrypoint.file
|
||||
# Check if it's an MCPConfig first (has canonical mcpServers key)
|
||||
if "mcpServers" in data:
|
||||
# It's an MCPConfig, we don't process it here - just pass through
|
||||
pass
|
||||
else:
|
||||
# Try to parse as FastMCPConfig
|
||||
try:
|
||||
adapter = get_cached_typeadapter(FastMCPConfig)
|
||||
config: FastMCPConfig = adapter.validate_python(data)
|
||||
|
||||
# Merge environment config with CLI args
|
||||
# Check if environment has any non-None values
|
||||
if config.environment and any(
|
||||
getattr(config.environment, field, None) is not None
|
||||
for field in EnvironmentConfig.model_fields
|
||||
):
|
||||
merged_env = config.environment.merge_with_cli_args(
|
||||
python=python_version,
|
||||
with_packages=with_packages,
|
||||
with_requirements=with_requirements,
|
||||
project=project,
|
||||
)
|
||||
python_version = merged_env["python"]
|
||||
with_packages = merged_env["with_packages"]
|
||||
with_requirements = merged_env["with_requirements"]
|
||||
project = merged_env["project"]
|
||||
# Apply deployment settings
|
||||
if config.deployment:
|
||||
config.deployment.apply_runtime_settings(config_path)
|
||||
|
||||
# Merge deployment config with CLI args
|
||||
# Check if deployment has any non-None values
|
||||
if config.deployment and any(
|
||||
getattr(config.deployment, field, None) is not None
|
||||
for field in DeploymentConfig.model_fields
|
||||
):
|
||||
merged_deploy = config.deployment.merge_with_cli_args(
|
||||
transport=transport,
|
||||
host=host,
|
||||
port=port,
|
||||
path=path,
|
||||
log_level=log_level,
|
||||
)
|
||||
transport = merged_deploy["transport"]
|
||||
host = merged_deploy["host"]
|
||||
port = merged_deploy["port"]
|
||||
path = merged_deploy["path"]
|
||||
log_level = merged_deploy["log_level"]
|
||||
cmd = ["uv", "run"]
|
||||
# Merge environment config with CLI args (CLI takes precedence)
|
||||
if config.environment:
|
||||
# Use CLI values if provided, otherwise fall back to config
|
||||
python_version = python_version or config.environment.python
|
||||
project = project or (
|
||||
Path(config.environment.project)
|
||||
if config.environment.project
|
||||
else None
|
||||
)
|
||||
with_requirements = with_requirements or (
|
||||
Path(config.environment.requirements)
|
||||
if config.environment.requirements
|
||||
else None
|
||||
)
|
||||
editable = editable or config.environment.editable
|
||||
|
||||
# Add Python version if specified
|
||||
if python_version:
|
||||
cmd.extend(["--python", python_version])
|
||||
# Merge packages from both sources
|
||||
# Only merge if with_packages doesn't already contain them
|
||||
# (they may have been merged already in CLI)
|
||||
if config.environment.dependencies and not with_packages:
|
||||
with_packages = list(config.environment.dependencies)
|
||||
|
||||
# Add project if specified
|
||||
if project:
|
||||
cmd.extend(["--project", str(project)])
|
||||
# Merge deployment config with CLI args (CLI takes precedence)
|
||||
if config.deployment:
|
||||
transport = transport or config.deployment.transport
|
||||
host = host or config.deployment.host
|
||||
port = port or config.deployment.port
|
||||
path = path or config.deployment.path
|
||||
log_level = log_level or config.deployment.log_level
|
||||
except ValidationError:
|
||||
# Not a valid FastMCPConfig, just pass through
|
||||
pass
|
||||
except (json.JSONDecodeError, FileNotFoundError):
|
||||
# Not a valid JSON file, just pass through
|
||||
pass
|
||||
|
||||
# Add fastmcp package
|
||||
cmd.extend(["--with", "fastmcp"])
|
||||
|
||||
# Add additional packages
|
||||
if with_packages:
|
||||
for pkg in with_packages:
|
||||
if pkg:
|
||||
cmd.extend(["--with", pkg])
|
||||
|
||||
# Add requirements file
|
||||
if with_requirements:
|
||||
cmd.extend(["--with-requirements", str(with_requirements)])
|
||||
|
||||
# Add fastmcp run command
|
||||
cmd.extend(["fastmcp", "run", server_spec])
|
||||
# Build uv command using Environment.build_uv_args()
|
||||
env_config = Environment(
|
||||
python=python_version,
|
||||
dependencies=with_packages if with_packages else None,
|
||||
requirements=str(with_requirements.resolve()) if with_requirements else None,
|
||||
project=str(project.resolve()) if project else None,
|
||||
editable=editable,
|
||||
)
|
||||
# IMPORTANT: We add --skip-env to prevent infinite recursion.
|
||||
# When this function executes `uv run ... fastmcp run server.py`, the inner
|
||||
# `fastmcp run` command will be executed inside the uv environment we're creating.
|
||||
# Without --skip-env, that inner command would detect it needs uv (due to the same
|
||||
# CLI args) and try to spawn ANOTHER uv subprocess, creating infinite recursion.
|
||||
# The --skip-env flag tells the inner fastmcp: "skip environment setup, we're already
|
||||
# inside the uv environment that was just created for us."
|
||||
cmd = ["uv"] + env_config.build_uv_args(
|
||||
["fastmcp", "run", server_spec, "--skip-env"]
|
||||
)
|
||||
|
||||
# Add transport options
|
||||
if transport:
|
||||
|
|
@ -336,16 +194,14 @@ def create_mcp_config_server(mcp_config_path: Path) -> FastMCP[None]:
|
|||
return server
|
||||
|
||||
|
||||
def load_fastmcp_config(
|
||||
config_path: Path,
|
||||
) -> tuple[EntrypointConfig, DeploymentConfig | None, EnvironmentConfig | None]:
|
||||
def load_fastmcp_config(config_path: Path) -> FastMCPConfig:
|
||||
"""Load a FastMCP configuration from a fastmcp.json file.
|
||||
|
||||
Args:
|
||||
config_path: Path to fastmcp.json file
|
||||
|
||||
Returns:
|
||||
Tuple of (entrypoint, deployment config, environment config)
|
||||
FastMCPConfig object
|
||||
"""
|
||||
config = FastMCPConfig.from_file(config_path)
|
||||
|
||||
|
|
@ -353,55 +209,7 @@ def load_fastmcp_config(
|
|||
if config.deployment:
|
||||
config.deployment.apply_runtime_settings(config_path)
|
||||
|
||||
# Get entrypoint as structured object with resolved paths
|
||||
entrypoint = config.get_entrypoint(config_path)
|
||||
|
||||
# Return None for empty configs (backward compatibility)
|
||||
deployment = (
|
||||
config.deployment
|
||||
if any(
|
||||
getattr(config.deployment, field, None) is not None
|
||||
for field in DeploymentConfig.model_fields
|
||||
)
|
||||
else None
|
||||
)
|
||||
|
||||
environment = (
|
||||
config.environment
|
||||
if any(
|
||||
getattr(config.environment, field, None) is not None
|
||||
for field in EnvironmentConfig.model_fields
|
||||
)
|
||||
else None
|
||||
)
|
||||
|
||||
return entrypoint, deployment, environment
|
||||
|
||||
|
||||
async def import_server_with_args(
|
||||
file: Path,
|
||||
server_or_factory: str | None = None,
|
||||
server_args: list[str] | None = None,
|
||||
) -> Any:
|
||||
"""Import a server with optional command line arguments.
|
||||
|
||||
Args:
|
||||
file: Path to the server file
|
||||
server_or_factory: Optional server object or factory function name
|
||||
server_args: Optional command line arguments to inject
|
||||
|
||||
Returns:
|
||||
The imported server object
|
||||
"""
|
||||
if server_args:
|
||||
original_argv = sys.argv[:]
|
||||
try:
|
||||
sys.argv = [str(file)] + server_args
|
||||
return await import_server(file, server_or_factory)
|
||||
finally:
|
||||
sys.argv = original_argv
|
||||
else:
|
||||
return await import_server(file, server_or_factory)
|
||||
return config
|
||||
|
||||
|
||||
async def run_command(
|
||||
|
|
@ -414,6 +222,7 @@ async def run_command(
|
|||
server_args: list[str] | None = None,
|
||||
show_banner: bool = True,
|
||||
use_direct_import: bool = False,
|
||||
skip_source: bool = False,
|
||||
) -> None:
|
||||
"""Run a MCP server or connect to a remote one.
|
||||
|
||||
|
|
@ -427,49 +236,76 @@ async def run_command(
|
|||
server_args: Additional arguments to pass to the server
|
||||
show_banner: Whether to show the server banner
|
||||
use_direct_import: Whether to use direct import instead of subprocess
|
||||
skip_source: Whether to skip source preparation step
|
||||
"""
|
||||
# Special case: URLs
|
||||
if is_url(server_spec):
|
||||
# Handle URL case
|
||||
server = create_client_server(server_spec)
|
||||
logger.debug(f"Created client proxy server for {server_spec}")
|
||||
elif (
|
||||
server_spec.endswith("fastmcp.json") or "fastmcp.json" in Path(server_spec).name
|
||||
):
|
||||
# Handle fastmcp.json configuration file (matches test_fastmcp.json, my.fastmcp.json, etc)
|
||||
config_path = Path(server_spec)
|
||||
entrypoint, deployment, environment = load_fastmcp_config(config_path)
|
||||
|
||||
# Merge deployment config with CLI arguments (CLI takes precedence)
|
||||
if deployment:
|
||||
merged = deployment.merge_with_cli_args(
|
||||
transport=transport,
|
||||
host=host,
|
||||
port=port,
|
||||
path=path,
|
||||
log_level=log_level,
|
||||
server_args=server_args,
|
||||
)
|
||||
transport = merged["transport"]
|
||||
host = merged["host"]
|
||||
port = merged["port"]
|
||||
path = merged["path"]
|
||||
log_level = merged["log_level"]
|
||||
server_args = merged["server_args"]
|
||||
|
||||
# Import the server from the structured entrypoint
|
||||
file_path = Path(entrypoint.file)
|
||||
server = await import_server_with_args(
|
||||
file_path, entrypoint.object, server_args
|
||||
)
|
||||
logger.debug(f'Found server "{server.name}" from config {config_path}')
|
||||
# Special case: MCPConfig files (legacy)
|
||||
elif server_spec.endswith(".json"):
|
||||
# Handle other JSON files as MCPConfig
|
||||
server = create_mcp_config_server(Path(server_spec))
|
||||
# Load JSON and check which type of config it is
|
||||
config_path = Path(server_spec)
|
||||
with open(config_path) as f:
|
||||
data = json.load(f)
|
||||
|
||||
# Check if it's an MCPConfig first (has canonical mcpServers key)
|
||||
if "mcpServers" in data:
|
||||
# It's an MCP config
|
||||
server = create_mcp_config_server(config_path)
|
||||
else:
|
||||
# It's a FastMCP config - load it properly
|
||||
config = load_fastmcp_config(config_path)
|
||||
|
||||
# Merge deployment config with CLI arguments (CLI takes precedence)
|
||||
if config.deployment:
|
||||
transport = transport or config.deployment.transport
|
||||
host = host or config.deployment.host
|
||||
port = port or config.deployment.port
|
||||
path = path or config.deployment.path
|
||||
log_level = log_level or config.deployment.log_level
|
||||
server_args = (
|
||||
server_args if server_args is not None else config.deployment.args
|
||||
)
|
||||
|
||||
# Prepare the source if needed (e.g., clone git repo, download from cloud)
|
||||
if not skip_source:
|
||||
await config.source.prepare()
|
||||
|
||||
# Load the server using the source
|
||||
from contextlib import nullcontext
|
||||
|
||||
from fastmcp.cli.cli import with_argv
|
||||
|
||||
# Use sys.argv context manager if deployment args specified
|
||||
argv_context = with_argv(server_args) if server_args else nullcontext()
|
||||
|
||||
with argv_context:
|
||||
server = await config.source.load_server()
|
||||
|
||||
logger.debug(f'Found server "{server.name}" from config {config_path}')
|
||||
else:
|
||||
# Handle file case
|
||||
file, server_or_factory = parse_file_path(server_spec)
|
||||
server = await import_server_with_args(file, server_or_factory, server_args)
|
||||
logger.debug(f'Found server "{server.name}" in {file}')
|
||||
# Regular file case - create a FastMCPConfig with FileSystemSource
|
||||
source = FileSystemSource(path=server_spec)
|
||||
config = FastMCPConfig(source=source)
|
||||
|
||||
# Prepare the source if needed
|
||||
if not skip_source:
|
||||
await config.source.prepare()
|
||||
|
||||
# Load the server
|
||||
from contextlib import nullcontext
|
||||
|
||||
from fastmcp.cli.cli import with_argv
|
||||
|
||||
# Use sys.argv context manager if server_args specified
|
||||
argv_context = with_argv(server_args) if server_args else nullcontext()
|
||||
|
||||
with argv_context:
|
||||
server = await config.source.load_server()
|
||||
|
||||
logger.debug(f'Found server "{server.name}" in {source.path}')
|
||||
|
||||
# Run the server
|
||||
|
||||
|
|
@ -506,6 +342,8 @@ def run_v1_server(
|
|||
port: int | None = None,
|
||||
transport: TransportType | None = None,
|
||||
) -> None:
|
||||
from functools import partial
|
||||
|
||||
if host:
|
||||
server.settings.host = host
|
||||
if port:
|
||||
|
|
|
|||
|
|
@ -217,8 +217,13 @@ class OAuth(OAuthClientProvider):
|
|||
self.redirect_port = callback_port or find_available_port()
|
||||
redirect_uri = f"http://localhost:{self.redirect_port}/callback"
|
||||
|
||||
scopes_str: str
|
||||
if isinstance(scopes, list):
|
||||
scopes = " ".join(scopes)
|
||||
scopes_str = " ".join(scopes)
|
||||
elif scopes is not None:
|
||||
scopes_str = str(scopes)
|
||||
else:
|
||||
scopes_str = ""
|
||||
|
||||
client_metadata = OAuthClientMetadata(
|
||||
client_name=client_name,
|
||||
|
|
@ -226,7 +231,7 @@ class OAuth(OAuthClientProvider):
|
|||
grant_types=["authorization_code", "refresh_token"],
|
||||
response_types=["code"],
|
||||
# token_endpoint_auth_method="client_secret_post",
|
||||
scope=scopes,
|
||||
scope=scopes_str,
|
||||
**(additional_client_metadata or {}),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
|||
import asyncio
|
||||
import copy
|
||||
import datetime
|
||||
import secrets
|
||||
from contextlib import AsyncExitStack, asynccontextmanager
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
|
@ -212,6 +213,7 @@ class Client(Generic[ClientTransportT]):
|
|||
| dict[str, Any]
|
||||
| str
|
||||
),
|
||||
name: str | None = None,
|
||||
roots: RootsList | RootsHandler | None = None,
|
||||
sampling_handler: ClientSamplingHandler | None = None,
|
||||
elicitation_handler: ElicitationHandler | None = None,
|
||||
|
|
@ -223,6 +225,8 @@ class Client(Generic[ClientTransportT]):
|
|||
client_info: mcp.types.Implementation | None = None,
|
||||
auth: httpx.Auth | Literal["oauth"] | str | None = None,
|
||||
) -> None:
|
||||
self.name = name or self.generate_name()
|
||||
|
||||
self.transport = cast(ClientTransportT, infer_transport(transport))
|
||||
if auth is not None:
|
||||
self.transport._set_auth(auth)
|
||||
|
|
@ -236,7 +240,7 @@ class Client(Generic[ClientTransportT]):
|
|||
self._progress_handler = progress_handler
|
||||
|
||||
if isinstance(timeout, int | float):
|
||||
timeout = datetime.timedelta(seconds=timeout)
|
||||
timeout = datetime.timedelta(seconds=float(timeout))
|
||||
|
||||
# handle init handshake timeout
|
||||
if init_timeout is None:
|
||||
|
|
@ -339,6 +343,8 @@ class Client(Generic[ClientTransportT]):
|
|||
# Reset session state to fresh state
|
||||
new_client._session_state = ClientSessionState()
|
||||
|
||||
new_client.name += f":{secrets.token_hex(2)}"
|
||||
|
||||
return new_client
|
||||
|
||||
@asynccontextmanager
|
||||
|
|
@ -538,6 +544,8 @@ class Client(Generic[ClientTransportT]):
|
|||
Raises:
|
||||
RuntimeError: If called while the client is not connected.
|
||||
"""
|
||||
logger.debug(f"[{self.name}] called list_resources")
|
||||
|
||||
result = await self.session.list_resources()
|
||||
return result
|
||||
|
||||
|
|
@ -565,6 +573,8 @@ class Client(Generic[ClientTransportT]):
|
|||
Raises:
|
||||
RuntimeError: If called while the client is not connected.
|
||||
"""
|
||||
logger.debug(f"[{self.name}] called list_resource_templates")
|
||||
|
||||
result = await self.session.list_resource_templates()
|
||||
return result
|
||||
|
||||
|
|
@ -597,6 +607,8 @@ class Client(Generic[ClientTransportT]):
|
|||
Raises:
|
||||
RuntimeError: If called while the client is not connected.
|
||||
"""
|
||||
logger.debug(f"[{self.name}] called read_resource: {uri}")
|
||||
|
||||
if isinstance(uri, str):
|
||||
uri = AnyUrl(uri) # Ensure AnyUrl
|
||||
result = await self.session.read_resource(uri)
|
||||
|
|
@ -651,6 +663,8 @@ class Client(Generic[ClientTransportT]):
|
|||
Raises:
|
||||
RuntimeError: If called while the client is not connected.
|
||||
"""
|
||||
logger.debug(f"[{self.name}] called list_prompts")
|
||||
|
||||
result = await self.session.list_prompts()
|
||||
return result
|
||||
|
||||
|
|
@ -683,6 +697,8 @@ class Client(Generic[ClientTransportT]):
|
|||
Raises:
|
||||
RuntimeError: If called while the client is not connected.
|
||||
"""
|
||||
logger.debug(f"[{self.name}] called get_prompt: {name}")
|
||||
|
||||
# Serialize arguments for MCP protocol - convert non-string values to JSON
|
||||
serialized_arguments: dict[str, str] | None = None
|
||||
if arguments:
|
||||
|
|
@ -740,6 +756,8 @@ class Client(Generic[ClientTransportT]):
|
|||
Raises:
|
||||
RuntimeError: If called while the client is not connected.
|
||||
"""
|
||||
logger.debug(f"[{self.name}] called complete: {ref}")
|
||||
|
||||
result = await self.session.complete(ref=ref, argument=argument)
|
||||
return result
|
||||
|
||||
|
|
@ -775,6 +793,8 @@ class Client(Generic[ClientTransportT]):
|
|||
Raises:
|
||||
RuntimeError: If called while the client is not connected.
|
||||
"""
|
||||
logger.debug(f"[{self.name}] called list_tools")
|
||||
|
||||
result = await self.session.list_tools()
|
||||
return result
|
||||
|
||||
|
|
@ -817,9 +837,10 @@ class Client(Generic[ClientTransportT]):
|
|||
Raises:
|
||||
RuntimeError: If called while the client is not connected.
|
||||
"""
|
||||
logger.debug(f"[{self.name}] called call_tool: {name}")
|
||||
|
||||
if isinstance(timeout, int | float):
|
||||
timeout = datetime.timedelta(seconds=timeout)
|
||||
timeout = datetime.timedelta(seconds=float(timeout))
|
||||
result = await self.session.call_tool(
|
||||
name=name,
|
||||
arguments=arguments,
|
||||
|
|
@ -889,7 +910,7 @@ class Client(Generic[ClientTransportT]):
|
|||
else:
|
||||
data = result.structuredContent
|
||||
except Exception as e:
|
||||
logger.error(f"Error parsing structured content: {e}")
|
||||
logger.error(f"[{self.name}] Error parsing structured content: {e}")
|
||||
|
||||
return CallToolResult(
|
||||
content=result.content,
|
||||
|
|
@ -898,6 +919,14 @@ class Client(Generic[ClientTransportT]):
|
|||
is_error=result.isError,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def generate_name(cls, name: str | None = None) -> str:
|
||||
class_name = cls.__name__
|
||||
if name is None:
|
||||
return f"{class_name}-{secrets.token_hex(2)}"
|
||||
else:
|
||||
return f"{class_name}-{name}-{secrets.token_hex(2)}"
|
||||
|
||||
|
||||
@dataclass
|
||||
class CallToolResult:
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ from fastmcp.client.auth.oauth import OAuth
|
|||
from fastmcp.mcp_config import MCPConfig, infer_transport_type_from_url
|
||||
from fastmcp.server.dependencies import get_http_headers
|
||||
from fastmcp.server.server import FastMCP
|
||||
from fastmcp.utilities.fastmcp_config.v1.fastmcp_config import EnvironmentConfig
|
||||
from fastmcp.utilities.fastmcp_config.v1.fastmcp_config import Environment
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
|
@ -182,7 +182,7 @@ class SSETransport(ClientTransport):
|
|||
self.httpx_client_factory = httpx_client_factory
|
||||
|
||||
if isinstance(sse_read_timeout, int | float):
|
||||
sse_read_timeout = datetime.timedelta(seconds=sse_read_timeout)
|
||||
sse_read_timeout = datetime.timedelta(seconds=float(sse_read_timeout))
|
||||
self.sse_read_timeout = sse_read_timeout
|
||||
|
||||
def _set_auth(self, auth: httpx.Auth | Literal["oauth"] | str | None):
|
||||
|
|
@ -252,7 +252,7 @@ class StreamableHttpTransport(ClientTransport):
|
|||
self.httpx_client_factory = httpx_client_factory
|
||||
|
||||
if isinstance(sse_read_timeout, int | float):
|
||||
sse_read_timeout = datetime.timedelta(seconds=sse_read_timeout)
|
||||
sse_read_timeout = datetime.timedelta(seconds=float(sse_read_timeout))
|
||||
self.sse_read_timeout = sse_read_timeout
|
||||
|
||||
def _set_auth(self, auth: httpx.Auth | Literal["oauth"] | str | None):
|
||||
|
|
@ -596,8 +596,8 @@ class UvStdioTransport(StdioTransport):
|
|||
f"Project directory not found: {project_directory}"
|
||||
)
|
||||
|
||||
# Create EnvironmentConfig from provided parameters (internal use)
|
||||
env_config = EnvironmentConfig(
|
||||
# Create Environment from provided parameters (internal use)
|
||||
env_config = Environment(
|
||||
python=python_version,
|
||||
dependencies=with_packages,
|
||||
requirements=with_requirements,
|
||||
|
|
@ -902,7 +902,8 @@ class MCPConfigTransport(ClientTransport):
|
|||
|
||||
# otherwise create a composite client
|
||||
else:
|
||||
self._composite_server = FastMCP[Any]()
|
||||
name = FastMCP.generate_name("MCPRouter")
|
||||
self._composite_server = FastMCP[Any](name=name)
|
||||
|
||||
for name, server, transport in mcp_config_to_servers_and_transports(
|
||||
self.config
|
||||
|
|
@ -1024,28 +1025,36 @@ def infer_transport(
|
|||
|
||||
# the transport is a FastMCP server (2.x or 1.0)
|
||||
elif isinstance(transport, FastMCP | FastMCP1Server):
|
||||
inferred_transport = FastMCPTransport(mcp=transport)
|
||||
inferred_transport = FastMCPTransport(
|
||||
mcp=cast(FastMCP[Any] | FastMCP1Server, transport)
|
||||
)
|
||||
|
||||
# the transport is a path to a script
|
||||
elif isinstance(transport, Path | str) and Path(transport).exists():
|
||||
if str(transport).endswith(".py"):
|
||||
inferred_transport = PythonStdioTransport(script_path=transport)
|
||||
inferred_transport = PythonStdioTransport(script_path=cast(Path, transport))
|
||||
elif str(transport).endswith(".js"):
|
||||
inferred_transport = NodeStdioTransport(script_path=transport)
|
||||
inferred_transport = NodeStdioTransport(script_path=cast(Path, transport))
|
||||
else:
|
||||
raise ValueError(f"Unsupported script type: {transport}")
|
||||
|
||||
# the transport is an http(s) URL
|
||||
elif isinstance(transport, AnyUrl | str) and str(transport).startswith("http"):
|
||||
inferred_transport_type = infer_transport_type_from_url(transport)
|
||||
inferred_transport_type = infer_transport_type_from_url(
|
||||
cast(AnyUrl | str, transport)
|
||||
)
|
||||
if inferred_transport_type == "sse":
|
||||
inferred_transport = SSETransport(url=transport)
|
||||
inferred_transport = SSETransport(url=cast(AnyUrl | str, transport))
|
||||
else:
|
||||
inferred_transport = StreamableHttpTransport(url=transport)
|
||||
inferred_transport = StreamableHttpTransport(
|
||||
url=cast(AnyUrl | str, transport)
|
||||
)
|
||||
|
||||
# if the transport is a config dict or MCPConfig
|
||||
elif isinstance(transport, dict | MCPConfig):
|
||||
inferred_transport = MCPConfigTransport(config=transport)
|
||||
inferred_transport = MCPConfigTransport(
|
||||
config=cast(dict | MCPConfig, transport)
|
||||
)
|
||||
|
||||
# the transport is an unknown type
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ from __future__ import annotations
|
|||
import datetime
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Annotated, Any, Literal
|
||||
from typing import TYPE_CHECKING, Annotated, Any, Literal, cast
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import httpx
|
||||
|
|
@ -91,14 +91,24 @@ class _TransformingMCPServerMixin(FastMCPBaseModel):
|
|||
|
||||
def _to_server_and_underlying_transport(
|
||||
self,
|
||||
server_name: str | None = None,
|
||||
client_name: str | None = None,
|
||||
) -> tuple[FastMCP[Any], ClientTransport]:
|
||||
"""Turn the Transforming MCPServer into a FastMCP Server and also return the underlying transport."""
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.client import Client
|
||||
from fastmcp.client.transports import (
|
||||
ClientTransport, # pyright: ignore[reportUnusedImport]
|
||||
)
|
||||
|
||||
transport: ClientTransport = super().to_transport() # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue, reportUnknownVariableType]
|
||||
transport = cast(ClientTransport, transport)
|
||||
|
||||
client: Client[ClientTransport] = Client(transport=transport, name=client_name)
|
||||
|
||||
wrapped_mcp_server = FastMCP.as_proxy(
|
||||
transport,
|
||||
name=server_name,
|
||||
backend=client,
|
||||
tool_transformations=self.tools,
|
||||
include_tags=self.include_tags,
|
||||
exclude_tags=self.exclude_tags,
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ from starlette.responses import JSONResponse, RedirectResponse
|
|||
from starlette.routing import Route
|
||||
|
||||
from fastmcp.server.auth.auth import OAuthProvider, TokenVerifier
|
||||
from fastmcp.server.auth.redirect_validation import validate_redirect_uri
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -52,7 +53,7 @@ logger = get_logger(__name__)
|
|||
|
||||
|
||||
class ProxyDCRClient(OAuthClientInformationFull):
|
||||
"""Client for DCR proxy that accepts any localhost redirect URI.
|
||||
"""Client for DCR proxy with configurable redirect URI validation.
|
||||
|
||||
This special client class is critical for the OAuth proxy to work correctly
|
||||
with Dynamic Client Registration (DCR). Here's why it exists:
|
||||
|
|
@ -61,36 +62,48 @@ class ProxyDCRClient(OAuthClientInformationFull):
|
|||
--------
|
||||
When MCP clients use OAuth, they dynamically register with random localhost
|
||||
ports (e.g., http://localhost:55454/callback). The OAuth proxy needs to:
|
||||
1. Accept these dynamic redirect URIs from clients
|
||||
1. Accept these dynamic redirect URIs from clients based on configured patterns
|
||||
2. Use its own fixed redirect URI with the upstream provider (Google, GitHub, etc.)
|
||||
3. Forward the authorization code back to the client's dynamic URI
|
||||
|
||||
Solution:
|
||||
---------
|
||||
This class overrides redirect_uri validation to accept ANY localhost URI,
|
||||
This class validates redirect URIs against configurable patterns,
|
||||
while the proxy internally uses its own fixed redirect URI with the upstream
|
||||
provider. This allows the flow to work even when clients reconnect with
|
||||
different ports or when tokens are cached.
|
||||
|
||||
Without this class, clients would get "Redirect URI not registered" errors
|
||||
when trying to authenticate with cached tokens, because the stored client
|
||||
would have fixed redirect URIs that don't match the new dynamic port.
|
||||
Without proper validation, clients could get "Redirect URI not registered" errors
|
||||
when trying to authenticate with cached tokens, or security vulnerabilities could
|
||||
arise from accepting arbitrary redirect URIs.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, *args, allowed_redirect_uri_patterns: list[str] | None = None, **kwargs
|
||||
):
|
||||
"""Initialize with allowed redirect URI patterns.
|
||||
|
||||
Args:
|
||||
allowed_redirect_uri_patterns: List of allowed redirect URI patterns with wildcard support.
|
||||
If None, defaults to localhost-only patterns.
|
||||
If empty list, allows all redirect URIs.
|
||||
"""
|
||||
super().__init__(*args, **kwargs)
|
||||
self._allowed_redirect_uri_patterns = allowed_redirect_uri_patterns
|
||||
|
||||
def validate_redirect_uri(self, redirect_uri: AnyUrl | None) -> AnyUrl:
|
||||
"""Accept any localhost redirect URI for DCR clients.
|
||||
"""Validate redirect URI against allowed patterns.
|
||||
|
||||
Since we're acting as a proxy and clients register dynamically,
|
||||
we need to accept their localhost redirect URIs even though they're
|
||||
not pre-registered with us. This is essential for cached token
|
||||
scenarios where the client may reconnect with a different port.
|
||||
we validate their redirect URIs against configurable patterns.
|
||||
This is essential for cached token scenarios where the client may
|
||||
reconnect with a different port.
|
||||
"""
|
||||
if redirect_uri is not None:
|
||||
# Accept any localhost redirect URI for DCR clients
|
||||
uri_str = str(redirect_uri)
|
||||
if uri_str.startswith(("http://localhost", "http://127.0.0.1")):
|
||||
# Validate against allowed patterns
|
||||
if validate_redirect_uri(redirect_uri, self._allowed_redirect_uri_patterns):
|
||||
return redirect_uri
|
||||
# Fall back to normal validation for non-localhost URIs
|
||||
# Fall back to normal validation if not in allowed patterns
|
||||
return super().validate_redirect_uri(redirect_uri)
|
||||
# If no redirect_uri provided, use default behavior
|
||||
return super().validate_redirect_uri(redirect_uri)
|
||||
|
|
@ -229,6 +242,8 @@ class OAuthProxy(OAuthProvider):
|
|||
issuer_url: AnyHttpUrl | str | None = None,
|
||||
service_documentation_url: AnyHttpUrl | str | None = None,
|
||||
resource_server_url: AnyHttpUrl | str | None = None,
|
||||
# Client redirect URI validation
|
||||
allowed_client_redirect_uris: list[str] | None = None,
|
||||
):
|
||||
"""Initialize the OAuth proxy provider.
|
||||
|
||||
|
|
@ -244,6 +259,11 @@ class OAuthProxy(OAuthProvider):
|
|||
issuer_url: Issuer URL for OAuth metadata (defaults to base_url)
|
||||
service_documentation_url: Optional service documentation URL
|
||||
resource_server_url: Resource server URL (defaults to base_url)
|
||||
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
|
||||
Patterns support wildcards (e.g., "http://localhost:*", "https://*.example.com/*").
|
||||
If None (default), only localhost redirect URIs are allowed.
|
||||
If empty list, all redirect URIs are allowed (not recommended for production).
|
||||
These are for MCP clients performing loopback redirects, NOT for the upstream OAuth app.
|
||||
"""
|
||||
# Convert string URLs to AnyHttpUrl for parent class
|
||||
base_url_parsed = (
|
||||
|
|
@ -302,6 +322,7 @@ class OAuthProxy(OAuthProvider):
|
|||
self._redirect_path = (
|
||||
redirect_path if redirect_path.startswith("/") else f"/{redirect_path}"
|
||||
)
|
||||
self._allowed_client_redirect_uris = allowed_client_redirect_uris
|
||||
|
||||
# Local state for DCR and token bookkeeping
|
||||
self._clients: dict[str, OAuthClientInformationFull] = {}
|
||||
|
|
@ -353,9 +374,10 @@ class OAuthProxy(OAuthProvider):
|
|||
client_secret=None,
|
||||
redirect_uris=[
|
||||
AnyUrl("http://localhost")
|
||||
], # Placeholder - we accept any localhost URI
|
||||
], # Placeholder, validation uses allowed_patterns
|
||||
grant_types=["authorization_code", "refresh_token"],
|
||||
token_endpoint_auth_method="none",
|
||||
allowed_redirect_uri_patterns=self._allowed_client_redirect_uris,
|
||||
)
|
||||
logger.debug("Created ProxyDCRClient for unregistered client %s", client_id)
|
||||
|
||||
|
|
@ -386,7 +408,7 @@ class OAuthProxy(OAuthProvider):
|
|||
upstream_id = self._upstream_client_id
|
||||
upstream_secret = self._upstream_client_secret.get_secret_value()
|
||||
|
||||
# Create a ProxyDCRClient that accepts any localhost redirect URI
|
||||
# Create a ProxyDCRClient with configured redirect URI validation
|
||||
proxy_client = ProxyDCRClient(
|
||||
client_id=upstream_id,
|
||||
client_secret=upstream_secret,
|
||||
|
|
@ -394,17 +416,9 @@ class OAuthProxy(OAuthProvider):
|
|||
grant_types=client_info.grant_types
|
||||
or ["authorization_code", "refresh_token"],
|
||||
token_endpoint_auth_method="none",
|
||||
allowed_redirect_uri_patterns=self._allowed_client_redirect_uris,
|
||||
)
|
||||
|
||||
# Modify the client_info object in place (framework ignores return values)
|
||||
client_info.client_id = upstream_id
|
||||
client_info.client_secret = upstream_secret
|
||||
client_info.token_endpoint_auth_method = "none"
|
||||
|
||||
# Ensure correct grant types
|
||||
if not client_info.grant_types:
|
||||
client_info.grant_types = ["authorization_code", "refresh_token"]
|
||||
|
||||
# Store the ProxyDCRClient using the upstream ID
|
||||
self._clients[upstream_id] = proxy_client
|
||||
|
||||
|
|
|
|||
|
|
@ -159,17 +159,20 @@ class JWTVerifierSettings(BaseSettings):
|
|||
@register_provider("JWT")
|
||||
class JWTVerifier(TokenVerifier):
|
||||
"""
|
||||
JWT token verifier using public key or JWKS.
|
||||
JWT token verifier supporting both asymmetric (RSA/ECDSA) and symmetric (HMAC) algorithms.
|
||||
|
||||
This verifier validates JWT tokens signed by an external issuer. It's ideal for
|
||||
scenarios where you have a centralized identity provider (like Auth0, Okta, or
|
||||
your own OAuth server) that issues JWTs, and your FastMCP server acts as a
|
||||
resource server validating those tokens.
|
||||
This verifier validates JWT tokens using various signing algorithms:
|
||||
- **Asymmetric algorithms** (RS256/384/512, ES256/384/512, PS256/384/512):
|
||||
Uses public/private key pairs. Ideal for external clients and services where
|
||||
only the authorization server has the private key.
|
||||
- **Symmetric algorithms** (HS256/384/512): Uses a shared secret for both
|
||||
signing and verification. Perfect for internal microservices and trusted
|
||||
environments where the secret can be securely shared.
|
||||
|
||||
Use this when:
|
||||
- You have JWT tokens issued by an external service
|
||||
- You want asymmetric key verification (public/private key pairs)
|
||||
- You need JWKS support for automatic key rotation
|
||||
- You have JWT tokens issued by an external service (asymmetric)
|
||||
- You need JWKS support for automatic key rotation (asymmetric)
|
||||
- You have internal microservices sharing a secret key (symmetric)
|
||||
- Your tokens contain standard OAuth scopes and claims
|
||||
"""
|
||||
|
||||
|
|
@ -188,11 +191,14 @@ class JWTVerifier(TokenVerifier):
|
|||
Initialize the JWT token verifier.
|
||||
|
||||
Args:
|
||||
public_key: PEM-encoded public key for verification
|
||||
jwks_uri: URI to fetch JSON Web Key Set
|
||||
public_key: For asymmetric algorithms (RS256, ES256, etc.): PEM-encoded public key.
|
||||
For symmetric algorithms (HS256, HS384, HS512): The shared secret string.
|
||||
jwks_uri: URI to fetch JSON Web Key Set (only for asymmetric algorithms)
|
||||
issuer: Expected issuer claim
|
||||
audience: Expected audience claim(s)
|
||||
algorithm: JWT signing algorithm (default: RS256)
|
||||
algorithm: JWT signing algorithm. Supported algorithms:
|
||||
- Asymmetric: RS256/384/512, ES256/384/512, PS256/384/512 (default: RS256)
|
||||
- Symmetric: HS256, HS384, HS512
|
||||
required_scopes: Required scopes for all tokens
|
||||
resource_server_url: Resource server URL for TokenVerifier protocol
|
||||
"""
|
||||
|
|
|
|||
70
src/fastmcp/server/auth/redirect_validation.py
Normal file
70
src/fastmcp/server/auth/redirect_validation.py
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
"""Utilities for validating client redirect URIs in OAuth flows."""
|
||||
|
||||
import fnmatch
|
||||
|
||||
from pydantic import AnyUrl
|
||||
|
||||
|
||||
def matches_allowed_pattern(uri: str, pattern: str) -> bool:
|
||||
"""Check if a URI matches an allowed pattern with wildcard support.
|
||||
|
||||
Patterns support * wildcard matching:
|
||||
- http://localhost:* matches any localhost port
|
||||
- http://127.0.0.1:* matches any 127.0.0.1 port
|
||||
- https://*.example.com/* matches any subdomain of example.com
|
||||
- https://app.example.com/auth/* matches any path under /auth/
|
||||
|
||||
Args:
|
||||
uri: The redirect URI to validate
|
||||
pattern: The allowed pattern (may contain wildcards)
|
||||
|
||||
Returns:
|
||||
True if the URI matches the pattern
|
||||
"""
|
||||
# Use fnmatch for wildcard matching
|
||||
return fnmatch.fnmatch(uri, pattern)
|
||||
|
||||
|
||||
def validate_redirect_uri(
|
||||
redirect_uri: str | AnyUrl | None,
|
||||
allowed_patterns: list[str] | None,
|
||||
) -> bool:
|
||||
"""Validate a redirect URI against allowed patterns.
|
||||
|
||||
Args:
|
||||
redirect_uri: The redirect URI to validate
|
||||
allowed_patterns: List of allowed patterns. If None, defaults to localhost.
|
||||
If empty list, all URIs are allowed.
|
||||
|
||||
Returns:
|
||||
True if the redirect URI is allowed
|
||||
"""
|
||||
if redirect_uri is None:
|
||||
return True # None is allowed (will use client's default)
|
||||
|
||||
uri_str = str(redirect_uri)
|
||||
|
||||
# If no patterns specified, default to localhost only
|
||||
if allowed_patterns is None:
|
||||
allowed_patterns = [
|
||||
"http://localhost:*",
|
||||
"http://127.0.0.1:*",
|
||||
]
|
||||
|
||||
# Empty list means allow all
|
||||
if len(allowed_patterns) == 0:
|
||||
return True
|
||||
|
||||
# Check if URI matches any allowed pattern
|
||||
for pattern in allowed_patterns:
|
||||
if matches_allowed_pattern(uri_str, pattern):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
# Default patterns for localhost-only validation
|
||||
DEFAULT_LOCALHOST_PATTERNS = [
|
||||
"http://localhost:*",
|
||||
"http://127.0.0.1:*",
|
||||
]
|
||||
|
|
@ -548,7 +548,7 @@ class Context:
|
|||
if isinstance(validated_data, ScalarElicitationType):
|
||||
return AcceptedElicitation[T](data=validated_data.value)
|
||||
else:
|
||||
return AcceptedElicitation[T](data=validated_data)
|
||||
return AcceptedElicitation[T](data=cast(T, validated_data))
|
||||
elif result.content:
|
||||
raise ValueError(
|
||||
"Elicitation expected an empty response, but received: "
|
||||
|
|
|
|||
|
|
@ -546,6 +546,8 @@ class ProxyClient(Client[ClientTransportT]):
|
|||
| str,
|
||||
**kwargs,
|
||||
):
|
||||
if "name" not in kwargs:
|
||||
kwargs["name"] = self.generate_name()
|
||||
if "roots" not in kwargs:
|
||||
kwargs["roots"] = default_proxy_roots_handler
|
||||
if "sampling_handler" not in kwargs:
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from __future__ import annotations
|
|||
import inspect
|
||||
import json
|
||||
import re
|
||||
import secrets
|
||||
import warnings
|
||||
from collections.abc import AsyncIterator, Awaitable, Callable
|
||||
from contextlib import (
|
||||
|
|
@ -197,8 +198,9 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
lifespan = default_lifespan
|
||||
else:
|
||||
self._has_lifespan = True
|
||||
# Generate random ID if no name provided
|
||||
self._mcp_server = LowLevelServer[LifespanResultT](
|
||||
name=name or "FastMCP",
|
||||
name=name or self.generate_name(),
|
||||
version=version,
|
||||
instructions=instructions,
|
||||
lifespan=_lifespan_wrapper(self, lifespan),
|
||||
|
|
@ -519,7 +521,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
return routes
|
||||
|
||||
async def _mcp_list_tools(self) -> list[MCPTool]:
|
||||
logger.debug("Handler called: list_tools")
|
||||
logger.debug(f"[{self.name}] Handler called: list_tools")
|
||||
|
||||
async with fastmcp.server.context.Context(fastmcp=self):
|
||||
tools = await self._list_tools()
|
||||
|
|
@ -563,7 +565,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
return await self._apply_middleware(mw_context, _handler)
|
||||
|
||||
async def _mcp_list_resources(self) -> list[MCPResource]:
|
||||
logger.debug("Handler called: list_resources")
|
||||
logger.debug(f"[{self.name}] Handler called: list_resources")
|
||||
|
||||
async with fastmcp.server.context.Context(fastmcp=self):
|
||||
resources = await self._list_resources()
|
||||
|
|
@ -608,7 +610,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
return await self._apply_middleware(mw_context, _handler)
|
||||
|
||||
async def _mcp_list_resource_templates(self) -> list[MCPResourceTemplate]:
|
||||
logger.debug("Handler called: list_resource_templates")
|
||||
logger.debug(f"[{self.name}] Handler called: list_resource_templates")
|
||||
|
||||
async with fastmcp.server.context.Context(fastmcp=self):
|
||||
templates = await self._list_resource_templates()
|
||||
|
|
@ -653,7 +655,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
return await self._apply_middleware(mw_context, _handler)
|
||||
|
||||
async def _mcp_list_prompts(self) -> list[MCPPrompt]:
|
||||
logger.debug("Handler called: list_prompts")
|
||||
logger.debug(f"[{self.name}] Handler called: list_prompts")
|
||||
|
||||
async with fastmcp.server.context.Context(fastmcp=self):
|
||||
prompts = await self._list_prompts()
|
||||
|
|
@ -712,7 +714,9 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
Returns:
|
||||
List of MCP Content objects containing the tool results
|
||||
"""
|
||||
logger.debug("Handler called: call_tool %s with %s", key, arguments)
|
||||
logger.debug(
|
||||
f"[{self.name}] Handler called: call_tool %s with %s", key, arguments
|
||||
)
|
||||
|
||||
async with fastmcp.server.context.Context(fastmcp=self):
|
||||
try:
|
||||
|
|
@ -754,7 +758,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
|
||||
Delegates to _read_resource, which should be overridden by FastMCP subclasses.
|
||||
"""
|
||||
logger.debug("Handler called: read_resource %s", uri)
|
||||
logger.debug(f"[{self.name}] Handler called: read_resource %s", uri)
|
||||
|
||||
async with fastmcp.server.context.Context(fastmcp=self):
|
||||
try:
|
||||
|
|
@ -809,7 +813,9 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
|
||||
Delegates to _get_prompt, which should be overridden by FastMCP subclasses.
|
||||
"""
|
||||
logger.debug("Handler called: get_prompt %s with %s", name, arguments)
|
||||
logger.debug(
|
||||
f"[{self.name}] Handler called: get_prompt %s with %s", name, arguments
|
||||
)
|
||||
|
||||
async with fastmcp.server.context.Context(fastmcp=self):
|
||||
try:
|
||||
|
|
@ -1027,7 +1033,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
description=description,
|
||||
tags=tags,
|
||||
output_schema=output_schema,
|
||||
annotations=annotations,
|
||||
annotations=cast(ToolAnnotations | None, annotations),
|
||||
exclude_args=exclude_args,
|
||||
meta=meta,
|
||||
serializer=self._tool_serializer,
|
||||
|
|
@ -1257,7 +1263,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
mime_type=mime_type,
|
||||
tags=tags,
|
||||
enabled=enabled,
|
||||
annotations=annotations,
|
||||
annotations=cast(Annotations | None, annotations),
|
||||
meta=meta,
|
||||
)
|
||||
self.add_template(template)
|
||||
|
|
@ -1272,7 +1278,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
mime_type=mime_type,
|
||||
tags=tags,
|
||||
enabled=enabled,
|
||||
annotations=annotations,
|
||||
annotations=cast(Annotations | None, annotations),
|
||||
meta=meta,
|
||||
)
|
||||
self.add_resource(resource)
|
||||
|
|
@ -1966,9 +1972,11 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
self._prompt_manager.add_prompt(prompt)
|
||||
|
||||
if prefix:
|
||||
logger.debug(f"Imported server {server.name} with prefix '{prefix}'")
|
||||
logger.debug(
|
||||
f"[{self.name}] Imported server {server.name} with prefix '{prefix}'"
|
||||
)
|
||||
else:
|
||||
logger.debug(f"Imported server {server.name}")
|
||||
logger.debug(f"[{self.name}] Imported server {server.name}")
|
||||
|
||||
@classmethod
|
||||
def from_openapi(
|
||||
|
|
@ -2195,6 +2203,15 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def generate_name(cls, name: str | None = None) -> str:
|
||||
class_name = cls.__name__
|
||||
|
||||
if name is None:
|
||||
return f"{class_name}-{secrets.token_hex(2)}"
|
||||
else:
|
||||
return f"{class_name}-{name}-{secrets.token_hex(2)}"
|
||||
|
||||
|
||||
@dataclass
|
||||
class MountedServer:
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ class Settings(BaseSettings):
|
|||
|
||||
test_mode: bool = False
|
||||
|
||||
log_enabled: bool = True
|
||||
log_level: LOG_LEVEL = "INFO"
|
||||
|
||||
@field_validator("log_level", mode="before")
|
||||
|
|
@ -314,7 +315,7 @@ class Settings(BaseSettings):
|
|||
Whether to include FastMCP meta in the server's MCP responses.
|
||||
If True, a `_fastmcp` key will be added to the `meta` field of
|
||||
all MCP component responses. This key will contain a dict of
|
||||
various FastMCP-specific metadata, such as tags.
|
||||
various FastMCP-specific metadata, such as tags.
|
||||
"""
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -5,17 +5,19 @@ The current version is v1, which is re-exported here for convenience.
|
|||
"""
|
||||
|
||||
from fastmcp.utilities.fastmcp_config.v1.fastmcp_config import (
|
||||
DeploymentConfig,
|
||||
EntrypointConfig,
|
||||
EnvironmentConfig,
|
||||
Deployment,
|
||||
Environment,
|
||||
FastMCPConfig,
|
||||
generate_schema,
|
||||
)
|
||||
from fastmcp.utilities.fastmcp_config.v1.sources.base import BaseSource
|
||||
from fastmcp.utilities.fastmcp_config.v1.sources.filesystem import FileSystemSource
|
||||
|
||||
__all__ = [
|
||||
"BaseSource",
|
||||
"Deployment",
|
||||
"Environment",
|
||||
"FastMCPConfig",
|
||||
"EntrypointConfig",
|
||||
"EnvironmentConfig",
|
||||
"DeploymentConfig",
|
||||
"FileSystemSource",
|
||||
"generate_schema",
|
||||
]
|
||||
|
|
|
|||
0
src/fastmcp/utilities/fastmcp_config/v1/__init__.py
Normal file
0
src/fastmcp/utilities/fastmcp_config/v1/__init__.py
Normal file
|
|
@ -15,36 +15,20 @@ from typing import TYPE_CHECKING, Any, Literal, overload
|
|||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
from fastmcp.utilities.fastmcp_config.v1.sources.filesystem import FileSystemSource
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
|
||||
logger = get_logger("cli.config")
|
||||
|
||||
# JSON Schema for IDE support
|
||||
FASTMCP_JSON_SCHEMA = "https://gofastmcp.com/schemas/fastmcp_config/v1.json"
|
||||
FASTMCP_JSON_SCHEMA = "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json"
|
||||
|
||||
|
||||
class EntrypointConfig(BaseModel):
|
||||
"""Configuration for server entrypoint when using object format."""
|
||||
|
||||
file: str = Field(
|
||||
description="Path to Python file containing the server",
|
||||
examples=["server.py", "src/server.py", "app/main.py"],
|
||||
)
|
||||
|
||||
object: str | None = Field(
|
||||
default=None,
|
||||
description="Name of the server object in the file (defaults to searching for mcp/server/app)",
|
||||
examples=["app", "mcp", "server"],
|
||||
)
|
||||
|
||||
repo: str | None = Field(
|
||||
default=None,
|
||||
description="Git repository URL",
|
||||
examples=["https://github.com/user/repo"],
|
||||
)
|
||||
# Type alias for source union (will expand with GitSource, etc in future)
|
||||
SourceType = FileSystemSource
|
||||
|
||||
|
||||
class EnvironmentConfig(BaseModel):
|
||||
class Environment(BaseModel):
|
||||
"""Configuration for Python environment setup."""
|
||||
|
||||
python: str | None = Field(
|
||||
|
|
@ -96,13 +80,18 @@ class EnvironmentConfig(BaseModel):
|
|||
if self.project:
|
||||
args.extend(["--project", str(self.project)])
|
||||
|
||||
# Add fastmcp as a base dependency
|
||||
args.extend(["--with", "fastmcp"])
|
||||
# Add fastmcp dependency - use editable install if in development mode
|
||||
dev_path = self._find_fastmcp_dev_path()
|
||||
if dev_path:
|
||||
args.extend(["--with-editable", str(dev_path)])
|
||||
else:
|
||||
args.extend(["--with", "fastmcp"])
|
||||
|
||||
# Add additional dependencies
|
||||
# Add additional dependencies (skip fastmcp if already added)
|
||||
if self.dependencies:
|
||||
for dep in self.dependencies:
|
||||
args.extend(["--with", dep])
|
||||
if dep != "fastmcp": # Skip fastmcp since we already added it
|
||||
args.extend(["--with", dep])
|
||||
|
||||
# Add requirements file
|
||||
if self.requirements:
|
||||
|
|
@ -121,6 +110,34 @@ class EnvironmentConfig(BaseModel):
|
|||
|
||||
return args
|
||||
|
||||
def _find_fastmcp_dev_path(self) -> Path | None:
|
||||
"""Find the fastmcp development directory by looking for pyproject.toml.
|
||||
|
||||
Searches from the current working directory up the directory tree
|
||||
looking for a pyproject.toml file that contains name = "fastmcp".
|
||||
|
||||
Returns:
|
||||
Path to the fastmcp project directory if found, None otherwise
|
||||
"""
|
||||
current_path = Path.cwd()
|
||||
|
||||
# Search up the directory tree
|
||||
for path in [current_path] + list(current_path.parents):
|
||||
pyproject_path = path / "pyproject.toml"
|
||||
if pyproject_path.exists():
|
||||
try:
|
||||
# Read and check if this is the fastmcp project
|
||||
content = pyproject_path.read_text(encoding="utf-8")
|
||||
if 'name = "fastmcp"' in content or "name='fastmcp'" in content:
|
||||
logger.debug(f"Found fastmcp development project at: {path}")
|
||||
return path
|
||||
except (OSError, UnicodeDecodeError):
|
||||
# Skip files that can't be read
|
||||
continue
|
||||
|
||||
logger.debug("No fastmcp development project found, using PyPI package")
|
||||
return None
|
||||
|
||||
def run_with_uv(self, command: list[str]) -> None:
|
||||
"""Execute a command using uv run with this environment configuration.
|
||||
|
||||
|
|
@ -150,51 +167,18 @@ class EnvironmentConfig(BaseModel):
|
|||
Returns:
|
||||
True if any environment settings require uv run
|
||||
"""
|
||||
return bool(
|
||||
self.python
|
||||
or self.dependencies
|
||||
or self.requirements
|
||||
or self.project
|
||||
or self.editable
|
||||
return any(
|
||||
[
|
||||
self.python is not None,
|
||||
self.dependencies is not None,
|
||||
self.requirements is not None,
|
||||
self.project is not None,
|
||||
self.editable is not None,
|
||||
]
|
||||
)
|
||||
|
||||
def merge_with_cli_args(
|
||||
self,
|
||||
python: str | None = None,
|
||||
with_packages: list[str] | None = None,
|
||||
with_requirements: Path | None = None,
|
||||
project: Path | None = None,
|
||||
with_editable: Path | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Merge environment config with CLI arguments, with CLI taking precedence.
|
||||
|
||||
For packages, combines both config and CLI packages.
|
||||
For other fields, CLI takes precedence if provided.
|
||||
|
||||
Returns:
|
||||
Dictionary with merged arguments suitable for CLI commands
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
# Merge packages from both sources
|
||||
packages = []
|
||||
if self.dependencies:
|
||||
packages.extend(self.dependencies)
|
||||
if with_packages:
|
||||
packages.extend(with_packages)
|
||||
|
||||
return {
|
||||
"python": python or self.python,
|
||||
"with_packages": packages,
|
||||
"with_requirements": with_requirements
|
||||
or (Path(self.requirements) if self.requirements else None),
|
||||
"project": project or (Path(self.project) if self.project else None),
|
||||
"with_editable": with_editable
|
||||
or (Path(self.editable) if self.editable else None),
|
||||
}
|
||||
|
||||
|
||||
class DeploymentConfig(BaseModel):
|
||||
class Deployment(BaseModel):
|
||||
"""Configuration for server deployment and runtime settings."""
|
||||
|
||||
transport: Literal["stdio", "http", "sse"] | None = Field(
|
||||
|
|
@ -243,29 +227,6 @@ class DeploymentConfig(BaseModel):
|
|||
examples=[["--config", "config.json", "--debug"]],
|
||||
)
|
||||
|
||||
def merge_with_cli_args(
|
||||
self,
|
||||
transport: str | None = None,
|
||||
host: str | None = None,
|
||||
port: int | None = None,
|
||||
path: str | None = None,
|
||||
log_level: str | None = None,
|
||||
server_args: list[str] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Merge deployment config with CLI arguments, with CLI taking precedence.
|
||||
|
||||
Returns:
|
||||
Dictionary with merged arguments suitable for CLI commands
|
||||
"""
|
||||
return {
|
||||
"transport": transport or self.transport,
|
||||
"host": host or self.host,
|
||||
"port": port or self.port,
|
||||
"path": path or self.path,
|
||||
"log_level": log_level or self.log_level,
|
||||
"server_args": server_args if server_args is not None else self.args,
|
||||
}
|
||||
|
||||
def apply_runtime_settings(self, config_path: Path | None = None) -> None:
|
||||
"""Apply runtime settings like environment variables and working directory.
|
||||
|
||||
|
|
@ -324,149 +285,99 @@ class FastMCPConfig(BaseModel):
|
|||
|
||||
# Schema field for IDE support
|
||||
schema_: str | None = Field(
|
||||
default="https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
default="https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
alias="$schema",
|
||||
description="JSON schema for IDE support and validation",
|
||||
)
|
||||
|
||||
# Server entrypoint - supports both string and object format
|
||||
entrypoint: EntrypointConfig = Field(
|
||||
description="Server entrypoint as a string (file or file:object) or object with file/object/repo",
|
||||
# Server source - defines where and how to load the server
|
||||
source: SourceType = Field(
|
||||
description="Source configuration for the server",
|
||||
examples=[
|
||||
"server.py",
|
||||
"server.py:app",
|
||||
{"file": "src/server.py", "object": "app"},
|
||||
{"path": "server.py"},
|
||||
{"path": "server.py", "entrypoint": "app"},
|
||||
{"type": "filesystem", "path": "src/server.py", "entrypoint": "mcp"},
|
||||
],
|
||||
)
|
||||
|
||||
# Environment configuration
|
||||
environment: EnvironmentConfig = Field(
|
||||
default_factory=lambda: EnvironmentConfig(),
|
||||
environment: Environment = Field(
|
||||
default_factory=lambda: Environment(),
|
||||
description="Python environment setup configuration",
|
||||
)
|
||||
|
||||
# Deployment configuration
|
||||
deployment: DeploymentConfig = Field(
|
||||
default_factory=lambda: DeploymentConfig(),
|
||||
deployment: Deployment = Field(
|
||||
default_factory=lambda: Deployment(),
|
||||
description="Server deployment and runtime settings",
|
||||
)
|
||||
|
||||
# purely for static type checkers to avoid issues with providng str entrypoint
|
||||
# purely for static type checkers to avoid issues with providing dict source
|
||||
if TYPE_CHECKING:
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self, *, entrypoint: str | dict | EntrypointConfig, **data
|
||||
) -> None: ...
|
||||
def __init__(self, *, source: dict | FileSystemSource, **data) -> None: ...
|
||||
@overload
|
||||
def __init__(
|
||||
self, *, environment: dict | EnvironmentConfig, **data
|
||||
) -> None: ...
|
||||
def __init__(self, *, environment: dict | Environment, **data) -> None: ...
|
||||
@overload
|
||||
def __init__(self, *, deployment: dict | DeploymentConfig, **data) -> None: ...
|
||||
def __init__(self, *, deployment: dict | Deployment, **data) -> None: ...
|
||||
def __init__(self, **data) -> None: ...
|
||||
|
||||
@field_validator("entrypoint", mode="before")
|
||||
@field_validator("source", mode="before")
|
||||
@classmethod
|
||||
def validate_entrypoint(cls, v: str | EntrypointConfig) -> EntrypointConfig:
|
||||
"""Validate and convert entrypoint to proper format.
|
||||
def validate_source(cls, v: dict | FileSystemSource) -> FileSystemSource:
|
||||
"""Validate and convert source to proper format.
|
||||
|
||||
Supports:
|
||||
- String format: "server.py" or "server.py:object"
|
||||
- Object format: {"file": "server.py", "object": "app"}
|
||||
- EntrypointConfig instance (passed through)
|
||||
- Dict format: {"path": "server.py", "entrypoint": "app"}
|
||||
- FileSystemSource instance (passed through)
|
||||
|
||||
The string format with :object syntax is automatically parsed into
|
||||
the object format for consistency.
|
||||
No string parsing happens here - that's only at CLI boundaries.
|
||||
FastMCPConfig works only with properly typed objects.
|
||||
"""
|
||||
if isinstance(v, EntrypointConfig):
|
||||
# Already an EntrypointConfig instance, return as-is
|
||||
if isinstance(v, FileSystemSource):
|
||||
# Already a FileSystemSource instance, return as-is
|
||||
return v
|
||||
elif isinstance(v, dict):
|
||||
return EntrypointConfig(**v)
|
||||
elif isinstance(v, str):
|
||||
# Parse file.py:object syntax into object format if present
|
||||
if ":" in v:
|
||||
# Check if it's a Windows path (e.g., C:\...)
|
||||
has_windows_drive = len(v) > 1 and v[1] == ":"
|
||||
|
||||
# Only split if colon is not part of Windows drive
|
||||
if ":" in (v[2:] if has_windows_drive else v):
|
||||
file, obj = v.rsplit(":", 1)
|
||||
return EntrypointConfig(file=file, object=obj)
|
||||
else:
|
||||
return EntrypointConfig(file=v)
|
||||
|
||||
raise ValueError("entrypoint must be a string, EntrypointConfig instance")
|
||||
# Dict can have type field or not (filesystem is default)
|
||||
if "type" not in v:
|
||||
v["type"] = "filesystem"
|
||||
return FileSystemSource(**v)
|
||||
else:
|
||||
raise ValueError("source must be a dict or FileSystemSource instance")
|
||||
|
||||
@field_validator("environment", mode="before")
|
||||
@classmethod
|
||||
def validate_environment(cls, v: dict | EnvironmentConfig) -> EnvironmentConfig:
|
||||
"""Validate and convert environment to EnvironmentConfig.
|
||||
def validate_environment(cls, v: dict | Environment) -> Environment:
|
||||
"""Validate and convert environment to Environment.
|
||||
|
||||
Accepts:
|
||||
- EnvironmentConfig instance
|
||||
- dict that can be converted to EnvironmentConfig
|
||||
- Environment instance
|
||||
- dict that can be converted to Environment
|
||||
"""
|
||||
if isinstance(v, EnvironmentConfig):
|
||||
if isinstance(v, Environment):
|
||||
return v
|
||||
elif isinstance(v, dict):
|
||||
return EnvironmentConfig(**v) # type: ignore[arg-type]
|
||||
return Environment(**v) # type: ignore[arg-type]
|
||||
else:
|
||||
raise ValueError("environment must be a dict, EnvironmentConfig instance")
|
||||
raise ValueError("environment must be a dict, Environment instance")
|
||||
|
||||
@field_validator("deployment", mode="before")
|
||||
@classmethod
|
||||
def validate_deployment(cls, v: dict | DeploymentConfig) -> DeploymentConfig:
|
||||
"""Validate and convert deployment to DeploymentConfig.
|
||||
def validate_deployment(cls, v: dict | Deployment) -> Deployment:
|
||||
"""Validate and convert deployment to Deployment.
|
||||
|
||||
Accepts:
|
||||
- DeploymentConfig instance
|
||||
- dict that can be converted to DeploymentConfig
|
||||
- Deployment instance
|
||||
- dict that can be converted to Deployment
|
||||
|
||||
"""
|
||||
if isinstance(v, DeploymentConfig):
|
||||
if isinstance(v, Deployment):
|
||||
return v
|
||||
elif isinstance(v, dict):
|
||||
return DeploymentConfig(**v) # type: ignore[arg-type]
|
||||
return Deployment(**v) # type: ignore[arg-type]
|
||||
else:
|
||||
raise ValueError("deployment must be a dict, DeploymentConfig instance")
|
||||
|
||||
def get_entrypoint(self, config_path: Path | None = None) -> EntrypointConfig:
|
||||
"""Get the entrypoint as a structured object with resolved paths.
|
||||
|
||||
Args:
|
||||
config_path: Path to config file for resolving relative paths
|
||||
|
||||
Returns:
|
||||
EntrypointConfig object with file, object, and repo fields.
|
||||
If config_path is provided, relative file paths are resolved
|
||||
relative to the config file location.
|
||||
"""
|
||||
if isinstance(self.entrypoint, str):
|
||||
# Parse string format into structured object
|
||||
if ":" in self.entrypoint:
|
||||
file, obj = self.entrypoint.rsplit(":", 1)
|
||||
entrypoint = EntrypointConfig(file=file, object=obj)
|
||||
else:
|
||||
entrypoint = EntrypointConfig(file=self.entrypoint)
|
||||
else:
|
||||
# Already an EntrypointConfig
|
||||
entrypoint = self.entrypoint
|
||||
|
||||
# Resolve relative paths if config_path provided
|
||||
if config_path:
|
||||
file_path = Path(entrypoint.file)
|
||||
if not file_path.is_absolute():
|
||||
resolved_path = (config_path.parent / file_path).resolve()
|
||||
# Create new EntrypointConfig with resolved path
|
||||
entrypoint = EntrypointConfig(
|
||||
file=str(resolved_path),
|
||||
object=entrypoint.object,
|
||||
repo=entrypoint.repo,
|
||||
)
|
||||
|
||||
return entrypoint
|
||||
raise ValueError("deployment must be a dict, Deployment instance")
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, file_path: Path) -> FastMCPConfig:
|
||||
|
|
@ -494,7 +405,7 @@ class FastMCPConfig(BaseModel):
|
|||
@classmethod
|
||||
def from_cli_args(
|
||||
cls,
|
||||
entrypoint: str,
|
||||
source: FileSystemSource,
|
||||
transport: Literal["stdio", "http", "sse", "streamable-http"] | None = None,
|
||||
host: str | None = None,
|
||||
port: int | None = None,
|
||||
|
|
@ -516,7 +427,7 @@ class FastMCPConfig(BaseModel):
|
|||
goes through a config object.
|
||||
|
||||
Args:
|
||||
entrypoint: Server entrypoint (file or file:object)
|
||||
source: Server source (FileSystemSource instance)
|
||||
transport: Transport protocol
|
||||
host: Host for HTTP transport
|
||||
port: Port for HTTP transport
|
||||
|
|
@ -537,7 +448,7 @@ class FastMCPConfig(BaseModel):
|
|||
# Build environment config if any env args provided
|
||||
environment = None
|
||||
if any([python, dependencies, requirements, project, editable]):
|
||||
environment = EnvironmentConfig(
|
||||
environment = Environment(
|
||||
python=python,
|
||||
dependencies=dependencies,
|
||||
requirements=requirements,
|
||||
|
|
@ -551,7 +462,7 @@ class FastMCPConfig(BaseModel):
|
|||
# Convert streamable-http to http for backward compatibility
|
||||
if transport == "streamable-http":
|
||||
transport = "http" # type: ignore[assignment]
|
||||
deployment = DeploymentConfig(
|
||||
deployment = Deployment(
|
||||
transport=transport, # type: ignore[arg-type]
|
||||
host=host,
|
||||
port=port,
|
||||
|
|
@ -563,7 +474,7 @@ class FastMCPConfig(BaseModel):
|
|||
)
|
||||
|
||||
return cls(
|
||||
entrypoint=entrypoint,
|
||||
source=source,
|
||||
environment=environment,
|
||||
deployment=deployment,
|
||||
)
|
||||
|
|
@ -588,48 +499,6 @@ class FastMCPConfig(BaseModel):
|
|||
|
||||
return None
|
||||
|
||||
async def load_server(self, config_path: Path | None = None) -> Any:
|
||||
"""Load the server from the configuration.
|
||||
|
||||
This handles environment setup, working directory changes,
|
||||
and imports the server module.
|
||||
|
||||
Args:
|
||||
config_path: Path to the config file (for resolving relative paths)
|
||||
|
||||
Returns:
|
||||
The imported server object
|
||||
"""
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# Set environment variables if specified
|
||||
if self.deployment and self.deployment.env:
|
||||
for key, value in self.deployment.env.items():
|
||||
os.environ[key] = value
|
||||
|
||||
# Change working directory if specified
|
||||
if self.deployment and self.deployment.cwd:
|
||||
cwd_path = Path(self.deployment.cwd)
|
||||
if not cwd_path.is_absolute():
|
||||
# If config_path provided, resolve relative to it
|
||||
if config_path:
|
||||
cwd_path = (config_path.parent / cwd_path).resolve()
|
||||
else:
|
||||
cwd_path = cwd_path.resolve()
|
||||
os.chdir(cwd_path)
|
||||
|
||||
# Get structured entrypoint with resolved paths
|
||||
entrypoint = self.get_entrypoint(config_path)
|
||||
|
||||
# Import the server
|
||||
from fastmcp.cli.run import import_server_with_args
|
||||
|
||||
file_path = Path(entrypoint.file)
|
||||
server_args = self.deployment.args if self.deployment else None
|
||||
|
||||
return await import_server_with_args(file_path, entrypoint.object, server_args)
|
||||
|
||||
async def run_server(self, **kwargs: Any) -> None:
|
||||
"""Load and run the server with this configuration.
|
||||
|
||||
|
|
@ -637,7 +506,12 @@ class FastMCPConfig(BaseModel):
|
|||
**kwargs: Additional arguments to pass to server.run_async()
|
||||
These override config settings
|
||||
"""
|
||||
server = await self.load_server()
|
||||
# Apply deployment settings (env vars, cwd)
|
||||
if self.deployment:
|
||||
self.deployment.apply_runtime_settings()
|
||||
|
||||
# Load the server
|
||||
server = await self.source.load_server()
|
||||
|
||||
# Build run arguments from config
|
||||
run_args = {}
|
||||
|
|
@ -659,14 +533,19 @@ class FastMCPConfig(BaseModel):
|
|||
await server.run_async(**run_args)
|
||||
|
||||
|
||||
def generate_schema() -> dict[str, Any]:
|
||||
def generate_schema(output_path: Path | str | None = None) -> dict[str, Any] | None:
|
||||
"""Generate JSON schema for fastmcp.json files.
|
||||
|
||||
This is used to create the schema file that IDEs can use for
|
||||
validation and auto-completion.
|
||||
|
||||
Args:
|
||||
output_path: Optional path to write the schema to. If provided,
|
||||
writes the schema and returns None. If not provided,
|
||||
returns the schema as a dictionary.
|
||||
|
||||
Returns:
|
||||
JSON schema as a dictionary
|
||||
JSON schema as a dictionary if output_path is None, otherwise None
|
||||
"""
|
||||
schema = FastMCPConfig.model_json_schema()
|
||||
|
||||
|
|
@ -675,4 +554,14 @@ def generate_schema() -> dict[str, Any]:
|
|||
schema["title"] = "FastMCP Configuration"
|
||||
schema["description"] = "Configuration file for FastMCP servers"
|
||||
|
||||
if output_path:
|
||||
import json
|
||||
|
||||
output = Path(output_path)
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(output, "w") as f:
|
||||
json.dump(schema, f, indent=2)
|
||||
f.write("\n") # Add trailing newline
|
||||
return None
|
||||
|
||||
return schema
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$defs": {
|
||||
"DeploymentConfig": {
|
||||
"Deployment": {
|
||||
"description": "Configuration for server deployment and runtime settings.",
|
||||
"properties": {
|
||||
"transport": {
|
||||
|
|
@ -159,64 +159,10 @@
|
|||
"title": "Args"
|
||||
}
|
||||
},
|
||||
"title": "DeploymentConfig",
|
||||
"title": "Deployment",
|
||||
"type": "object"
|
||||
},
|
||||
"EntrypointConfig": {
|
||||
"description": "Configuration for server entrypoint when using object format.",
|
||||
"properties": {
|
||||
"file": {
|
||||
"description": "Path to Python file containing the server",
|
||||
"examples": [
|
||||
"server.py",
|
||||
"src/server.py",
|
||||
"app/main.py"
|
||||
],
|
||||
"title": "File",
|
||||
"type": "string"
|
||||
},
|
||||
"object": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Name of the server object in the file (defaults to searching for mcp/server/app)",
|
||||
"examples": [
|
||||
"app",
|
||||
"mcp",
|
||||
"server"
|
||||
],
|
||||
"title": "Object"
|
||||
},
|
||||
"repo": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Git repository URL",
|
||||
"examples": [
|
||||
"https://github.com/user/repo"
|
||||
],
|
||||
"title": "Repo"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"file"
|
||||
],
|
||||
"title": "EntrypointConfig",
|
||||
"type": "object"
|
||||
},
|
||||
"EnvironmentConfig": {
|
||||
"Environment": {
|
||||
"description": "Configuration for Python environment setup.",
|
||||
"properties": {
|
||||
"python": {
|
||||
|
|
@ -312,7 +258,42 @@
|
|||
"title": "Editable"
|
||||
}
|
||||
},
|
||||
"title": "EnvironmentConfig",
|
||||
"title": "Environment",
|
||||
"type": "object"
|
||||
},
|
||||
"FileSystemSource": {
|
||||
"description": "Source for local Python files.",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "filesystem",
|
||||
"default": "filesystem",
|
||||
"description": "Source type",
|
||||
"title": "Type",
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"description": "Path to Python file containing the server",
|
||||
"title": "Path",
|
||||
"type": "string"
|
||||
},
|
||||
"entrypoint": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Name of server instance or factory function (a no-arg function that returns a FastMCP server)",
|
||||
"title": "Entrypoint"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"title": "FileSystemSource",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
|
|
@ -327,35 +308,41 @@
|
|||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": "https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
"default": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"description": "JSON schema for IDE support and validation",
|
||||
"title": "$Schema"
|
||||
},
|
||||
"entrypoint": {
|
||||
"$ref": "#/$defs/EntrypointConfig",
|
||||
"description": "Server entrypoint as a string (file or file:object) or object with file/object/repo",
|
||||
"source": {
|
||||
"$ref": "#/$defs/FileSystemSource",
|
||||
"description": "Source configuration for the server",
|
||||
"examples": [
|
||||
"server.py",
|
||||
"server.py:app",
|
||||
{
|
||||
"file": "src/server.py",
|
||||
"object": "app"
|
||||
"path": "server.py"
|
||||
},
|
||||
{
|
||||
"entrypoint": "app",
|
||||
"path": "server.py"
|
||||
},
|
||||
{
|
||||
"entrypoint": "mcp",
|
||||
"path": "src/server.py",
|
||||
"type": "filesystem"
|
||||
}
|
||||
]
|
||||
},
|
||||
"environment": {
|
||||
"$ref": "#/$defs/EnvironmentConfig",
|
||||
"$ref": "#/$defs/Environment",
|
||||
"description": "Python environment setup configuration"
|
||||
},
|
||||
"deployment": {
|
||||
"$ref": "#/$defs/DeploymentConfig",
|
||||
"$ref": "#/$defs/Deployment",
|
||||
"description": "Server deployment and runtime settings"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"entrypoint"
|
||||
"source"
|
||||
],
|
||||
"title": "FastMCP Configuration",
|
||||
"type": "object",
|
||||
"$id": "https://gofastmcp.com/schemas/fastmcp_config/v1.json"
|
||||
}
|
||||
"$id": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json"
|
||||
}
|
||||
|
|
|
|||
30
src/fastmcp/utilities/fastmcp_config/v1/sources/base.py
Normal file
30
src/fastmcp/utilities/fastmcp_config/v1/sources/base.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class BaseSource(BaseModel, ABC):
|
||||
"""Abstract base class for all source types."""
|
||||
|
||||
type: str = Field(description="Source type identifier")
|
||||
|
||||
async def prepare(self) -> None:
|
||||
"""Prepare the source (download, clone, install, etc).
|
||||
|
||||
For sources that need preparation (e.g., git clone, download),
|
||||
this method performs that preparation. For sources that don't
|
||||
need preparation (e.g., local files), this is a no-op.
|
||||
"""
|
||||
# Default implementation for sources that don't need preparation
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def load_server(self) -> Any:
|
||||
"""Load and return the FastMCP server instance.
|
||||
|
||||
Must be called after prepare() if the source requires preparation.
|
||||
All information needed to load the server should be available
|
||||
as attributes on the source instance.
|
||||
"""
|
||||
...
|
||||
215
src/fastmcp/utilities/fastmcp_config/v1/sources/filesystem.py
Normal file
215
src/fastmcp/utilities/fastmcp_config/v1/sources/filesystem.py
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
import importlib.util
|
||||
import inspect
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any, Literal
|
||||
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
from fastmcp.utilities.fastmcp_config.v1.sources.base import BaseSource
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class FileSystemSource(BaseSource):
|
||||
"""Source for local Python files."""
|
||||
|
||||
type: Literal["filesystem"] = Field(default="filesystem", description="Source type")
|
||||
path: str = Field(description="Path to Python file containing the server")
|
||||
entrypoint: str | None = Field(
|
||||
default=None,
|
||||
description="Name of server instance or factory function (a no-arg function that returns a FastMCP server)",
|
||||
)
|
||||
|
||||
@field_validator("path", mode="before")
|
||||
@classmethod
|
||||
def parse_path_with_object(cls, v: str) -> str:
|
||||
"""Parse path:object syntax and extract the object name.
|
||||
|
||||
This validator runs before the model is created, allowing us to
|
||||
handle the "file.py:object" syntax at the model boundary.
|
||||
"""
|
||||
if isinstance(v, str) and ":" in v:
|
||||
# Check if it's a Windows path (e.g., C:\...)
|
||||
has_windows_drive = len(v) > 1 and v[1] == ":"
|
||||
|
||||
# Only split if colon is not part of Windows drive
|
||||
if ":" in (v[2:] if has_windows_drive else v):
|
||||
# This path has an object specification
|
||||
# We'll handle it in __init__ by setting entrypoint
|
||||
return v
|
||||
return v
|
||||
|
||||
def __init__(self, **data: Any) -> None:
|
||||
"""Initialize FileSystemSource, handling path:object syntax."""
|
||||
# Check if path contains an object specification
|
||||
if "path" in data and isinstance(data["path"], str) and ":" in data["path"]:
|
||||
path_str = data["path"]
|
||||
# Check if it's a Windows path (e.g., C:\...)
|
||||
has_windows_drive = len(path_str) > 1 and path_str[1] == ":"
|
||||
|
||||
# Only split if colon is not part of Windows drive
|
||||
if ":" in (path_str[2:] if has_windows_drive else path_str):
|
||||
file_str, obj = path_str.rsplit(":", 1)
|
||||
data["path"] = file_str
|
||||
# Only set entrypoint if not already provided
|
||||
if "entrypoint" not in data or data["entrypoint"] is None:
|
||||
data["entrypoint"] = obj
|
||||
|
||||
super().__init__(**data)
|
||||
|
||||
async def load_server(self) -> Any:
|
||||
"""Load server from filesystem."""
|
||||
# Resolve the file path
|
||||
file_path = Path(self.path).expanduser().resolve()
|
||||
if not file_path.exists():
|
||||
logger.error(f"File not found: {file_path}")
|
||||
sys.exit(1)
|
||||
if not file_path.is_file():
|
||||
logger.error(f"Not a file: {file_path}")
|
||||
sys.exit(1)
|
||||
|
||||
# Import the module
|
||||
module = self._import_module(file_path)
|
||||
|
||||
# Find the server object
|
||||
server = await self._find_server_object(module, file_path)
|
||||
|
||||
return server
|
||||
|
||||
def _import_module(self, file_path: Path) -> Any:
|
||||
"""Import a Python module from a file path.
|
||||
|
||||
Args:
|
||||
file_path: Path to the Python file
|
||||
|
||||
Returns:
|
||||
The imported module
|
||||
"""
|
||||
# Add parent directory to Python path so imports can be resolved
|
||||
file_dir = str(file_path.parent)
|
||||
if file_dir not in sys.path:
|
||||
sys.path.insert(0, file_dir)
|
||||
|
||||
# Import the module
|
||||
spec = importlib.util.spec_from_file_location("server_module", file_path)
|
||||
if not spec or not spec.loader:
|
||||
logger.error("Could not load module", extra={"file": str(file_path)})
|
||||
sys.exit(1)
|
||||
|
||||
module = importlib.util.module_from_spec(spec) # type: ignore[arg-type]
|
||||
sys.modules["server_module"] = module # Register in sys.modules
|
||||
spec.loader.exec_module(module) # type: ignore[union-attr]
|
||||
|
||||
return module
|
||||
|
||||
async def _find_server_object(self, module: Any, file_path: Path) -> Any:
|
||||
"""Find the server object in the module.
|
||||
|
||||
Args:
|
||||
module: The imported Python module
|
||||
file_path: Path to the file (for error messages)
|
||||
|
||||
Returns:
|
||||
The server object (or result of calling a factory function)
|
||||
"""
|
||||
# Avoid circular import by importing here
|
||||
from mcp.server.fastmcp import FastMCP as FastMCP1x
|
||||
|
||||
from fastmcp.server.server import FastMCP
|
||||
|
||||
# If entrypoint is specified, use it
|
||||
if self.entrypoint:
|
||||
# Handle module:object syntax (though this is legacy)
|
||||
if ":" in self.entrypoint:
|
||||
module_name, object_name = self.entrypoint.split(":", 1)
|
||||
try:
|
||||
import importlib
|
||||
|
||||
server_module = importlib.import_module(module_name)
|
||||
obj = getattr(server_module, object_name, None)
|
||||
except ImportError:
|
||||
logger.error(
|
||||
f"Could not import module '{module_name}'",
|
||||
extra={"file": str(file_path)},
|
||||
)
|
||||
sys.exit(1)
|
||||
else:
|
||||
# Just object name
|
||||
obj = getattr(module, self.entrypoint, None)
|
||||
|
||||
if obj is None:
|
||||
logger.error(
|
||||
f"Server object '{self.entrypoint}' not found",
|
||||
extra={"file": str(file_path)},
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
return await self._resolve_factory(obj, file_path, self.entrypoint)
|
||||
|
||||
# No entrypoint specified, try common server names
|
||||
for name in ["mcp", "server", "app"]:
|
||||
if hasattr(module, name):
|
||||
obj = getattr(module, name)
|
||||
if isinstance(obj, FastMCP | FastMCP1x):
|
||||
return await self._resolve_factory(obj, file_path, name)
|
||||
|
||||
# No server found
|
||||
logger.error(
|
||||
f"No server object found in {file_path}. Please either:\n"
|
||||
"1. Use a standard variable name (mcp, server, or app)\n"
|
||||
"2. Specify the entrypoint name in fastmcp.json or use `file.py:object` syntax as your path.",
|
||||
extra={"file": str(file_path)},
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
async def _resolve_factory(self, obj: Any, file_path: Path, name: str) -> Any:
|
||||
"""Resolve a server object or factory function to a server instance.
|
||||
|
||||
Args:
|
||||
obj: The object that might be a server or factory function
|
||||
file_path: Path to the file for error messages
|
||||
name: Name of the object for error messages
|
||||
|
||||
Returns:
|
||||
A server instance
|
||||
"""
|
||||
# Avoid circular import by importing here
|
||||
from mcp.server.fastmcp import FastMCP as FastMCP1x
|
||||
|
||||
from fastmcp.server.server import FastMCP
|
||||
|
||||
# Check if it's a function or coroutine function
|
||||
if inspect.isfunction(obj) or inspect.iscoroutinefunction(obj):
|
||||
logger.debug(f"Found factory function '{name}' in {file_path}")
|
||||
|
||||
try:
|
||||
if inspect.iscoroutinefunction(obj):
|
||||
# Async factory function
|
||||
server = await obj()
|
||||
else:
|
||||
# Sync factory function
|
||||
server = obj()
|
||||
|
||||
# Validate the result is a FastMCP server
|
||||
if not isinstance(server, FastMCP | FastMCP1x):
|
||||
logger.error(
|
||||
f"Factory function '{name}' must return a FastMCP server instance, "
|
||||
f"got {type(server).__name__}",
|
||||
extra={"file": str(file_path)},
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
logger.debug(f"Factory function '{name}' created server: {server.name}")
|
||||
return server
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Failed to call factory function '{name}': {e}",
|
||||
extra={"file": str(file_path)},
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# Not a function, return as-is (should be a server instance)
|
||||
return obj
|
||||
|
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||
|
||||
import importlib.metadata
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
from mcp.server.fastmcp import FastMCP as FastMCP1x
|
||||
|
||||
|
|
@ -318,4 +318,4 @@ async def inspect_fastmcp(mcp: FastMCP[Any] | FastMCP1x) -> FastMCPInfo:
|
|||
if isinstance(mcp, FastMCP1x):
|
||||
return await inspect_fastmcp_v1(mcp)
|
||||
else:
|
||||
return await inspect_fastmcp_v2(mcp)
|
||||
return await inspect_fastmcp_v2(cast(FastMCP[Any], mcp))
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
from typing import Any
|
||||
|
||||
from fastmcp.client.transports import ClientTransport
|
||||
from fastmcp.client.transports import (
|
||||
ClientTransport,
|
||||
SSETransport,
|
||||
StdioTransport,
|
||||
StreamableHttpTransport,
|
||||
)
|
||||
from fastmcp.mcp_config import (
|
||||
MCPConfig,
|
||||
MCPServerTypes,
|
||||
)
|
||||
from fastmcp.server.proxy import FastMCPProxy, ProxyClient
|
||||
from fastmcp.server.server import FastMCP
|
||||
|
||||
|
||||
|
|
@ -23,6 +29,7 @@ def mcp_server_type_to_servers_and_transports(
|
|||
mcp_server: MCPServerTypes,
|
||||
) -> tuple[str, FastMCP[Any], ClientTransport]:
|
||||
"""A utility function to convert each entry of an MCP Config into a transport and server."""
|
||||
|
||||
from fastmcp.mcp_config import (
|
||||
TransformingRemoteMCPServer,
|
||||
TransformingStdioMCPServer,
|
||||
|
|
@ -31,10 +38,19 @@ def mcp_server_type_to_servers_and_transports(
|
|||
server: FastMCP[Any]
|
||||
transport: ClientTransport
|
||||
|
||||
client_name = ProxyClient.generate_name(f"MCP_{name}")
|
||||
server_name = FastMCPProxy.generate_name(f"MCP_{name}")
|
||||
|
||||
if isinstance(mcp_server, TransformingRemoteMCPServer | TransformingStdioMCPServer):
|
||||
server, transport = mcp_server._to_server_and_underlying_transport()
|
||||
server, transport = mcp_server._to_server_and_underlying_transport(
|
||||
server_name=server_name,
|
||||
client_name=client_name,
|
||||
)
|
||||
else:
|
||||
transport = mcp_server.to_transport()
|
||||
server = FastMCP.as_proxy(backend=transport)
|
||||
client: ProxyClient[StreamableHttpTransport | SSETransport | StdioTransport] = (
|
||||
ProxyClient(transport=transport, name=client_name)
|
||||
)
|
||||
server = FastMCP.as_proxy(name=server_name, backend=client)
|
||||
|
||||
return name, server, transport
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from pathlib import Path
|
|||
from types import EllipsisType, UnionType
|
||||
from typing import (
|
||||
Annotated,
|
||||
Any,
|
||||
Protocol,
|
||||
TypeAlias,
|
||||
TypeVar,
|
||||
|
|
@ -122,7 +123,7 @@ def issubclass_safe(cls: type, base: type) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def is_class_member_of_type(cls: type, base: type) -> bool:
|
||||
def is_class_member_of_type(cls: Any, base: type) -> bool:
|
||||
"""
|
||||
Check if cls is a member of base, even if cls is a type variable.
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from unittest.mock import Mock, patch
|
|||
|
||||
import pytest
|
||||
|
||||
from fastmcp.cli.cli import _build_uv_command, _parse_env_var, app
|
||||
from fastmcp.cli.cli import _parse_env_var, app
|
||||
|
||||
|
||||
class TestMainCLI:
|
||||
|
|
@ -34,150 +34,6 @@ class TestMainCLI:
|
|||
_parse_env_var("INVALID_FORMAT")
|
||||
assert exc_info.value.code == 1
|
||||
|
||||
def test_build_uv_command_basic(self):
|
||||
"""Test building basic uv command."""
|
||||
cmd = _build_uv_command("server.py")
|
||||
expected = ["uv", "run", "--with", "fastmcp", "fastmcp", "run", "server.py"]
|
||||
assert cmd == expected
|
||||
|
||||
def test_build_uv_command_with_editable(self):
|
||||
"""Test building uv command with editable package."""
|
||||
editable_path = Path("/path/to/package")
|
||||
cmd = _build_uv_command("server.py", with_editable=editable_path)
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--with",
|
||||
"fastmcp",
|
||||
"--with-editable",
|
||||
str(editable_path),
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
]
|
||||
assert cmd == expected
|
||||
|
||||
def test_build_uv_command_with_packages(self):
|
||||
"""Test building uv command with additional packages."""
|
||||
cmd = _build_uv_command("server.py", with_packages=["pkg1", "pkg2"])
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--with",
|
||||
"fastmcp",
|
||||
"--with",
|
||||
"pkg1",
|
||||
"--with",
|
||||
"pkg2",
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
]
|
||||
assert cmd == expected
|
||||
|
||||
def test_build_uv_command_no_banner(self):
|
||||
"""Test building uv command with no banner flag."""
|
||||
cmd = _build_uv_command("server.py", no_banner=True)
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--with",
|
||||
"fastmcp",
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
"--no-banner",
|
||||
]
|
||||
assert cmd == expected
|
||||
|
||||
def test_build_uv_command_with_python_version(self):
|
||||
"""Test building uv command with Python version."""
|
||||
cmd = _build_uv_command("server.py", python_version="3.11")
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--python",
|
||||
"3.11",
|
||||
"--with",
|
||||
"fastmcp",
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
]
|
||||
assert cmd == expected
|
||||
|
||||
def test_build_uv_command_with_project(self):
|
||||
"""Test building uv command with project directory."""
|
||||
project_path = Path("/path/to/project")
|
||||
cmd = _build_uv_command("server.py", project=project_path)
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--project",
|
||||
str(project_path),
|
||||
"--with",
|
||||
"fastmcp",
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
]
|
||||
assert cmd == expected
|
||||
|
||||
def test_build_uv_command_with_requirements(self):
|
||||
"""Test building uv command with requirements file."""
|
||||
req_path = Path("requirements.txt")
|
||||
cmd = _build_uv_command("server.py", with_requirements=req_path)
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--with",
|
||||
"fastmcp",
|
||||
"--with-requirements",
|
||||
"requirements.txt",
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
]
|
||||
assert cmd == expected
|
||||
|
||||
def test_build_uv_command_with_all_options(self):
|
||||
"""Test building uv command with all options."""
|
||||
project_path = Path("/my/project")
|
||||
editable_path = Path("/local/pkg")
|
||||
requirements_path = Path("reqs.txt")
|
||||
cmd = _build_uv_command(
|
||||
"server.py",
|
||||
python_version="3.10",
|
||||
project=project_path,
|
||||
with_packages=["pandas", "numpy"],
|
||||
with_requirements=requirements_path,
|
||||
with_editable=editable_path,
|
||||
no_banner=True,
|
||||
)
|
||||
expected = [
|
||||
"uv",
|
||||
"run",
|
||||
"--python",
|
||||
"3.10",
|
||||
"--project",
|
||||
str(project_path),
|
||||
"--with",
|
||||
"fastmcp",
|
||||
"--with-editable",
|
||||
str(editable_path),
|
||||
"--with",
|
||||
"pandas",
|
||||
"--with",
|
||||
"numpy",
|
||||
"--with-requirements",
|
||||
str(requirements_path),
|
||||
"fastmcp",
|
||||
"run",
|
||||
"server.py",
|
||||
"--no-banner",
|
||||
]
|
||||
assert cmd == expected
|
||||
|
||||
|
||||
class TestVersionCommand:
|
||||
"""Test the version command."""
|
||||
|
|
@ -472,6 +328,47 @@ class TestRunCommand:
|
|||
]
|
||||
)
|
||||
|
||||
def test_run_command_parsing_skip_env_flag(self):
|
||||
"""Test run command parsing with --skip-env flag."""
|
||||
command, bound, _ = app.parse_args(
|
||||
[
|
||||
"run",
|
||||
"server.py",
|
||||
"--skip-env",
|
||||
]
|
||||
)
|
||||
assert command is not None
|
||||
assert bound.arguments["server_spec"] == "server.py"
|
||||
assert bound.arguments["skip_env"] is True
|
||||
|
||||
def test_run_command_parsing_skip_source_flag(self):
|
||||
"""Test run command parsing with --skip-source flag."""
|
||||
command, bound, _ = app.parse_args(
|
||||
[
|
||||
"run",
|
||||
"server.py",
|
||||
"--skip-source",
|
||||
]
|
||||
)
|
||||
assert command is not None
|
||||
assert bound.arguments["server_spec"] == "server.py"
|
||||
assert bound.arguments["skip_source"] is True
|
||||
|
||||
def test_run_command_parsing_both_skip_flags(self):
|
||||
"""Test run command parsing with both --skip-env and --skip-source flags."""
|
||||
command, bound, _ = app.parse_args(
|
||||
[
|
||||
"run",
|
||||
"server.py",
|
||||
"--skip-env",
|
||||
"--skip-source",
|
||||
]
|
||||
)
|
||||
assert command is not None
|
||||
assert bound.arguments["server_spec"] == "server.py"
|
||||
assert bound.arguments["skip_env"] is True
|
||||
assert bound.arguments["skip_source"] is True
|
||||
|
||||
|
||||
class TestWindowsSpecific:
|
||||
"""Test Windows-specific functionality."""
|
||||
|
|
@ -557,22 +454,27 @@ class TestWindowsSpecific:
|
|||
|
||||
def test_windows_path_parsing_with_colon(self, tmp_path):
|
||||
"""Test parsing Windows paths with drive letters and colons."""
|
||||
from fastmcp.cli.run import parse_file_path
|
||||
from pathlib import Path
|
||||
|
||||
from fastmcp.utilities.fastmcp_config.v1.sources.filesystem import (
|
||||
FileSystemSource,
|
||||
)
|
||||
|
||||
# Create a real test file to test the logic
|
||||
test_file = tmp_path / "server.py"
|
||||
test_file.write_text("# test server")
|
||||
|
||||
# Test normal file parsing (works on all platforms)
|
||||
file_path, obj = parse_file_path(str(test_file))
|
||||
assert obj is None
|
||||
source = FileSystemSource(path=str(test_file))
|
||||
assert source.entrypoint is None
|
||||
assert Path(source.path).resolve() == test_file.resolve()
|
||||
|
||||
# Test file:object parsing
|
||||
file_path, obj = parse_file_path(f"{test_file}:myapp")
|
||||
assert obj == "myapp"
|
||||
source = FileSystemSource(path=f"{test_file}:myapp")
|
||||
assert source.entrypoint == "myapp"
|
||||
|
||||
# Test that the file portion resolves correctly when object is specified
|
||||
assert file_path == test_file.resolve()
|
||||
assert Path(source.path).resolve() == test_file.resolve()
|
||||
|
||||
|
||||
class TestInspectCommand:
|
||||
|
|
|
|||
|
|
@ -8,77 +8,52 @@ import pytest
|
|||
from pydantic import ValidationError
|
||||
|
||||
from fastmcp.utilities.fastmcp_config import (
|
||||
DeploymentConfig,
|
||||
EntrypointConfig,
|
||||
EnvironmentConfig,
|
||||
Deployment,
|
||||
Environment,
|
||||
FastMCPConfig,
|
||||
)
|
||||
from fastmcp.utilities.fastmcp_config.v1.sources.filesystem import FileSystemSource
|
||||
|
||||
|
||||
class TestEntrypointConfig:
|
||||
"""Test EntrypointConfig class."""
|
||||
class TestFileSystemSource:
|
||||
"""Test FileSystemSource class."""
|
||||
|
||||
def test_string_entrypoint(self):
|
||||
"""Test that string entrypoint is converted to EntrypointConfig."""
|
||||
config = FastMCPConfig(entrypoint="server.py")
|
||||
# With the new validator, this should be converted to EntrypointConfig
|
||||
assert isinstance(config.entrypoint, EntrypointConfig)
|
||||
assert config.entrypoint.file == "server.py"
|
||||
assert config.entrypoint.object is None
|
||||
def test_dict_source_minimal(self):
|
||||
"""Test that dict source is converted to FileSystemSource."""
|
||||
config = FastMCPConfig(source={"path": "server.py"})
|
||||
# Dict is converted to FileSystemSource
|
||||
assert isinstance(config.source, FileSystemSource)
|
||||
assert config.source.path == "server.py"
|
||||
assert config.source.entrypoint is None
|
||||
assert config.source.type == "filesystem"
|
||||
|
||||
# get_entrypoint should return the same object
|
||||
entrypoint = config.get_entrypoint()
|
||||
assert isinstance(entrypoint, EntrypointConfig)
|
||||
assert entrypoint.file == "server.py"
|
||||
assert entrypoint.object is None
|
||||
def test_dict_source_with_entrypoint(self):
|
||||
"""Test dict source with entrypoint field."""
|
||||
config = FastMCPConfig(source={"path": "server.py", "entrypoint": "app"})
|
||||
# Dict with entrypoint is converted to FileSystemSource
|
||||
assert isinstance(config.source, FileSystemSource)
|
||||
assert config.source.path == "server.py"
|
||||
assert config.source.entrypoint == "app"
|
||||
assert config.source.type == "filesystem"
|
||||
|
||||
def test_string_entrypoint_with_object(self):
|
||||
"""Test string entrypoint with :object syntax."""
|
||||
config = FastMCPConfig(entrypoint="server.py:app")
|
||||
# With the new validator, this should be converted to EntrypointConfig
|
||||
assert isinstance(config.entrypoint, EntrypointConfig)
|
||||
assert config.entrypoint.file == "server.py"
|
||||
assert config.entrypoint.object == "app"
|
||||
|
||||
# get_entrypoint should return the same object
|
||||
entrypoint = config.get_entrypoint()
|
||||
assert isinstance(entrypoint, EntrypointConfig)
|
||||
assert entrypoint.file == "server.py"
|
||||
assert entrypoint.object == "app"
|
||||
|
||||
def test_object_entrypoint(self):
|
||||
"""Test EntrypointConfig object format."""
|
||||
def test_filesystem_source_entrypoint(self):
|
||||
"""Test FileSystemSource entrypoint format."""
|
||||
config = FastMCPConfig(
|
||||
entrypoint=EntrypointConfig(file="src/server.py", object="mcp")
|
||||
source=FileSystemSource(path="src/server.py", entrypoint="mcp")
|
||||
)
|
||||
assert isinstance(config.entrypoint, EntrypointConfig)
|
||||
assert config.entrypoint.file == "src/server.py"
|
||||
assert config.entrypoint.object == "mcp"
|
||||
|
||||
def test_get_entrypoint_path_resolution(self, tmp_path):
|
||||
"""Test that get_entrypoint resolves paths relative to config file."""
|
||||
config_dir = tmp_path / "config"
|
||||
config_dir.mkdir()
|
||||
server_dir = tmp_path / "src"
|
||||
server_dir.mkdir()
|
||||
server_file = server_dir / "server.py"
|
||||
server_file.write_text("# server")
|
||||
|
||||
config = FastMCPConfig(entrypoint="../src/server.py")
|
||||
entrypoint = config.get_entrypoint(config_dir / "fastmcp.json")
|
||||
|
||||
# Should resolve to absolute path
|
||||
assert Path(entrypoint.file).is_absolute()
|
||||
assert Path(entrypoint.file) == server_file.resolve()
|
||||
assert isinstance(config.source, FileSystemSource)
|
||||
assert config.source.path == "src/server.py"
|
||||
assert config.source.entrypoint == "mcp"
|
||||
assert config.source.type == "filesystem"
|
||||
|
||||
|
||||
class TestEnvironmentConfig:
|
||||
"""Test EnvironmentConfig class."""
|
||||
class TestEnvironment:
|
||||
"""Test Environment class."""
|
||||
|
||||
def test_environment_config_fields(self):
|
||||
"""Test all EnvironmentConfig fields."""
|
||||
"""Test all Environment fields."""
|
||||
config = FastMCPConfig(
|
||||
entrypoint="server.py",
|
||||
source={"path": "server.py"},
|
||||
environment={
|
||||
"python": "3.12",
|
||||
"dependencies": ["requests", "numpy>=2.0"],
|
||||
|
|
@ -98,27 +73,29 @@ class TestEnvironmentConfig:
|
|||
def test_needs_uv(self):
|
||||
"""Test needs_uv() method."""
|
||||
# No environment config - doesn't need UV
|
||||
config = FastMCPConfig(entrypoint="server.py")
|
||||
config = FastMCPConfig(source={"path": "server.py"})
|
||||
assert not config.environment.needs_uv()
|
||||
|
||||
# Empty environment - doesn't need UV
|
||||
config = FastMCPConfig(entrypoint="server.py", environment={})
|
||||
config = FastMCPConfig(source={"path": "server.py"}, environment={})
|
||||
assert not config.environment.needs_uv()
|
||||
|
||||
# With dependencies - needs UV
|
||||
config = FastMCPConfig(
|
||||
entrypoint="server.py", environment={"dependencies": ["requests"]}
|
||||
source={"path": "server.py"}, environment={"dependencies": ["requests"]}
|
||||
)
|
||||
assert config.environment.needs_uv()
|
||||
|
||||
# With Python version - needs UV
|
||||
config = FastMCPConfig(entrypoint="server.py", environment={"python": "3.12"})
|
||||
config = FastMCPConfig(
|
||||
source={"path": "server.py"}, environment={"python": "3.12"}
|
||||
)
|
||||
assert config.environment.needs_uv()
|
||||
|
||||
def test_build_uv_args(self):
|
||||
"""Test build_uv_args() method."""
|
||||
config = FastMCPConfig(
|
||||
entrypoint="server.py",
|
||||
source={"path": "server.py"},
|
||||
environment={
|
||||
"python": "3.12",
|
||||
"dependencies": ["requests", "numpy"],
|
||||
|
|
@ -143,33 +120,10 @@ class TestEnvironmentConfig:
|
|||
assert "run" in args[-2:]
|
||||
assert "server.py" in args[-1:]
|
||||
|
||||
def test_merge_with_cli_args(self):
|
||||
"""Test merge_with_cli_args() method."""
|
||||
config = FastMCPConfig(
|
||||
entrypoint="server.py",
|
||||
environment={
|
||||
"python": "3.11",
|
||||
"dependencies": ["requests"],
|
||||
},
|
||||
)
|
||||
|
||||
# CLI args should take precedence
|
||||
merged = config.environment.merge_with_cli_args(
|
||||
python="3.12", # Override
|
||||
with_packages=["numpy"], # Add to dependencies
|
||||
with_requirements=None,
|
||||
project=None,
|
||||
)
|
||||
|
||||
assert merged["python"] == "3.12" # CLI override
|
||||
assert set(merged["with_packages"]) == {"requests", "numpy"} # Merged
|
||||
assert merged["with_requirements"] is None
|
||||
assert merged["project"] is None
|
||||
|
||||
def test_run_with_uv(self):
|
||||
"""Test run_with_uv() subprocess execution."""
|
||||
config = FastMCPConfig(
|
||||
entrypoint="server.py", environment={"dependencies": ["requests"]}
|
||||
source={"path": "server.py"}, environment={"dependencies": ["requests"]}
|
||||
)
|
||||
|
||||
# run_with_uv calls sys.exit, so we expect SystemExit
|
||||
|
|
@ -182,13 +136,13 @@ class TestEnvironmentConfig:
|
|||
assert exc_info.value.code == 1
|
||||
|
||||
|
||||
class TestDeploymentConfig:
|
||||
"""Test DeploymentConfig class."""
|
||||
class TestDeployment:
|
||||
"""Test Deployment class."""
|
||||
|
||||
def test_deployment_config_fields(self):
|
||||
"""Test all DeploymentConfig fields."""
|
||||
"""Test all Deployment fields."""
|
||||
config = FastMCPConfig(
|
||||
entrypoint="server.py",
|
||||
source={"path": "server.py"},
|
||||
deployment={
|
||||
"transport": "http",
|
||||
"host": "0.0.0.0",
|
||||
|
|
@ -211,33 +165,6 @@ class TestDeploymentConfig:
|
|||
assert deploy.cwd == "./work"
|
||||
assert deploy.args == ["--debug"]
|
||||
|
||||
def test_merge_with_cli_args(self):
|
||||
"""Test DeploymentConfig merge_with_cli_args() method."""
|
||||
config = FastMCPConfig(
|
||||
entrypoint="server.py",
|
||||
deployment={
|
||||
"transport": "stdio",
|
||||
"port": 3000,
|
||||
"log_level": "INFO",
|
||||
},
|
||||
)
|
||||
|
||||
# CLI args should take precedence
|
||||
merged = config.deployment.merge_with_cli_args(
|
||||
transport="http", # Override
|
||||
host="localhost", # New value
|
||||
port=None, # Keep config value
|
||||
path=None,
|
||||
log_level="DEBUG", # Override
|
||||
server_args=["--test"],
|
||||
)
|
||||
|
||||
assert merged["transport"] == "http" # CLI override
|
||||
assert merged["host"] == "localhost" # CLI value
|
||||
assert merged["port"] == 3000 # Config value (CLI was None)
|
||||
assert merged["log_level"] == "DEBUG" # CLI override
|
||||
assert merged["server_args"] == ["--test"] # CLI value
|
||||
|
||||
def test_apply_runtime_settings(self, tmp_path):
|
||||
"""Test apply_runtime_settings() method."""
|
||||
import os
|
||||
|
|
@ -247,7 +174,7 @@ class TestDeploymentConfig:
|
|||
work_dir.mkdir()
|
||||
|
||||
config = FastMCPConfig(
|
||||
entrypoint="server.py",
|
||||
source={"path": "server.py"},
|
||||
deployment={
|
||||
"env": {"TEST_VAR": "test_value"},
|
||||
"cwd": "work",
|
||||
|
|
@ -283,7 +210,7 @@ class TestDeploymentConfig:
|
|||
os.environ["ENV_NAME"] = "production"
|
||||
|
||||
config = FastMCPConfig(
|
||||
entrypoint="server.py",
|
||||
source={"path": "server.py"},
|
||||
deployment={
|
||||
"env": {
|
||||
"API_URL": "https://api.${BASE_URL}/v1",
|
||||
|
|
@ -328,24 +255,24 @@ class TestFastMCPConfig:
|
|||
|
||||
def test_minimal_config(self):
|
||||
"""Test creating a config with only required fields."""
|
||||
config = FastMCPConfig(entrypoint="server.py")
|
||||
assert isinstance(config.entrypoint, EntrypointConfig)
|
||||
assert config.entrypoint.file == "server.py"
|
||||
assert config.entrypoint.object is None
|
||||
config = FastMCPConfig(source={"path": "server.py"})
|
||||
assert isinstance(config.source, FileSystemSource)
|
||||
assert config.source.path == "server.py"
|
||||
assert config.source.entrypoint is None
|
||||
# Environment and deployment are now always present but empty
|
||||
assert isinstance(config.environment, EnvironmentConfig)
|
||||
assert isinstance(config.deployment, DeploymentConfig)
|
||||
assert isinstance(config.environment, Environment)
|
||||
assert isinstance(config.deployment, Deployment)
|
||||
# Check they have no values set
|
||||
assert not config.environment.needs_uv()
|
||||
assert all(
|
||||
getattr(config.deployment, field, None) is None
|
||||
for field in DeploymentConfig.model_fields
|
||||
for field in Deployment.model_fields
|
||||
)
|
||||
|
||||
def test_nested_structure(self):
|
||||
"""Test the nested configuration structure."""
|
||||
config = FastMCPConfig(
|
||||
entrypoint="server.py",
|
||||
source={"path": "server.py"},
|
||||
environment={
|
||||
"python": "3.12",
|
||||
"dependencies": ["fastmcp"],
|
||||
|
|
@ -356,17 +283,17 @@ class TestFastMCPConfig:
|
|||
},
|
||||
)
|
||||
|
||||
assert isinstance(config.entrypoint, EntrypointConfig)
|
||||
assert config.entrypoint.file == "server.py"
|
||||
assert config.entrypoint.object is None
|
||||
assert isinstance(config.environment, EnvironmentConfig)
|
||||
assert isinstance(config.deployment, DeploymentConfig)
|
||||
assert isinstance(config.source, FileSystemSource)
|
||||
assert config.source.path == "server.py"
|
||||
assert config.source.entrypoint is None
|
||||
assert isinstance(config.environment, Environment)
|
||||
assert isinstance(config.deployment, Deployment)
|
||||
|
||||
def test_from_file(self, tmp_path):
|
||||
"""Test loading config from JSON file with nested structure."""
|
||||
config_data = {
|
||||
"$schema": "https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
"entrypoint": {"file": "src/server.py", "object": "app"},
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {"path": "src/server.py", "entrypoint": "app"},
|
||||
"environment": {"python": "3.12", "dependencies": ["requests"]},
|
||||
"deployment": {"transport": "http", "port": 8000},
|
||||
}
|
||||
|
|
@ -376,19 +303,19 @@ class TestFastMCPConfig:
|
|||
|
||||
config = FastMCPConfig.from_file(config_file)
|
||||
|
||||
# When loaded from JSON with object format, it becomes EntrypointConfig
|
||||
assert isinstance(config.entrypoint, EntrypointConfig)
|
||||
assert config.entrypoint.file == "src/server.py"
|
||||
assert config.entrypoint.object == "app"
|
||||
# When loaded from JSON with entrypoint format, it becomes EntrypointConfig
|
||||
assert isinstance(config.source, FileSystemSource)
|
||||
assert config.source.path == "src/server.py"
|
||||
assert config.source.entrypoint == "app"
|
||||
assert config.environment.python == "3.12"
|
||||
assert config.environment.dependencies == ["requests"]
|
||||
assert config.deployment.transport == "http"
|
||||
assert config.deployment.port == 8000
|
||||
|
||||
def test_from_file_with_string_entrypoint(self, tmp_path):
|
||||
"""Test loading config with string entrypoint."""
|
||||
"""Test loading config with dict source format."""
|
||||
config_data = {
|
||||
"entrypoint": "server.py:mcp",
|
||||
"source": {"path": "server.py", "entrypoint": "mcp"},
|
||||
"environment": {"dependencies": ["fastmcp"]},
|
||||
}
|
||||
|
||||
|
|
@ -397,19 +324,14 @@ class TestFastMCPConfig:
|
|||
|
||||
config = FastMCPConfig.from_file(config_file)
|
||||
# String entrypoint with : should be converted to EntrypointConfig
|
||||
assert isinstance(config.entrypoint, EntrypointConfig)
|
||||
assert config.entrypoint.file == "server.py"
|
||||
assert config.entrypoint.object == "mcp"
|
||||
assert isinstance(config.source, FileSystemSource)
|
||||
assert config.source.path == "server.py"
|
||||
assert config.source.entrypoint == "mcp"
|
||||
|
||||
# get_entrypoint should return the same
|
||||
entrypoint = config.get_entrypoint()
|
||||
assert entrypoint.file == "server.py"
|
||||
assert entrypoint.object == "mcp"
|
||||
|
||||
def test_string_entrypoint_with_object_and_environment(self, tmp_path):
|
||||
"""Test that file.py:object syntax works with environment config."""
|
||||
def test_string_entrypoint_with_entrypoint_and_environment(self, tmp_path):
|
||||
"""Test that file.py:entrypoint syntax works with environment config."""
|
||||
config_data = {
|
||||
"entrypoint": "src/server.py:app",
|
||||
"source": {"path": "src/server.py", "entrypoint": "app"},
|
||||
"environment": {"python": "3.12", "dependencies": ["fastmcp", "requests"]},
|
||||
"deployment": {"transport": "http", "port": 8000},
|
||||
}
|
||||
|
|
@ -420,9 +342,9 @@ class TestFastMCPConfig:
|
|||
config = FastMCPConfig.from_file(config_file)
|
||||
|
||||
# Should be parsed into EntrypointConfig
|
||||
assert isinstance(config.entrypoint, EntrypointConfig)
|
||||
assert config.entrypoint.file == "src/server.py"
|
||||
assert config.entrypoint.object == "app"
|
||||
assert isinstance(config.source, FileSystemSource)
|
||||
assert config.source.path == "src/server.py"
|
||||
assert config.source.entrypoint == "app"
|
||||
|
||||
# Environment config should still work
|
||||
assert config.environment.python == "3.12"
|
||||
|
|
@ -435,7 +357,7 @@ class TestFastMCPConfig:
|
|||
def test_find_config_in_current_dir(self, tmp_path):
|
||||
"""Test finding config in current directory."""
|
||||
config_file = tmp_path / "fastmcp.json"
|
||||
config_file.write_text(json.dumps({"entrypoint": "server.py"}))
|
||||
config_file.write_text(json.dumps({"source": {"path": "server.py"}}))
|
||||
|
||||
original_cwd = os.getcwd()
|
||||
try:
|
||||
|
|
@ -448,7 +370,7 @@ class TestFastMCPConfig:
|
|||
def test_find_config_not_in_parent_dir(self, tmp_path):
|
||||
"""Test that config is NOT found in parent directory."""
|
||||
config_file = tmp_path / "fastmcp.json"
|
||||
config_file.write_text(json.dumps({"entrypoint": "server.py"}))
|
||||
config_file.write_text(json.dumps({"source": {"path": "server.py"}}))
|
||||
|
||||
subdir = tmp_path / "subdir"
|
||||
subdir.mkdir()
|
||||
|
|
@ -460,7 +382,7 @@ class TestFastMCPConfig:
|
|||
def test_find_config_in_specified_dir(self, tmp_path):
|
||||
"""Test finding config in the specified directory."""
|
||||
config_file = tmp_path / "fastmcp.json"
|
||||
config_file.write_text(json.dumps({"entrypoint": "server.py"}))
|
||||
config_file.write_text(json.dumps({"source": {"path": "server.py"}}))
|
||||
|
||||
# Should find config when looking in the directory that contains it
|
||||
found = FastMCPConfig.find_config(tmp_path)
|
||||
|
|
@ -474,7 +396,7 @@ class TestFastMCPConfig:
|
|||
def test_invalid_transport(self, tmp_path):
|
||||
"""Test loading config with invalid transport value."""
|
||||
config_data = {
|
||||
"entrypoint": "server.py",
|
||||
"source": {"path": "server.py"},
|
||||
"deployment": {"transport": "invalid_transport"},
|
||||
}
|
||||
|
||||
|
|
@ -485,29 +407,33 @@ class TestFastMCPConfig:
|
|||
FastMCPConfig.from_file(config_file)
|
||||
|
||||
def test_optional_sections(self):
|
||||
"""Test that all config sections are optional except entrypoint."""
|
||||
# Only entrypoint is required
|
||||
config = FastMCPConfig(entrypoint="server.py")
|
||||
assert isinstance(config.entrypoint, EntrypointConfig)
|
||||
assert config.entrypoint.file == "server.py"
|
||||
"""Test that all config sections are optional except source."""
|
||||
# Only source is required
|
||||
config = FastMCPConfig(source={"path": "server.py"})
|
||||
assert isinstance(config.source, FileSystemSource)
|
||||
assert config.source.path == "server.py"
|
||||
# Environment and deployment are now always present but may be empty
|
||||
assert isinstance(config.environment, EnvironmentConfig)
|
||||
assert isinstance(config.deployment, DeploymentConfig)
|
||||
assert isinstance(config.environment, Environment)
|
||||
assert isinstance(config.deployment, Deployment)
|
||||
|
||||
# Only environment with values
|
||||
config = FastMCPConfig(entrypoint="server.py", environment={"python": "3.12"})
|
||||
config = FastMCPConfig(
|
||||
source={"path": "server.py"}, environment={"python": "3.12"}
|
||||
)
|
||||
assert config.environment.python == "3.12"
|
||||
assert isinstance(config.deployment, DeploymentConfig)
|
||||
assert isinstance(config.deployment, Deployment)
|
||||
assert all(
|
||||
getattr(config.deployment, field, None) is None
|
||||
for field in DeploymentConfig.model_fields
|
||||
for field in Deployment.model_fields
|
||||
)
|
||||
|
||||
# Only deployment with values
|
||||
config = FastMCPConfig(entrypoint="server.py", deployment={"transport": "http"})
|
||||
assert isinstance(config.environment, EnvironmentConfig)
|
||||
config = FastMCPConfig(
|
||||
source={"path": "server.py"}, deployment={"transport": "http"}
|
||||
)
|
||||
assert isinstance(config.environment, Environment)
|
||||
assert all(
|
||||
getattr(config.environment, field, None) is None
|
||||
for field in EnvironmentConfig.model_fields
|
||||
for field in Environment.model_fields
|
||||
)
|
||||
assert config.deployment.transport == "http"
|
||||
|
|
|
|||
|
|
@ -239,11 +239,14 @@ class TestInstallCursor:
|
|||
"""Test cursor installation with editable package."""
|
||||
mock_open_deeplink.return_value = True
|
||||
|
||||
# Use an absolute path that works on all platforms
|
||||
editable_path = Path.cwd() / "local" / "package"
|
||||
|
||||
result = install_cursor(
|
||||
file=Path("/path/to/server.py"),
|
||||
server_object="custom_app",
|
||||
name="test-server",
|
||||
with_editable=Path("/local/package"),
|
||||
with_editable=editable_path,
|
||||
)
|
||||
|
||||
assert result is True
|
||||
|
|
@ -255,9 +258,10 @@ class TestInstallCursor:
|
|||
config_data = json.loads(decoded)
|
||||
|
||||
assert "--with-editable" in config_data["args"]
|
||||
# Check for the editable path in a platform-agnostic way
|
||||
editable_path_str = str(Path("/local/package"))
|
||||
assert editable_path_str in config_data["args"]
|
||||
# Check that the path was resolved (should be absolute)
|
||||
editable_idx = config_data["args"].index("--with-editable") + 1
|
||||
resolved_path = config_data["args"][editable_idx]
|
||||
assert Path(resolved_path).is_absolute()
|
||||
assert "server.py:custom_app" in " ".join(config_data["args"])
|
||||
|
||||
@patch("fastmcp.cli.install.cursor.open_deeplink")
|
||||
|
|
@ -276,7 +280,11 @@ class TestInstallCursor:
|
|||
# Verify failure message was printed
|
||||
mock_print.assert_called()
|
||||
|
||||
def test_install_cursor_deduplicate_packages(self):
|
||||
@patch(
|
||||
"fastmcp.utilities.fastmcp_config.v1.fastmcp_config.Environment._find_fastmcp_dev_path",
|
||||
return_value=None, # Mock to disable dev mode so "fastmcp" count is predictable
|
||||
)
|
||||
def test_install_cursor_deduplicate_packages(self, mock_find_dev):
|
||||
"""Test that duplicate packages are deduplicated."""
|
||||
with patch("fastmcp.cli.install.cursor.open_deeplink") as mock_open:
|
||||
mock_open.return_value = True
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ if __name__ == "__main__":
|
|||
|
||||
# Create config file
|
||||
config_data = {
|
||||
"$schema": "https://gofastmcp.com/schemas/fastmcp_config/v1.json",
|
||||
"entrypoint": "server.py",
|
||||
"$schema": "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json",
|
||||
"source": {"path": "server.py"},
|
||||
"environment": {
|
||||
"python": sys.version.split()[0], # Use current Python version
|
||||
"dependencies": ["fastmcp"],
|
||||
|
|
@ -58,7 +58,7 @@ class TestConfigFileDetection:
|
|||
def test_detect_standard_fastmcp_json(self, tmp_path):
|
||||
"""Test detection of standard fastmcp.json file."""
|
||||
config_file = tmp_path / "fastmcp.json"
|
||||
config_file.write_text(json.dumps({"entrypoint": "server.py"}))
|
||||
config_file.write_text(json.dumps({"source": {"path": "server.py"}}))
|
||||
|
||||
# Should be detected as fastmcp config
|
||||
assert "fastmcp.json" in config_file.name
|
||||
|
|
@ -67,7 +67,7 @@ class TestConfigFileDetection:
|
|||
def test_detect_prefixed_fastmcp_json(self, tmp_path):
|
||||
"""Test detection of prefixed fastmcp.json files."""
|
||||
config_file = tmp_path / "my.fastmcp.json"
|
||||
config_file.write_text(json.dumps({"entrypoint": "server.py"}))
|
||||
config_file.write_text(json.dumps({"source": {"path": "server.py"}}))
|
||||
|
||||
# Should be detected as fastmcp config
|
||||
assert "fastmcp.json" in config_file.name
|
||||
|
|
@ -75,7 +75,7 @@ class TestConfigFileDetection:
|
|||
def test_detect_test_fastmcp_json(self, tmp_path):
|
||||
"""Test detection of test_fastmcp.json file."""
|
||||
config_file = tmp_path / "test_fastmcp.json"
|
||||
config_file.write_text(json.dumps({"entrypoint": "server.py"}))
|
||||
config_file.write_text(json.dumps({"source": {"path": "server.py"}}))
|
||||
|
||||
# Should be detected as fastmcp config
|
||||
assert "fastmcp.json" in config_file.name
|
||||
|
|
@ -91,14 +91,18 @@ class TestConfigWithClient:
|
|||
config_file = server_with_config / "fastmcp.json"
|
||||
config = FastMCPConfig.from_file(config_file)
|
||||
|
||||
# Import the server using the entrypoint
|
||||
# Import the server using the source
|
||||
import importlib.util
|
||||
import sys
|
||||
|
||||
entrypoint = config.get_entrypoint(config_file)
|
||||
spec = importlib.util.spec_from_file_location("test_server", entrypoint.file)
|
||||
# Resolve the path from the source
|
||||
source_path = Path(config.source.path)
|
||||
if not source_path.is_absolute():
|
||||
source_path = (config_file.parent / source_path).resolve()
|
||||
|
||||
spec = importlib.util.spec_from_file_location("test_server", str(source_path))
|
||||
if spec is None or spec.loader is None:
|
||||
raise RuntimeError(f"Could not load module from {entrypoint.file}")
|
||||
raise RuntimeError(f"Could not load module from {source_path}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules["test_server"] = module
|
||||
spec.loader.exec_module(module)
|
||||
|
|
@ -129,7 +133,7 @@ class TestEnvironmentExecution:
|
|||
def test_needs_uv_with_dependencies(self):
|
||||
"""Test that environment with dependencies needs UV."""
|
||||
config = FastMCPConfig(
|
||||
entrypoint="server.py",
|
||||
source={"path": "server.py"},
|
||||
environment={"dependencies": ["requests", "numpy"]}, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
|
|
@ -139,7 +143,7 @@ class TestEnvironmentExecution:
|
|||
def test_needs_uv_with_python_version(self):
|
||||
"""Test that environment with Python version needs UV."""
|
||||
config = FastMCPConfig(
|
||||
entrypoint="server.py",
|
||||
source={"path": "server.py"},
|
||||
environment={"python": "3.12"}, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
|
|
@ -148,7 +152,7 @@ class TestEnvironmentExecution:
|
|||
|
||||
def test_no_uv_needed_without_environment(self):
|
||||
"""Test that no UV is needed without environment config."""
|
||||
config = FastMCPConfig(entrypoint="server.py")
|
||||
config = FastMCPConfig(source={"path": "server.py"})
|
||||
|
||||
# Environment is now always present but may be empty
|
||||
assert config.environment is not None
|
||||
|
|
@ -157,7 +161,7 @@ class TestEnvironmentExecution:
|
|||
def test_no_uv_needed_with_empty_environment(self):
|
||||
"""Test that no UV is needed with empty environment config."""
|
||||
config = FastMCPConfig(
|
||||
entrypoint="server.py",
|
||||
source={"path": "server.py"},
|
||||
environment={}, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
|
|
@ -165,77 +169,11 @@ class TestEnvironmentExecution:
|
|||
assert not config.environment.needs_uv()
|
||||
|
||||
|
||||
class TestCLIArgumentMerging:
|
||||
"""Test CLI argument merging with config values."""
|
||||
|
||||
def test_cli_overrides_environment(self):
|
||||
"""Test that CLI args override environment config."""
|
||||
config = FastMCPConfig(
|
||||
entrypoint="server.py",
|
||||
environment={"python": "3.11", "dependencies": ["requests"]}, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
assert config.environment is not None
|
||||
merged = config.environment.merge_with_cli_args(
|
||||
python="3.12", # Override Python version
|
||||
with_packages=["numpy"], # Add package
|
||||
with_requirements=None,
|
||||
project=None,
|
||||
)
|
||||
|
||||
assert merged["python"] == "3.12" # CLI wins
|
||||
assert "requests" in merged["with_packages"] # From config
|
||||
assert "numpy" in merged["with_packages"] # From CLI
|
||||
|
||||
def test_cli_overrides_deployment(self):
|
||||
"""Test that CLI args override deployment config."""
|
||||
config = FastMCPConfig(
|
||||
entrypoint="server.py",
|
||||
deployment={"transport": "stdio", "port": 3000, "log_level": "INFO"}, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
assert config.deployment is not None
|
||||
merged = config.deployment.merge_with_cli_args(
|
||||
transport="http", # Override transport
|
||||
host="localhost", # New value
|
||||
port=8080, # Override port
|
||||
path=None,
|
||||
log_level="DEBUG", # Override log level
|
||||
server_args=None,
|
||||
)
|
||||
|
||||
assert merged["transport"] == "http" # CLI wins
|
||||
assert merged["host"] == "localhost" # CLI value
|
||||
assert merged["port"] == 8080 # CLI wins
|
||||
assert merged["log_level"] == "DEBUG" # CLI wins
|
||||
|
||||
def test_config_values_when_cli_is_none(self):
|
||||
"""Test that config values are used when CLI args are None."""
|
||||
config = FastMCPConfig(
|
||||
entrypoint="server.py",
|
||||
deployment={"transport": "http", "port": 3000}, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
assert config.deployment is not None
|
||||
merged = config.deployment.merge_with_cli_args(
|
||||
transport=None, # Use config
|
||||
host=None, # No value
|
||||
port=None, # Use config
|
||||
path=None,
|
||||
log_level=None,
|
||||
server_args=None,
|
||||
)
|
||||
|
||||
assert merged["transport"] == "http" # From config
|
||||
assert merged["port"] == 3000 # From config
|
||||
assert merged["host"] is None # No value provided
|
||||
|
||||
|
||||
class TestPathResolution:
|
||||
"""Test path resolution in configurations."""
|
||||
|
||||
def test_entrypoint_path_resolution(self, tmp_path):
|
||||
"""Test that entrypoint paths are resolved relative to config."""
|
||||
def test_source_path_resolution(self, tmp_path):
|
||||
"""Test that source paths are resolved relative to config."""
|
||||
# Create nested directory structure
|
||||
config_dir = tmp_path / "config"
|
||||
config_dir.mkdir()
|
||||
|
|
@ -246,14 +184,11 @@ class TestPathResolution:
|
|||
server_file = src_dir / "server.py"
|
||||
server_file.write_text("# Server")
|
||||
|
||||
config = FastMCPConfig(entrypoint="../src/server.py")
|
||||
config = FastMCPConfig(source={"path": "../src/server.py"})
|
||||
|
||||
# Get entrypoint resolved relative to config location
|
||||
config_file = config_dir / "fastmcp.json"
|
||||
entrypoint = config.get_entrypoint(config_file)
|
||||
|
||||
# Should resolve to absolute path of server file
|
||||
assert Path(entrypoint.file) == server_file.resolve()
|
||||
# The source path is resolved during load_server
|
||||
# For now, just check that the source is created correctly
|
||||
assert config.source.path == "../src/server.py"
|
||||
|
||||
def test_cwd_path_resolution(self, tmp_path):
|
||||
"""Test that working directory is resolved relative to config."""
|
||||
|
|
@ -264,7 +199,7 @@ class TestPathResolution:
|
|||
work_dir.mkdir()
|
||||
|
||||
config = FastMCPConfig(
|
||||
entrypoint="server.py",
|
||||
source={"path": "server.py"},
|
||||
deployment={"cwd": "work"}, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
|
|
@ -288,7 +223,7 @@ class TestPathResolution:
|
|||
reqs_file.write_text("fastmcp>=2.0")
|
||||
|
||||
config = FastMCPConfig(
|
||||
entrypoint="server.py",
|
||||
source={"path": "server.py"},
|
||||
environment={"requirements": "requirements.txt"}, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
|
|
@ -309,7 +244,7 @@ class TestConfigValidation:
|
|||
"""Test that invalid transport values are rejected."""
|
||||
with pytest.raises(ValueError):
|
||||
FastMCPConfig(
|
||||
entrypoint="server.py",
|
||||
source={"path": "server.py"},
|
||||
deployment={"transport": "invalid_transport"}, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
|
|
@ -317,7 +252,7 @@ class TestConfigValidation:
|
|||
"""Test that streamable-http transport is rejected in fastmcp.json config."""
|
||||
with pytest.raises(ValueError):
|
||||
FastMCPConfig(
|
||||
entrypoint="server.py",
|
||||
source={"path": "server.py"},
|
||||
deployment={"transport": "streamable-http"}, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
|
|
@ -325,12 +260,12 @@ class TestConfigValidation:
|
|||
"""Test that invalid log level values are rejected."""
|
||||
with pytest.raises(ValueError):
|
||||
FastMCPConfig(
|
||||
entrypoint="server.py",
|
||||
source={"path": "server.py"},
|
||||
deployment={"log_level": "INVALID"}, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
def test_missing_entrypoint_rejected(self):
|
||||
"""Test that config without entrypoint is rejected."""
|
||||
def test_missing_source_rejected(self):
|
||||
"""Test that config without source is rejected."""
|
||||
with pytest.raises(ValueError):
|
||||
FastMCPConfig() # type: ignore[call-arg]
|
||||
|
||||
|
|
@ -338,7 +273,7 @@ class TestConfigValidation:
|
|||
"""Test that all valid transport values are accepted."""
|
||||
for transport in ["stdio", "http", "sse"]:
|
||||
config = FastMCPConfig(
|
||||
entrypoint="server.py",
|
||||
source={"path": "server.py"},
|
||||
deployment={"transport": transport}, # type: ignore[arg-type]
|
||||
)
|
||||
assert config.deployment is not None
|
||||
|
|
@ -348,7 +283,7 @@ class TestConfigValidation:
|
|||
"""Test that all valid log levels are accepted."""
|
||||
for level in ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]:
|
||||
config = FastMCPConfig(
|
||||
entrypoint="server.py",
|
||||
source={"path": "server.py"},
|
||||
deployment={"log_level": level}, # type: ignore[arg-type]
|
||||
)
|
||||
assert config.deployment is not None
|
||||
|
|
|
|||
|
|
@ -40,10 +40,11 @@ def test_schema_has_correct_id():
|
|||
"""Test that the schema has the correct $id field."""
|
||||
generated_schema = generate_schema()
|
||||
|
||||
assert generated_schema is not None
|
||||
assert "$id" in generated_schema
|
||||
assert (
|
||||
generated_schema["$id"]
|
||||
== "https://gofastmcp.com/schemas/fastmcp_config/v1.json"
|
||||
== "https://gofastmcp.com/public/schemas/fastmcp.json/v1.json"
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -51,19 +52,21 @@ def test_schema_has_required_fields():
|
|||
"""Test that the schema specifies the required fields correctly."""
|
||||
generated_schema = generate_schema()
|
||||
|
||||
# Check that entrypoint is required
|
||||
assert generated_schema is not None
|
||||
# Check that source is required
|
||||
assert "required" in generated_schema
|
||||
assert "entrypoint" in generated_schema["required"]
|
||||
assert "source" in generated_schema["required"]
|
||||
|
||||
# Check that entrypoint is in properties
|
||||
# Check that source is in properties
|
||||
assert "properties" in generated_schema
|
||||
assert "entrypoint" in generated_schema["properties"]
|
||||
assert "source" in generated_schema["properties"]
|
||||
|
||||
|
||||
def test_schema_nested_structure():
|
||||
"""Test that the schema has the correct nested structure."""
|
||||
generated_schema = generate_schema()
|
||||
|
||||
assert generated_schema is not None
|
||||
properties = generated_schema["properties"]
|
||||
|
||||
# Check environment section
|
||||
|
|
@ -95,6 +98,7 @@ def test_schema_transport_enum():
|
|||
"""Test that transport field has correct enum values."""
|
||||
generated_schema = generate_schema()
|
||||
|
||||
assert generated_schema is not None
|
||||
# Navigate to transport field
|
||||
deploy_schema = generated_schema["properties"]["deployment"]
|
||||
|
||||
|
|
@ -129,6 +133,7 @@ def test_schema_log_level_enum():
|
|||
"""Test that log_level field has correct enum values."""
|
||||
generated_schema = generate_schema()
|
||||
|
||||
assert generated_schema is not None
|
||||
# Navigate to log_level field
|
||||
deploy_schema = generated_schema["properties"]["deployment"]
|
||||
|
||||
|
|
|
|||
|
|
@ -7,14 +7,13 @@ from pydantic import ValidationError
|
|||
|
||||
from fastmcp.cli.run import (
|
||||
create_mcp_config_server,
|
||||
import_server,
|
||||
is_url,
|
||||
parse_file_path,
|
||||
)
|
||||
from fastmcp.client.client import Client
|
||||
from fastmcp.client.transports import FastMCPTransport
|
||||
from fastmcp.mcp_config import MCPConfig, StdioMCPServer
|
||||
from fastmcp.server.server import FastMCP
|
||||
from fastmcp.utilities.fastmcp_config.v1.sources.filesystem import FileSystemSource
|
||||
|
||||
|
||||
class TestUrlDetection:
|
||||
|
|
@ -41,52 +40,54 @@ class TestUrlDetection:
|
|||
assert not is_url("file:///path/to/file")
|
||||
|
||||
|
||||
class TestFilePathParsing:
|
||||
"""Test file path parsing functionality."""
|
||||
class TestFileSystemSource:
|
||||
"""Test FileSystemSource path parsing functionality."""
|
||||
|
||||
def test_parse_file_path_simple(self, tmp_path):
|
||||
def test_parse_simple_path(self, tmp_path):
|
||||
"""Test parsing simple file path without object."""
|
||||
test_file = tmp_path / "server.py"
|
||||
test_file.write_text("# test server")
|
||||
|
||||
file_path, server_object = parse_file_path(str(test_file))
|
||||
assert file_path == test_file.resolve()
|
||||
assert server_object is None
|
||||
source = FileSystemSource(path=str(test_file))
|
||||
assert Path(source.path).resolve() == test_file.resolve()
|
||||
assert source.entrypoint is None
|
||||
|
||||
def test_parse_file_path_with_object(self, tmp_path):
|
||||
def test_parse_path_with_object(self, tmp_path):
|
||||
"""Test parsing file path with object specification."""
|
||||
test_file = tmp_path / "server.py"
|
||||
test_file.write_text("# test server")
|
||||
|
||||
file_path, server_object = parse_file_path(f"{test_file}:app")
|
||||
assert file_path == test_file.resolve()
|
||||
assert server_object == "app"
|
||||
source = FileSystemSource(path=f"{test_file}:app")
|
||||
assert Path(source.path).resolve() == test_file.resolve()
|
||||
assert source.entrypoint == "app"
|
||||
|
||||
def test_parse_file_path_complex_object(self, tmp_path):
|
||||
def test_parse_complex_object(self, tmp_path):
|
||||
"""Test parsing file path with complex object specification."""
|
||||
test_file = tmp_path / "server.py"
|
||||
test_file.write_text("# test server")
|
||||
|
||||
# The current implementation splits on the last colon, so file:module:app
|
||||
# becomes file_path="file:module" and server_object="app"
|
||||
# The implementation splits on the last colon, so file:module:app
|
||||
# becomes file_path="file:module" and entrypoint="app"
|
||||
# We need to create a file with a colon in the name for this test
|
||||
complex_file = tmp_path / "server:module.py"
|
||||
complex_file.write_text("# test server")
|
||||
|
||||
file_path, server_object = parse_file_path(f"{complex_file}:app")
|
||||
assert file_path == complex_file.resolve()
|
||||
assert server_object == "app"
|
||||
source = FileSystemSource(path=f"{complex_file}:app")
|
||||
assert Path(source.path).resolve() == complex_file.resolve()
|
||||
assert source.entrypoint == "app"
|
||||
|
||||
def test_parse_file_path_nonexistent(self):
|
||||
"""Test parsing nonexistent file path exits."""
|
||||
async def test_load_server_nonexistent(self):
|
||||
"""Test loading nonexistent file path exits."""
|
||||
source = FileSystemSource(path="nonexistent.py")
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
parse_file_path("nonexistent.py")
|
||||
await source.load_server()
|
||||
assert exc_info.value.code == 1
|
||||
|
||||
def test_parse_file_path_directory(self, tmp_path):
|
||||
"""Test parsing directory path exits."""
|
||||
async def test_load_server_directory(self, tmp_path):
|
||||
"""Test loading directory path exits."""
|
||||
source = FileSystemSource(path=str(tmp_path))
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
parse_file_path(str(tmp_path))
|
||||
await source.load_server()
|
||||
assert exc_info.value.code == 1
|
||||
|
||||
|
||||
|
|
@ -157,7 +158,8 @@ def greet(name: str) -> str:
|
|||
return f"Hello, {name}!"
|
||||
""")
|
||||
|
||||
server = await import_server(test_file)
|
||||
source = FileSystemSource(path=str(test_file))
|
||||
server = await source.load_server()
|
||||
assert server.name == "TestServer"
|
||||
tools = await server.get_tools()
|
||||
assert "greet" in tools
|
||||
|
|
@ -178,7 +180,8 @@ if __name__ == "__main__":
|
|||
app.run()
|
||||
""")
|
||||
|
||||
server = await import_server(test_file)
|
||||
source = FileSystemSource(path=str(test_file))
|
||||
server = await source.load_server()
|
||||
assert server.name == "MainServer"
|
||||
tools = await server.get_tools()
|
||||
assert "calculate" in tools
|
||||
|
|
@ -192,7 +195,8 @@ import fastmcp
|
|||
mcp = fastmcp.FastMCP("MCPServer")
|
||||
""")
|
||||
|
||||
server = await import_server(mcp_file)
|
||||
source = FileSystemSource(path=str(mcp_file))
|
||||
server = await source.load_server()
|
||||
assert server.name == "MCPServer"
|
||||
|
||||
# Test with 'server' name
|
||||
|
|
@ -202,7 +206,8 @@ import fastmcp
|
|||
server = fastmcp.FastMCP("ServerServer")
|
||||
""")
|
||||
|
||||
server = await import_server(server_file)
|
||||
source = FileSystemSource(path=str(server_file))
|
||||
server = await source.load_server()
|
||||
assert server.name == "ServerServer"
|
||||
|
||||
# Test with 'app' name
|
||||
|
|
@ -212,7 +217,8 @@ import fastmcp
|
|||
app = fastmcp.FastMCP("AppServer")
|
||||
""")
|
||||
|
||||
server = await import_server(app_file)
|
||||
source = FileSystemSource(path=str(app_file))
|
||||
server = await source.load_server()
|
||||
assert server.name == "AppServer"
|
||||
|
||||
async def test_import_server_nonstandard_name(self, tmp_path):
|
||||
|
|
@ -228,7 +234,8 @@ def custom_tool() -> str:
|
|||
return "custom"
|
||||
""")
|
||||
|
||||
server = await import_server(test_file, "my_custom_server")
|
||||
source = FileSystemSource(path=f"{test_file}:my_custom_server")
|
||||
server = await source.load_server()
|
||||
assert server.name == "CustomServer"
|
||||
tools = await server.get_tools()
|
||||
assert "custom_tool" in tools
|
||||
|
|
@ -242,8 +249,9 @@ import fastmcp
|
|||
other_name = fastmcp.FastMCP("OtherServer")
|
||||
""")
|
||||
|
||||
source = FileSystemSource(path=str(test_file))
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
await import_server(test_file)
|
||||
await source.load_server()
|
||||
assert exc_info.value.code == 1
|
||||
|
||||
async def test_import_server_nonexistent_object_fails(self, tmp_path):
|
||||
|
|
@ -255,6 +263,131 @@ import fastmcp
|
|||
mcp = fastmcp.FastMCP("TestServer")
|
||||
""")
|
||||
|
||||
source = FileSystemSource(path=f"{test_file}:nonexistent")
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
await import_server(test_file, "nonexistent")
|
||||
await source.load_server()
|
||||
assert exc_info.value.code == 1
|
||||
|
||||
|
||||
class TestSkipSource:
|
||||
"""Test the --skip-source functionality."""
|
||||
|
||||
async def test_run_command_calls_prepare_by_default(self, tmp_path):
|
||||
"""Test that run_command calls source.prepare() by default."""
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from fastmcp.cli.run import run_command
|
||||
|
||||
# Create a test server file
|
||||
test_file = tmp_path / "server.py"
|
||||
test_file.write_text("""
|
||||
import fastmcp
|
||||
mcp = fastmcp.FastMCP("TestServer")
|
||||
""")
|
||||
|
||||
# Create a test config file
|
||||
config_file = tmp_path / "fastmcp.json"
|
||||
config_data = {"source": {"path": str(test_file), "entrypoint": "mcp"}}
|
||||
config_file.write_text(json.dumps(config_data))
|
||||
|
||||
# Mock the prepare method and server run
|
||||
with (
|
||||
patch.object(
|
||||
FileSystemSource, "prepare", new_callable=AsyncMock
|
||||
) as prepare_mock,
|
||||
patch("fastmcp.server.server.FastMCP.run_async", new_callable=AsyncMock),
|
||||
):
|
||||
# Run the command
|
||||
await run_command(str(config_file))
|
||||
|
||||
# Verify prepare was called
|
||||
prepare_mock.assert_called_once()
|
||||
|
||||
async def test_run_command_skips_prepare_with_flag(self, tmp_path):
|
||||
"""Test that run_command skips source.prepare() when skip_source=True."""
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from fastmcp.cli.run import run_command
|
||||
|
||||
# Create a test server file
|
||||
test_file = tmp_path / "server.py"
|
||||
test_file.write_text("""
|
||||
import fastmcp
|
||||
mcp = fastmcp.FastMCP("TestServer")
|
||||
""")
|
||||
|
||||
# Create a test config file
|
||||
config_file = tmp_path / "fastmcp.json"
|
||||
config_data = {"source": {"path": str(test_file), "entrypoint": "mcp"}}
|
||||
config_file.write_text(json.dumps(config_data))
|
||||
|
||||
# Mock the prepare method and server run
|
||||
with (
|
||||
patch.object(
|
||||
FileSystemSource, "prepare", new_callable=AsyncMock
|
||||
) as prepare_mock,
|
||||
patch("fastmcp.server.server.FastMCP.run_async", new_callable=AsyncMock),
|
||||
):
|
||||
# Run the command with skip_source=True
|
||||
await run_command(str(config_file), skip_source=True)
|
||||
|
||||
# Verify prepare was NOT called
|
||||
prepare_mock.assert_not_called()
|
||||
|
||||
async def test_filesystem_source_prepare_by_default(self, tmp_path):
|
||||
"""Test that FileSystemSource is prepared when using direct file spec."""
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from fastmcp.cli.run import run_command
|
||||
from fastmcp.utilities.fastmcp_config.v1.sources.filesystem import (
|
||||
FileSystemSource,
|
||||
)
|
||||
|
||||
# Create a test server file
|
||||
test_file = tmp_path / "server.py"
|
||||
test_file.write_text("""
|
||||
import fastmcp
|
||||
mcp = fastmcp.FastMCP("TestServer")
|
||||
""")
|
||||
|
||||
# Mock the prepare method and server run
|
||||
with (
|
||||
patch.object(
|
||||
FileSystemSource, "prepare", new_callable=AsyncMock
|
||||
) as prepare_mock,
|
||||
patch("fastmcp.server.server.FastMCP.run_async", new_callable=AsyncMock),
|
||||
):
|
||||
# Run with direct file specification
|
||||
await run_command(str(test_file))
|
||||
|
||||
# Verify prepare was called
|
||||
prepare_mock.assert_called_once()
|
||||
|
||||
async def test_filesystem_source_skip_prepare_with_flag(self, tmp_path):
|
||||
"""Test that FileSystemSource.prepare() is skipped with skip_source flag."""
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from fastmcp.cli.run import run_command
|
||||
from fastmcp.utilities.fastmcp_config.v1.sources.filesystem import (
|
||||
FileSystemSource,
|
||||
)
|
||||
|
||||
# Create a test server file
|
||||
test_file = tmp_path / "server.py"
|
||||
test_file.write_text("""
|
||||
import fastmcp
|
||||
mcp = fastmcp.FastMCP("TestServer")
|
||||
""")
|
||||
|
||||
# Mock the prepare method and server run
|
||||
with (
|
||||
patch.object(
|
||||
FileSystemSource, "prepare", new_callable=AsyncMock
|
||||
) as prepare_mock,
|
||||
patch("fastmcp.server.server.FastMCP.run_async", new_callable=AsyncMock),
|
||||
):
|
||||
# Run with direct file specification and skip_source=True
|
||||
await run_command(str(test_file), skip_source=True)
|
||||
|
||||
# Verify prepare was NOT called
|
||||
prepare_mock.assert_not_called()
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue