mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-09 07:09:11 +02:00
Add development docs for FastMCP (#1719)
Co-authored-by: Jeremiah Lowin <jlowin@users.noreply.github.com> Co-authored-by: marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com>
This commit is contained in:
parent
a3940985c7
commit
c5c6a3ee06
12 changed files with 847 additions and 407 deletions
14
AGENTS.md
14
AGENTS.md
|
|
@ -48,6 +48,15 @@ When modifying MCP functionality, changes typically need to be applied across al
|
|||
- **Resource Templates** (`src/resources/` + `ResourceManager`)
|
||||
- **Prompts** (`src/prompts/` + `PromptManager`)
|
||||
|
||||
## Writing Style
|
||||
|
||||
- Be brief and to the point. Do not regurgitate information that can easily be gleaned from the code, except to guide the reader to where the code is located.
|
||||
- **NEVER** use "This isn't..." or "not just..." constructions. State what something IS directly. Avoid defensive writing patterns like:
|
||||
- "This isn't X, it's Y" or "Not just X, but Y" → Just say "This is Y"
|
||||
- "Not just about X" → State the actual purpose
|
||||
- "We're not doing X, we're doing Y" → Just explain what you're doing
|
||||
- Any variation of explaining what something isn't before what it is
|
||||
|
||||
## Testing Best Practices
|
||||
|
||||
### Testing Standards
|
||||
|
|
@ -59,6 +68,10 @@ When modifying MCP functionality, changes typically need to be applied across al
|
|||
- **NEVER** add `@pytest.mark.asyncio` to tests - `asyncio_mode = "auto"` is set globally
|
||||
- **ALWAYS** run pytest after significant changes
|
||||
|
||||
### Inline Snapshots
|
||||
|
||||
FastMCP uses `inline-snapshot` for testing complex data structures. On first run with empty `snapshot()`, pytest will auto-populate the expected value. To update snapshots after intentional changes, run `pytest --inline-snapshot=fix`. This is particularly useful for testing JSON schemas and API responses.
|
||||
|
||||
### Always Use In-Memory Transport
|
||||
|
||||
Pass FastMCP servers directly to clients for testing:
|
||||
|
|
@ -203,6 +216,7 @@ If something needs work, your review should help it get there through specific,
|
|||
### Review Checklist
|
||||
|
||||
Before approving, verify:
|
||||
|
||||
- [ ] All required development workflow steps completed (uv sync, pre-commit, pytest)
|
||||
- [ ] Changes align with repository patterns and conventions
|
||||
- [ ] API changes are documented and backwards-compatible where possible
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ MCP_AUTH_TOKEN=secret uvicorn app:app --host 0.0.0.0 --port 8000
|
|||
|
||||
## Testing Your Deployment
|
||||
|
||||
Once your server is deployed, you'll need to verify it's accessible and functioning correctly. For comprehensive testing strategies including connectivity tests, client testing, and authentication testing, see the [Testing Your Server](/deployment/testing) guide.
|
||||
Once your server is deployed, you'll need to verify it's accessible and functioning correctly. For comprehensive testing strategies including connectivity tests, client testing, and authentication testing, see the [Testing Your Server](/development/tests) guide.
|
||||
|
||||
## Hosting Your Server
|
||||
|
||||
|
|
|
|||
|
|
@ -1,163 +0,0 @@
|
|||
---
|
||||
title: Testing Your Server
|
||||
sidebarTitle: Testing
|
||||
description: Unit test your MCP servers with the FastMCP Client's deterministic testing capabilities
|
||||
icon: vial
|
||||
---
|
||||
|
||||
The [FastMCP Client](/clients/client) is a deterministic testing tool that gives you complete programmatic control over MCP server interactions. You call specific tools with exact arguments, verify responses, and test edge cases - making it ideal for unit testing your MCP servers.
|
||||
|
||||
## In-Memory Testing
|
||||
|
||||
The FastMCP Client's standout feature is in-memory testing. Instead of deploying your server or managing network connections, you pass your server instance directly to the client. This creates a zero-overhead connection that runs entirely in memory.
|
||||
|
||||
What makes this approach so powerful is that everything runs in the same Python process. You can set breakpoints anywhere - in your test code or inside your server handlers - and step through with your debugger. There's no server startup scripts, no port management, no cleanup between tests. Tests execute instantly without network overhead.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP, Client
|
||||
|
||||
# Create your server
|
||||
server = FastMCP("WeatherServer")
|
||||
|
||||
@server.tool
|
||||
def get_temperature(city: str) -> dict:
|
||||
"""Get current temperature for a city"""
|
||||
temps = {"NYC": 72, "LA": 85, "Chicago": 68}
|
||||
return {"city": city, "temp": temps.get(city, 70)}
|
||||
|
||||
@server.resource("weather://forecast")
|
||||
def get_forecast() -> dict:
|
||||
"""Get 5-day forecast"""
|
||||
return {"days": 5, "conditions": "sunny"}
|
||||
|
||||
async def test_weather_operations():
|
||||
# Pass server directly - no deployment needed
|
||||
async with Client(server) as client:
|
||||
# Test tool execution
|
||||
result = await client.call_tool("get_temperature", {"city": "NYC"})
|
||||
assert result.data == {"city": "NYC", "temp": 72}
|
||||
|
||||
# Test resource retrieval
|
||||
forecast = await client.read_resource("weather://forecast")
|
||||
assert forecast.contents[0].data == {"days": 5, "conditions": "sunny"}
|
||||
```
|
||||
|
||||
The in-memory approach transforms MCP testing from a deployment challenge into standard unit testing. You focus on testing your server's behavior, not wrestling with infrastructure.
|
||||
|
||||
## Testing with Frameworks
|
||||
|
||||
The FastMCP Client works seamlessly with any Python testing framework. Whether you prefer pytest, unittest, or another framework, the pattern remains consistent: create a server, pass it to the client, and verify behavior.
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from fastmcp import FastMCP, Client
|
||||
|
||||
@pytest.fixture
|
||||
def weather_server():
|
||||
server = FastMCP("WeatherServer")
|
||||
|
||||
@server.tool
|
||||
def get_temperature(city: str) -> dict:
|
||||
temps = {"NYC": 72, "LA": 85, "Chicago": 68}
|
||||
return {"city": city, "temp": temps.get(city, 70)}
|
||||
|
||||
return server
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_temperature_tool(weather_server):
|
||||
async with Client(weather_server) as client:
|
||||
result = await client.call_tool("get_temperature", {"city": "LA"})
|
||||
assert result.data == {"city": "LA", "temp": 85}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unknown_city(weather_server):
|
||||
async with Client(weather_server) as client:
|
||||
result = await client.call_tool("get_temperature", {"city": "Paris"})
|
||||
assert result.data["temp"] == 70 # Default temperature
|
||||
```
|
||||
|
||||
## Mocking External Dependencies
|
||||
|
||||
FastMCP servers are standard Python objects, so you can mock external dependencies using your preferred mocking approach. Replace databases, APIs, or any external service with test doubles to keep your tests fast and deterministic.
|
||||
|
||||
```python
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
async def test_database_tool():
|
||||
server = FastMCP("DataServer")
|
||||
|
||||
# Mock the database
|
||||
mock_db = AsyncMock()
|
||||
mock_db.fetch_users.return_value = [
|
||||
{"id": 1, "name": "Alice"},
|
||||
{"id": 2, "name": "Bob"}
|
||||
]
|
||||
|
||||
@server.tool
|
||||
async def list_users() -> list:
|
||||
return await mock_db.fetch_users()
|
||||
|
||||
async with Client(server) as client:
|
||||
result = await client.call_tool("list_users", {})
|
||||
assert len(result.data) == 2
|
||||
assert result.data[0]["name"] == "Alice"
|
||||
mock_db.fetch_users.assert_called_once()
|
||||
```
|
||||
|
||||
## Testing Deployed Servers
|
||||
|
||||
While in-memory testing covers most unit testing needs, you'll occasionally need to test against a deployed server - to verify authentication, test network behavior, or validate deployments.
|
||||
|
||||
### HTTP Transport Testing
|
||||
|
||||
When you need to test actual network behavior or verify a deployment, connect to your running server using its URL:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
|
||||
async def test_deployed_server():
|
||||
# Connect to a running server
|
||||
async with Client("http://localhost:8000/mcp/") as client:
|
||||
await client.ping()
|
||||
|
||||
# Test with real network transport
|
||||
tools = await client.list_tools()
|
||||
assert len(tools) > 0
|
||||
|
||||
result = await client.call_tool("greet", {"name": "World"})
|
||||
assert "Hello" in result.data
|
||||
```
|
||||
|
||||
### Testing Authentication
|
||||
|
||||
The FastMCP Client handles authentication transparently, making it easy to test secured servers:
|
||||
|
||||
```python
|
||||
from fastmcp.client.transports import StreamableHttpTransport
|
||||
|
||||
async def test_authenticated_server():
|
||||
# Bearer token authentication
|
||||
async with Client(
|
||||
StreamableHttpTransport(
|
||||
"https://api.example.com/mcp",
|
||||
headers={"Authorization": "Bearer test-token"}
|
||||
)
|
||||
) as client:
|
||||
await client.ping()
|
||||
tools = await client.list_tools()
|
||||
|
||||
# OAuth flow (opens browser for authorization)
|
||||
async with Client("https://api.example.com/mcp", auth="oauth") as client:
|
||||
result = await client.call_tool("protected_tool", {})
|
||||
assert result.data is not None
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Default to in-memory testing** - It's faster, more reliable, and easier to debug
|
||||
2. **Test behavior, not implementation** - Call tools and verify responses rather than testing internals
|
||||
3. **Use framework fixtures** - Create reusable server configurations for your test suite
|
||||
4. **Mock external dependencies** - Keep tests fast and deterministic by mocking databases, APIs, etc.
|
||||
5. **Test error cases** - Verify your server handles invalid inputs and edge cases properly
|
||||
|
||||
The FastMCP Client transforms MCP server testing from a deployment challenge into a straightforward unit testing task. With in-memory connections and deterministic control, you can build comprehensive test suites that run in milliseconds.
|
||||
187
docs/development/contributing.mdx
Normal file
187
docs/development/contributing.mdx
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
---
|
||||
title: "Contributing"
|
||||
description: "Development workflow for FastMCP contributors"
|
||||
icon: code-pull-request
|
||||
---
|
||||
|
||||
Contributing to FastMCP means joining a community that values clean, maintainable code and thoughtful API design. All contributions are valued - from fixing typos in documentation to implementing major features.
|
||||
|
||||
## Issues
|
||||
|
||||
### Issue First, Code Second
|
||||
|
||||
**Every pull request requires a corresponding issue - no exceptions.** This requirement creates a collaborative space where approach, scope, and alignment are established before code is written. Issues serve as design documents where maintainers and contributors discuss implementation strategy, identify potential conflicts with existing patterns, and ensure proposed changes advance FastMCP's vision.
|
||||
|
||||
**FastMCP is an opinionated framework, not a kitchen sink.** The maintainers have strong beliefs about what FastMCP should and shouldn't do. Just because something takes N lines of code and you want it in fewer lines doesn't mean FastMCP should take on the maintenance burden or endorse that pattern. This is judged at the maintainers' discretion.
|
||||
|
||||
Use issues to understand scope BEFORE opening PRs. The issue discussion determines whether a feature belongs in core, contrib, or not at all.
|
||||
|
||||
### Writing Good Issues
|
||||
|
||||
FastMCP is an extremely highly-trafficked repository maintained by a very small team. Issues that appear to transfer burden to maintainers without any effort to validate the problem will be closed. Please help the maintainers help you by always providing a minimal reproducible example and clearly describing the problem.
|
||||
|
||||
**LLM-generated issues will be closed immediately.** Issues that contain paragraphs of unnecessary explanation, verbose problem descriptions, or obvious LLM authorship patterns obfuscate the actual problem and transfer burden to maintainers.
|
||||
|
||||
Write clear, concise issues that:
|
||||
- State the problem directly
|
||||
- Provide a minimal reproducible example
|
||||
- Skip unnecessary background or context
|
||||
- Take responsibility for clear communication
|
||||
|
||||
Issues may be labeled "Invalid" simply due to confusion caused by verbosity or not adhering to the guidelines outlined here.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
PRs that deviate from FastMCP's core principles will be rejected regardless of implementation quality. **PRs are NOT for iterating on ideas** - they should only be opened for ideas that already have a bias toward acceptance based on issue discussion.
|
||||
|
||||
|
||||
### Development Environment
|
||||
|
||||
#### Installation
|
||||
|
||||
To contribute to FastMCP, you'll need to set up a development environment with all necessary tools and dependencies.
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/jlowin/fastmcp.git
|
||||
cd fastmcp
|
||||
|
||||
# Install all dependencies including dev tools
|
||||
uv sync
|
||||
|
||||
# Install pre-commit hooks
|
||||
uv run pre-commit install
|
||||
```
|
||||
|
||||
In addition, some development commands require [just](https://github.com/casey/just) to be installed.
|
||||
|
||||
Pre-commit hooks will run automatically on every commit to catch issues before they reach CI. If you see failures, fix them before committing - never commit broken code expecting to fix it later.
|
||||
|
||||
### Development Standards
|
||||
|
||||
#### Scope
|
||||
|
||||
Large pull requests create review bottlenecks and quality risks. Unless you're fixing a discrete bug or making an incredibly well-scoped change, keep PRs small and focused.
|
||||
|
||||
A PR that changes 50 lines across 3 files can be thoroughly reviewed in minutes. A PR that changes 500 lines across 20 files requires hours of careful analysis and often hides subtle issues.
|
||||
|
||||
Breaking large features into smaller PRs:
|
||||
- Creates better review experiences
|
||||
- Makes git history clear
|
||||
- Simplifies debugging with bisect
|
||||
- Reduces merge conflicts
|
||||
- Gets your code merged faster
|
||||
|
||||
#### Code Quality
|
||||
|
||||
FastMCP values clarity over cleverness. Every line you write will be maintained by someone else - possibly years from now, possibly without context about your decisions.
|
||||
|
||||
**PRs can be rejected for two opposing reasons:**
|
||||
1. **Insufficient quality** - Code that doesn't meet our standards for clarity, maintainability, or idiomaticity
|
||||
2. **Overengineering** - Code that is overbearing, unnecessarily complex, or tries to be too clever
|
||||
|
||||
The focus is on idiomatic, high-quality Python. FastMCP uses patterns like `NotSet` type as an alternative to `None` in certain situations - follow existing patterns.
|
||||
|
||||
#### Required Practices
|
||||
|
||||
**Full type annotations** on all functions and methods. They catch bugs before runtime and serve as inline documentation.
|
||||
|
||||
**Async/await patterns** for all I/O operations. Even if your specific use case doesn't need concurrency, consistency means users can compose features without worrying about blocking operations.
|
||||
|
||||
**Descriptive names** make code self-documenting. `auth_token` is clear; `tok` requires mental translation.
|
||||
|
||||
**Specific exception types** make error handling predictable. Catching `ValueError` tells readers exactly what error you expect. Never use bare `except` clauses.
|
||||
|
||||
#### Anti-Patterns to Avoid
|
||||
|
||||
**Complex one-liners** are hard to debug and modify. Break operations into clear steps.
|
||||
|
||||
**Mutable default arguments** cause subtle bugs. Use `None` as the default and create the mutable object inside the function.
|
||||
|
||||
**Breaking established patterns** confuses readers. If you must deviate, discuss in the issue first.
|
||||
|
||||
### Pre-Commit Checks
|
||||
|
||||
```bash
|
||||
# Runs automatically on commit, or manually:
|
||||
uv run pre-commit run --all-files
|
||||
```
|
||||
|
||||
This runs three critical tools:
|
||||
- **Ruff**: Linting and formatting
|
||||
- **ty**: Static type checking
|
||||
- **Pytest**: Core test suite
|
||||
|
||||
CI will reject PRs that fail these checks. Always run them locally first.
|
||||
|
||||
### Testing
|
||||
|
||||
Tests are documentation that shows how features work. Good tests give reviewers confidence and help future maintainers understand intent.
|
||||
|
||||
```bash
|
||||
# Run specific test directory
|
||||
uv run pytest tests/server/ -v
|
||||
|
||||
# Run all tests before submitting PR
|
||||
uv run pytest
|
||||
```
|
||||
|
||||
Every new feature needs tests. See the [Testing Guide](/development/tests) for patterns and requirements.
|
||||
|
||||
### Documentation
|
||||
|
||||
A feature doesn't exist unless it's documented. Note that FastMCP's hosted documentation always tracks the main branch - users who want historical documentation can clone the repo, checkout a specific tag, and host it themselves.
|
||||
|
||||
```bash
|
||||
# Preview documentation locally
|
||||
just docs
|
||||
```
|
||||
|
||||
Documentation requirements:
|
||||
- **Explain concepts in prose first** - Code without context is just syntax
|
||||
- **Complete, runnable examples** - Every code block should be copy-pasteable
|
||||
- **Register in docs.json** - Makes pages appear in navigation
|
||||
- **Version badges** - Mark when features were added using `<VersionBadge />`
|
||||
|
||||
#### SDK Documentation
|
||||
|
||||
FastMCP's SDK documentation is auto-generated from the source code docstrings and type annotations. It is automatically updated on every merge to main by a GitHub Actions workflow, so users are *not* responsible for keeping the documentation up to date. However, to generate it proactively, you can use the following command:
|
||||
|
||||
```bash
|
||||
just api-ref-all
|
||||
```
|
||||
|
||||
### Submitting Your PR
|
||||
|
||||
#### Before Submitting
|
||||
|
||||
1. **Run all checks**: `uv run pre-commit run --all-files && uv run pytest`
|
||||
2. **Keep scope small**: One feature or fix per PR
|
||||
3. **Write clear description**: Your PR description becomes permanent documentation
|
||||
4. **Update docs**: Include documentation for API changes
|
||||
|
||||
#### PR Description
|
||||
|
||||
Write PR descriptions that explain:
|
||||
- What problem you're solving
|
||||
- Why you chose this approach
|
||||
- Any trade-offs or alternatives considered
|
||||
- Migration path for breaking changes
|
||||
|
||||
Focus on the "why" - the code shows the "what". Keep it concise but complete.
|
||||
|
||||
#### What We Look For
|
||||
|
||||
**Framework Philosophy**: FastMCP is NOT trying to do all things or provide all shortcuts. Features are rejected when they don't align with the framework's vision, even if perfectly implemented. The burden of proof is on the PR to demonstrate value.
|
||||
|
||||
**Code Quality**: We verify code follows existing patterns. Consistency reduces cognitive load. When every module works similarly, developers understand new code quickly.
|
||||
|
||||
**Test Coverage**: Not every line needs testing, but every behavior does. Tests document intent and protect against regressions.
|
||||
|
||||
**Breaking Changes**: May be acceptable in minor versions but must be clearly documented. See the [versioning policy](/development/releases#versioning-policy).
|
||||
|
||||
## Special Modules
|
||||
|
||||
**`contrib`**: Community-maintained patterns and utilities. Original authors maintain their contributions. Not representative of the core framework.
|
||||
|
||||
**`experimental`**: Maintainer-developed features that may preview future functionality. Can break or be deleted at any time without notice. Pin your FastMCP version when using these features.
|
||||
79
docs/development/releases.mdx
Normal file
79
docs/development/releases.mdx
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
title: "Releases"
|
||||
description: "FastMCP versioning and release process"
|
||||
icon: "truck-fast"
|
||||
---
|
||||
|
||||
FastMCP releases frequently to deliver features quickly in the rapidly evolving MCP ecosystem. We use semantic versioning pragmatically - the Model Context Protocol is young, patterns are still emerging, and waiting for perfect stability would mean missing opportunities to empower developers with better tools.
|
||||
|
||||
## Versioning Policy
|
||||
|
||||
### Semantic Versioning
|
||||
|
||||
**Major (x.0.0)**: Complete API redesigns
|
||||
|
||||
Major versions represent fundamental shifts. FastMCP 2.x is entirely different from 1.x in both implementation and design philosophy.
|
||||
|
||||
**Minor (2.x.0)**: New features and evolution
|
||||
|
||||
<Warning>
|
||||
Unlike traditional semantic versioning, minor versions **may** include [breaking changes](#breaking-changes) when necessary for the ecosystem's evolution. This flexibility is essential in a young ecosystem where perfect backwards compatibility would prevent important improvements.
|
||||
</Warning>
|
||||
|
||||
FastMCP always targets the most current MCP Protocol version. Breaking changes in the MCP spec or MCP SDK automatically flow through to FastMCP - we prioritize staying current with the latest features and conventions over maintaining compatibility with older protocol versions.
|
||||
|
||||
**Patch (2.0.x)**: Bug fixes and refinements
|
||||
|
||||
Patch versions contain only bug fixes without breaking changes. These are safe updates you can apply with confidence.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
We permit breaking changes in minor versions because the MCP ecosystem is rapidly evolving. Refusing to break problematic APIs would accumulate design debt that eventually makes the framework unusable. Each breaking change represents a deliberate decision to keep FastMCP aligned with the ecosystem's evolution.
|
||||
|
||||
When breaking changes occur:
|
||||
- They only happen in minor versions (e.g., 2.3.x to 2.4.0)
|
||||
- Release notes explain what changed and how to migrate
|
||||
- We provide deprecation warnings at least 1 minor version in advance when possible
|
||||
- Changes must substantially benefit users to justify disruption
|
||||
|
||||
The public API is what's covered by our compatibility guarantees - these are the parts of FastMCP you can rely on to remain stable within a minor version. The public API consists of:
|
||||
- `FastMCP` server class, `Client` class, and FastMCP `Context`
|
||||
- Core MCP components: `Tool`, `Prompt`, `Resource`, `ResourceTemplate`, and transports
|
||||
- Their public methods and documented behaviors
|
||||
|
||||
Everything else (utilities, private methods, internal modules) may change without notice. This boundary lets us refactor internals and improve implementation details without breaking your code. For production stability, pin to specific versions.
|
||||
|
||||
<Warning>
|
||||
The `fastmcp.server.auth` module was introduced in 2.12.0 and is exempted from this policy temporarily, meaning it is *expected* to have breaking changes even on patch versions. This is because auth is a rapidly evolving part of the MCP spec and it would be dangerous to be beholden to old decisions. Please pin your FastMCP version if using authentication in production.
|
||||
|
||||
We expect this exemption to last through at least the 2.12.x and 2.13.x release series.
|
||||
</Warning>
|
||||
|
||||
### Production Use
|
||||
|
||||
Pin to exact versions:
|
||||
```
|
||||
fastmcp==2.11.0 # Good
|
||||
fastmcp>=2.11.0 # Bad - will install breaking changes
|
||||
```
|
||||
|
||||
## Creating Releases
|
||||
|
||||
Our release process is intentionally simple:
|
||||
|
||||
1. Create GitHub release with tag `vMAJOR.MINOR.PATCH` (e.g., `v2.11.0`)
|
||||
2. Generate release notes automatically, and curate or add additional editorial information as needed
|
||||
3. GitHub releases automatically trigger PyPI deployments
|
||||
|
||||
This automation lets maintainers focus on code quality rather than release mechanics.
|
||||
|
||||
### Release Cadence
|
||||
|
||||
We follow a feature-driven release cadence rather than a fixed schedule. Minor versions ship approximately every 3-4 weeks when significant functionality is ready.
|
||||
|
||||
Patch releases ship promptly for:
|
||||
- Critical bug fixes
|
||||
- Security updates (immediate release)
|
||||
- Regression fixes
|
||||
|
||||
This approach means you get improvements as soon as they're ready rather than waiting for arbitrary release dates.
|
||||
350
docs/development/tests.mdx
Normal file
350
docs/development/tests.mdx
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
---
|
||||
title: "Tests"
|
||||
description: "Testing patterns and requirements for FastMCP"
|
||||
icon: vial
|
||||
---
|
||||
|
||||
Good tests are the foundation of reliable software. In FastMCP, we treat tests as first-class documentation that demonstrates how features work while protecting against regressions. Every new capability needs comprehensive tests that demonstrate correctness.
|
||||
|
||||
## FastMCP Tests
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
uv run pytest
|
||||
|
||||
# Run specific test file
|
||||
uv run pytest tests/server/test_auth.py
|
||||
|
||||
# Run with coverage
|
||||
uv run pytest --cov=fastmcp
|
||||
|
||||
# Skip integration tests for faster runs
|
||||
uv run pytest -m "not integration"
|
||||
|
||||
# Skip tests that spawn processes
|
||||
uv run pytest -m "not integration and not client_process"
|
||||
```
|
||||
|
||||
Tests should complete in under 1 second unless marked as integration tests. This speed encourages running them frequently, catching issues early.
|
||||
|
||||
### Test Organization
|
||||
|
||||
Our test organization mirrors the `src/` directory structure, creating a predictable mapping between code and tests. When you're working on `src/fastmcp/server/auth.py`, you'll find its tests in `tests/server/test_auth.py`. In rare cases tests are split further - for example, the OpenAPI tests are so comprehensive they're split across multiple files.
|
||||
|
||||
### Test Markers
|
||||
|
||||
We use pytest markers to categorize tests that require special resources or take longer to run:
|
||||
|
||||
```python
|
||||
@pytest.mark.integration
|
||||
async def test_github_api_integration():
|
||||
"""Test GitHub API integration with real service."""
|
||||
token = os.getenv("FASTMCP_GITHUB_TOKEN")
|
||||
if not token:
|
||||
pytest.skip("FASTMCP_GITHUB_TOKEN not available")
|
||||
|
||||
# Test against real GitHub API
|
||||
client = GitHubClient(token)
|
||||
repos = await client.list_repos("jlowin")
|
||||
assert "fastmcp" in [repo.name for repo in repos]
|
||||
|
||||
@pytest.mark.client_process
|
||||
async def test_stdio_transport():
|
||||
"""Test STDIO transport with separate process."""
|
||||
# This spawns a subprocess
|
||||
async with Client("python examples/simple_echo.py") as client:
|
||||
result = await client.call_tool("echo", {"message": "test"})
|
||||
assert result.content[0].text == "test"
|
||||
```
|
||||
|
||||
## Writing Tests
|
||||
|
||||
|
||||
### Test Requirements
|
||||
|
||||
Following these practices creates maintainable, debuggable test suites that serve as both documentation and regression protection.
|
||||
|
||||
#### Single Behavior Per Test
|
||||
|
||||
Each test should verify exactly one behavior. When it fails, you need to know immediately what broke. A test that checks five things gives you five potential failure points to investigate. A test that checks one thing points directly to the problem.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Good: Atomic Test
|
||||
async def test_tool_registration():
|
||||
"""Test that tools are properly registered with the server."""
|
||||
mcp = FastMCP("test-server")
|
||||
|
||||
@mcp.tool
|
||||
def add(a: int, b: int) -> int:
|
||||
return a + b
|
||||
|
||||
tools = mcp.list_tools()
|
||||
assert len(tools) == 1
|
||||
assert tools[0].name == "add"
|
||||
```
|
||||
|
||||
```python Bad: Multi-Behavior Test
|
||||
async def test_server_functionality():
|
||||
"""Test multiple server features at once."""
|
||||
mcp = FastMCP("test-server")
|
||||
|
||||
# Tool registration
|
||||
@mcp.tool
|
||||
def add(a: int, b: int) -> int:
|
||||
return a + b
|
||||
|
||||
# Resource creation
|
||||
@mcp.resource("config://app")
|
||||
def get_config():
|
||||
return {"version": "1.0"}
|
||||
|
||||
# Authentication setup
|
||||
mcp.auth = BearerTokenProvider({"token": "user"})
|
||||
|
||||
# What exactly are we testing? If this fails, what broke?
|
||||
assert mcp.list_tools()
|
||||
assert mcp.list_resources()
|
||||
assert mcp.auth is not None
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
#### Self-Contained Setup
|
||||
|
||||
Every test must create its own setup. Tests should be runnable in any order, in parallel, or in isolation. When a test fails, you should be able to run just that test to reproduce the issue.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Good: Self-Contained
|
||||
async def test_tool_execution_with_error():
|
||||
"""Test that tool errors are properly handled."""
|
||||
mcp = FastMCP("test-server")
|
||||
|
||||
@mcp.tool
|
||||
def divide(a: int, b: int) -> float:
|
||||
if b == 0:
|
||||
raise ValueError("Cannot divide by zero")
|
||||
return a / b
|
||||
|
||||
async with Client(mcp) as client:
|
||||
with pytest.raises(Exception):
|
||||
await client.call_tool("divide", {"a": 10, "b": 0})
|
||||
```
|
||||
|
||||
```python Bad: Test Dependencies
|
||||
# Global state that tests depend on
|
||||
test_server = None
|
||||
|
||||
def test_setup_server():
|
||||
"""Setup for other tests."""
|
||||
global test_server
|
||||
test_server = FastMCP("shared-server")
|
||||
|
||||
def test_server_works():
|
||||
"""Test server functionality."""
|
||||
# Depends on test_setup_server running first
|
||||
assert test_server is not None
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
#### Clear Intent
|
||||
|
||||
Test names and assertions should make the verified behavior obvious. A developer reading your test should understand what feature it validates and how that feature should behave.
|
||||
|
||||
```python
|
||||
async def test_authenticated_tool_requires_valid_token():
|
||||
"""Test that authenticated users can access protected tools."""
|
||||
mcp = FastMCP("test-server")
|
||||
mcp.auth = BearerTokenProvider({"secret-token": "test-user"})
|
||||
|
||||
@mcp.tool
|
||||
def protected_action() -> str:
|
||||
return "success"
|
||||
|
||||
async with Client(mcp, auth=BearerAuth("secret-token")) as client:
|
||||
result = await client.call_tool("protected_action", {})
|
||||
assert result.content[0].text == "success"
|
||||
```
|
||||
|
||||
#### Using Fixtures
|
||||
|
||||
Use fixtures to create reusable data, server configurations, or other resources for your tests. Note that you should **not** open FastMCP clients in your fixtures as it can create hard-to-diagnose issues with event loops.
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from fastmcp import FastMCP, Client
|
||||
|
||||
@pytest.fixture
|
||||
def weather_server():
|
||||
server = FastMCP("WeatherServer")
|
||||
|
||||
@server.tool
|
||||
def get_temperature(city: str) -> dict:
|
||||
temps = {"NYC": 72, "LA": 85, "Chicago": 68}
|
||||
return {"city": city, "temp": temps.get(city, 70)}
|
||||
|
||||
return server
|
||||
|
||||
async def test_temperature_tool(weather_server):
|
||||
async with Client(weather_server) as client:
|
||||
result = await client.call_tool("get_temperature", {"city": "LA"})
|
||||
assert result.data == {"city": "LA", "temp": 85}
|
||||
```
|
||||
|
||||
#### Effective Assertions
|
||||
|
||||
Assertions should be specific and provide context on failure. When a test fails during CI, the assertion message should tell you exactly what went wrong.
|
||||
|
||||
```python
|
||||
# Basic assertion - minimal context on failure
|
||||
assert result.status == "success"
|
||||
|
||||
# Better - explains what was expected
|
||||
assert result.status == "success", f"Expected successful operation, got {result.status}: {result.error}"
|
||||
```
|
||||
|
||||
Try not to have too many assertions in a single test unless you truly need to check various aspects of the same behavior. In general, assertions of different behaviors should be in separate tests.
|
||||
|
||||
#### Inline Snapshots
|
||||
|
||||
FastMCP uses `inline-snapshot` for testing complex data structures. On first run with empty `snapshot()`, pytest will auto-populate the expected value. To update snapshots after intentional changes, run `pytest --inline-snapshot=fix`. This is particularly useful for testing JSON schemas and API responses.
|
||||
|
||||
```python
|
||||
from inline_snapshot import snapshot
|
||||
|
||||
async def test_tool_schema_generation():
|
||||
"""Test that tool schemas are generated correctly."""
|
||||
mcp = FastMCP("test-server")
|
||||
|
||||
@mcp.tool
|
||||
def calculate_tax(amount: float, rate: float = 0.1) -> dict:
|
||||
"""Calculate tax on an amount."""
|
||||
return {"amount": amount, "tax": amount * rate, "total": amount * (1 + rate)}
|
||||
|
||||
tools = mcp.list_tools()
|
||||
schema = tools[0].inputSchema
|
||||
|
||||
# First run: snapshot() is empty, gets auto-populated
|
||||
# Subsequent runs: compares against stored snapshot
|
||||
assert schema == snapshot({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {"type": "number"},
|
||||
"rate": {"type": "number", "default": 0.1}
|
||||
},
|
||||
"required": ["amount"]
|
||||
})
|
||||
```
|
||||
|
||||
### In-Memory Testing
|
||||
|
||||
FastMCP uses in-memory transport for testing, where servers and clients communicate directly. The majority of functionality can be tested in a deterministic fashion this way. We use more complex setups only when testing transports themselves.
|
||||
|
||||
The in-memory transport runs the real MCP protocol implementation without network overhead. Instead of deploying your server or managing network connections, you pass your server instance directly to the client. Everything runs in the same Python process - you can set breakpoints anywhere and step through with your debugger.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP, Client
|
||||
|
||||
# Create your server
|
||||
server = FastMCP("WeatherServer")
|
||||
|
||||
@server.tool
|
||||
def get_temperature(city: str) -> dict:
|
||||
"""Get current temperature for a city"""
|
||||
temps = {"NYC": 72, "LA": 85, "Chicago": 68}
|
||||
return {"city": city, "temp": temps.get(city, 70)}
|
||||
|
||||
async def test_weather_operations():
|
||||
# Pass server directly - no deployment needed
|
||||
async with Client(server) as client:
|
||||
result = await client.call_tool("get_temperature", {"city": "NYC"})
|
||||
assert result.data == {"city": "NYC", "temp": 72}
|
||||
```
|
||||
|
||||
This pattern makes tests deterministic and fast - typically completing in milliseconds rather than seconds.
|
||||
|
||||
### Mocking External Dependencies
|
||||
|
||||
FastMCP servers are standard Python objects, so you can mock external dependencies using your preferred approach:
|
||||
|
||||
```python
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
async def test_database_tool():
|
||||
server = FastMCP("DataServer")
|
||||
|
||||
# Mock the database
|
||||
mock_db = AsyncMock()
|
||||
mock_db.fetch_users.return_value = [
|
||||
{"id": 1, "name": "Alice"},
|
||||
{"id": 2, "name": "Bob"}
|
||||
]
|
||||
|
||||
@server.tool
|
||||
async def list_users() -> list:
|
||||
return await mock_db.fetch_users()
|
||||
|
||||
async with Client(server) as client:
|
||||
result = await client.call_tool("list_users", {})
|
||||
assert len(result.data) == 2
|
||||
assert result.data[0]["name"] == "Alice"
|
||||
mock_db.fetch_users.assert_called_once()
|
||||
```
|
||||
|
||||
### Testing Network Transports
|
||||
|
||||
While in-memory testing covers most unit testing needs, you'll occasionally need to test actual network transports. Use the `run_server_in_process` utility to spawn a server in a separate process for testing:
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from fastmcp.utilities.tests import run_server_in_process
|
||||
from fastmcp import FastMCP, Client
|
||||
from fastmcp.client.transports import StreamableHttpTransport
|
||||
|
||||
def run_server(host: str, port: int) -> None:
|
||||
"""Function to run in subprocess."""
|
||||
server = FastMCP("TestServer")
|
||||
|
||||
@server.tool
|
||||
def greet(name: str) -> str:
|
||||
return f"Hello, {name}!"
|
||||
|
||||
server.run(host=host, port=port)
|
||||
|
||||
@pytest.fixture
|
||||
async def http_server():
|
||||
"""Fixture that runs server in subprocess."""
|
||||
with run_server_in_process(run_server, transport="http") as url:
|
||||
yield f"{url}/mcp"
|
||||
|
||||
async def test_http_transport(http_server: str):
|
||||
"""Test actual HTTP transport behavior."""
|
||||
async with Client(
|
||||
transport=StreamableHttpTransport(http_server)
|
||||
) as client:
|
||||
result = await client.ping()
|
||||
assert result is True
|
||||
|
||||
greeting = await client.call_tool("greet", {"name": "World"})
|
||||
assert greeting.data == "Hello, World!"
|
||||
```
|
||||
|
||||
The `run_server_in_process` utility handles server lifecycle, port allocation, and cleanup automatically. This pattern is essential for testing transport-specific behavior like timeouts, headers, and authentication. Note that FastMCP often uses the `client_process` marker to isolate tests that spawn processes, as they can create contention in CI.
|
||||
|
||||
### Documentation Testing
|
||||
|
||||
Documentation requires the same validation as code. The `just docs` command launches a local Mintlify server that renders your documentation exactly as users will see it:
|
||||
|
||||
```bash
|
||||
# Start local documentation server with hot reload
|
||||
just docs
|
||||
|
||||
# Or run Mintlify directly
|
||||
mintlify dev
|
||||
```
|
||||
|
||||
The local server watches for changes and automatically refreshes. This preview catches formatting issues and helps you see documentation as users will experience it.
|
||||
346
docs/docs.json
346
docs/docs.json
|
|
@ -50,185 +50,187 @@
|
|||
"navigation": {
|
||||
"tabs": [
|
||||
{
|
||||
"anchors": [
|
||||
"groups": [
|
||||
{
|
||||
"anchor": "Documentation",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Get Started",
|
||||
"pages": [
|
||||
"getting-started/welcome",
|
||||
"getting-started/installation",
|
||||
"getting-started/quickstart"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Servers",
|
||||
"pages": [
|
||||
"servers/server",
|
||||
{
|
||||
"group": "Core Components",
|
||||
"icon": "toolbox",
|
||||
"pages": [
|
||||
"servers/tools",
|
||||
"servers/resources",
|
||||
"servers/prompts"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Advanced Features",
|
||||
"icon": "stars",
|
||||
"pages": [
|
||||
"servers/context",
|
||||
"servers/proxy",
|
||||
"servers/composition",
|
||||
"servers/elicitation",
|
||||
"servers/logging",
|
||||
"servers/progress",
|
||||
"servers/sampling",
|
||||
"servers/middleware"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Authentication",
|
||||
"icon": "shield-check",
|
||||
"pages": [
|
||||
"servers/auth/authentication",
|
||||
"servers/auth/token-verification",
|
||||
"servers/auth/remote-oauth",
|
||||
"servers/auth/oauth-proxy",
|
||||
"servers/auth/full-oauth-server"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Deployment",
|
||||
"icon": "rocket",
|
||||
"pages": [
|
||||
"deployment/running-server",
|
||||
"deployment/server-configuration",
|
||||
"deployment/testing",
|
||||
"deployment/self-hosted",
|
||||
"deployment/fastmcp-cloud"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Clients",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Essentials",
|
||||
"icon": "cube",
|
||||
"pages": ["clients/client", "clients/transports"]
|
||||
},
|
||||
{
|
||||
"group": "Core Operations",
|
||||
"icon": "handshake",
|
||||
"pages": [
|
||||
"clients/tools",
|
||||
"clients/resources",
|
||||
"clients/prompts"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Advanced Features",
|
||||
"icon": "stars",
|
||||
"pages": [
|
||||
"clients/elicitation",
|
||||
"clients/logging",
|
||||
"clients/progress",
|
||||
"clients/sampling",
|
||||
"clients/messages",
|
||||
"clients/roots"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Authentication",
|
||||
"icon": "user-shield",
|
||||
"pages": ["clients/auth/oauth", "clients/auth/bearer"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Integrations",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Authentication",
|
||||
"icon": "key",
|
||||
"pages": [
|
||||
"integrations/authkit",
|
||||
"integrations/azure",
|
||||
"integrations/github",
|
||||
"integrations/google",
|
||||
"integrations/workos-oauth"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Authorization",
|
||||
"icon": "shield-check",
|
||||
"pages": [
|
||||
"integrations/eunomia-authorization",
|
||||
"integrations/permit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "AI Assistants",
|
||||
"icon": "robot",
|
||||
"pages": [
|
||||
"integrations/chatgpt",
|
||||
"integrations/claude-code",
|
||||
"integrations/claude-desktop",
|
||||
"integrations/cursor",
|
||||
"integrations/mcp-json-configuration"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "AI SDKs",
|
||||
"icon": "code",
|
||||
"pages": [
|
||||
"integrations/anthropic",
|
||||
"integrations/gemini",
|
||||
"integrations/openai"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Web Frameworks",
|
||||
"icon": "globe",
|
||||
"pages": [
|
||||
"integrations/asgi",
|
||||
"integrations/fastapi",
|
||||
"integrations/openapi"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Patterns",
|
||||
"pages": [
|
||||
"patterns/tool-transformation",
|
||||
"patterns/decorating-methods",
|
||||
"patterns/cli",
|
||||
"patterns/contrib"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Tutorials",
|
||||
"pages": [
|
||||
"tutorials/mcp",
|
||||
"tutorials/create-mcp-server",
|
||||
"tutorials/rest-api"
|
||||
]
|
||||
}
|
||||
],
|
||||
"icon": "book"
|
||||
"group": "Get Started",
|
||||
"pages": [
|
||||
"getting-started/welcome",
|
||||
"getting-started/installation",
|
||||
"getting-started/quickstart"
|
||||
]
|
||||
},
|
||||
{
|
||||
"anchor": "What's New",
|
||||
"pages": ["updates", "changelog"]
|
||||
"group": "Servers",
|
||||
"pages": [
|
||||
"servers/server",
|
||||
{
|
||||
"group": "Core Components",
|
||||
"icon": "toolbox",
|
||||
"pages": [
|
||||
"servers/tools",
|
||||
"servers/resources",
|
||||
"servers/prompts"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Advanced Features",
|
||||
"icon": "stars",
|
||||
"pages": [
|
||||
"servers/context",
|
||||
"servers/proxy",
|
||||
"servers/composition",
|
||||
"servers/elicitation",
|
||||
"servers/logging",
|
||||
"servers/progress",
|
||||
"servers/sampling",
|
||||
"servers/middleware"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Authentication",
|
||||
"icon": "shield-check",
|
||||
"pages": [
|
||||
"servers/auth/authentication",
|
||||
"servers/auth/token-verification",
|
||||
"servers/auth/remote-oauth",
|
||||
"servers/auth/oauth-proxy",
|
||||
"servers/auth/full-oauth-server"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Deployment",
|
||||
"icon": "rocket",
|
||||
"pages": [
|
||||
"deployment/running-server",
|
||||
"deployment/server-configuration",
|
||||
"deployment/self-hosted",
|
||||
"deployment/fastmcp-cloud"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Clients",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Essentials",
|
||||
"icon": "cube",
|
||||
"pages": [
|
||||
"clients/client",
|
||||
"clients/transports"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Core Operations",
|
||||
"icon": "handshake",
|
||||
"pages": [
|
||||
"clients/tools",
|
||||
"clients/resources",
|
||||
"clients/prompts"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Advanced Features",
|
||||
"icon": "stars",
|
||||
"pages": [
|
||||
"clients/elicitation",
|
||||
"clients/logging",
|
||||
"clients/progress",
|
||||
"clients/sampling",
|
||||
"clients/messages",
|
||||
"clients/roots"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Authentication",
|
||||
"icon": "user-shield",
|
||||
"pages": [
|
||||
"clients/auth/oauth",
|
||||
"clients/auth/bearer"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Integrations",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Authentication",
|
||||
"icon": "key",
|
||||
"pages": [
|
||||
"integrations/authkit",
|
||||
"integrations/azure",
|
||||
"integrations/github",
|
||||
"integrations/google",
|
||||
"integrations/workos-oauth"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Authorization",
|
||||
"icon": "shield-check",
|
||||
"pages": [
|
||||
"integrations/eunomia-authorization",
|
||||
"integrations/permit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "AI Assistants",
|
||||
"icon": "robot",
|
||||
"pages": [
|
||||
"integrations/chatgpt",
|
||||
"integrations/claude-code",
|
||||
"integrations/claude-desktop",
|
||||
"integrations/cursor",
|
||||
"integrations/mcp-json-configuration"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "AI SDKs",
|
||||
"icon": "code",
|
||||
"pages": [
|
||||
"integrations/anthropic",
|
||||
"integrations/gemini",
|
||||
"integrations/openai"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Web Frameworks",
|
||||
"icon": "globe",
|
||||
"pages": [
|
||||
"integrations/asgi",
|
||||
"integrations/fastapi",
|
||||
"integrations/openapi"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Patterns",
|
||||
"pages": [
|
||||
"patterns/tool-transformation",
|
||||
"patterns/decorating-methods",
|
||||
"patterns/cli",
|
||||
"patterns/contrib"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Development",
|
||||
"pages": [
|
||||
"development/contributing",
|
||||
"development/tests",
|
||||
"development/releases"
|
||||
]
|
||||
}
|
||||
],
|
||||
"tab": "Documentation"
|
||||
},
|
||||
{
|
||||
"pages": [
|
||||
"updates",
|
||||
"changelog"
|
||||
],
|
||||
"tab": "What's New"
|
||||
},
|
||||
{
|
||||
"anchors": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -61,50 +61,22 @@ mcp = FastMCP("My MCP Server")
|
|||
Prior to `fastmcp==2.3.0` and `mcp==1.8.0`, the 2.x API always mirrored the official 1.0 API. However, as the projects diverge, this can not be guaranteed. You may see deprecation warnings if you attempt to use 1.0 APIs in FastMCP 2.x. Please refer to this documentation for details on new capabilities.
|
||||
</Warning>
|
||||
|
||||
## Versioning and Breaking Changes
|
||||
## Versioning Policy
|
||||
|
||||
While we make every effort not to introduce backwards-incompatible changes to our public APIs and behavior, FastMCP exists in a rapidly evolving MCP landscape. We're committed to bringing the most cutting-edge features to our users, which occasionally necessitates changes to existing functionality.
|
||||
FastMCP follows semantic versioning with pragmatic adaptations for the rapidly evolving MCP ecosystem. Breaking changes may occur in minor versions (e.g., 2.3.x to 2.4.0) when necessary to stay current with the MCP Protocol.
|
||||
|
||||
As a practice, breaking changes will only occur on minor version changes (e.g., 2.3.x to 2.4.0). A minor version change indicates either:
|
||||
- A significant new feature set that warrants a new minor version
|
||||
- Introducing breaking changes that may affect behavior on upgrade
|
||||
|
||||
For users concerned about stability in production environments, we recommend pinning FastMCP to a specific version in your dependencies.
|
||||
|
||||
Whenever possible, FastMCP will issue deprecation warnings when users attempt to use APIs that are either deprecated or destined for future removal. These warnings will be maintained for at least 1 minor version release, and may be maintained longer.
|
||||
|
||||
Note that the "public API" includes the public functionality of the `FastMCP` server, core FastMCP components like `Tool`, `Prompt`, `Resource`, and `ResourceTemplate`, and their respective public methods. It does not include private methods, utilities, or objects that are stored as private attributes, as we do not expect users to rely on those implementation details.
|
||||
|
||||
## Installing for Development
|
||||
|
||||
If you plan to contribute to FastMCP, you should begin by cloning the repository and using uv to install all dependencies (development dependencies are installed automatically):
|
||||
|
||||
```bash
|
||||
git clone https://github.com/jlowin/fastmcp.git
|
||||
cd fastmcp
|
||||
uv sync
|
||||
For production use, always pin to exact versions:
|
||||
```
|
||||
fastmcp==2.11.0 # Good
|
||||
fastmcp>=2.11.0 # Bad - will install breaking changes
|
||||
```
|
||||
|
||||
This will install all dependencies, including ones for development, and create a virtual environment, which you can activate and use as normal.
|
||||
See the full [versioning and release policy](/development/releases#versioning-policy) for details on our public API, deprecation practices, and breaking change philosophy.
|
||||
|
||||
### Unit Tests
|
||||
## Contributing to FastMCP
|
||||
|
||||
FastMCP has a comprehensive unit test suite, and all PR's must introduce and pass appropriate tests. To run the tests, use pytest:
|
||||
|
||||
```bash
|
||||
pytest
|
||||
```
|
||||
|
||||
### Pre-Commit Hooks
|
||||
|
||||
FastMCP uses pre-commit to manage code quality, including formatting, linting, and type-safety. All PRs must pass the pre-commit hooks, which are run as a part of the CI process. To install the pre-commit hooks, run:
|
||||
|
||||
```bash
|
||||
uv run pre-commit install
|
||||
```
|
||||
|
||||
Alternatively, to run pre-commit manually at any time, use:
|
||||
|
||||
```bash
|
||||
pre-commit run --all-files
|
||||
```
|
||||
Interested in contributing to FastMCP? See the [Contributing Guide](/development/contributing) for details on:
|
||||
- Setting up your development environment
|
||||
- Running tests and pre-commit hooks
|
||||
- Submitting issues and pull requests
|
||||
- Code standards and review process
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@ sidebarTitle: oauth
|
|||
|
||||
## Functions
|
||||
|
||||
### `default_cache_dir` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L48" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `default_cache_dir` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L55" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
default_cache_dir() -> Path
|
||||
```
|
||||
|
||||
### `check_if_auth_required` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L192" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `check_if_auth_required` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L199" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
check_if_auth_required(mcp_url: str, httpx_kwargs: dict[str, Any] | None = None) -> bool
|
||||
|
|
@ -28,13 +28,19 @@ Check if the MCP endpoint requires authentication by making a test request.
|
|||
|
||||
## Classes
|
||||
|
||||
### `StoredToken` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L37" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ClientNotFoundError` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L38" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Raised when OAuth client credentials are not found on the server.
|
||||
|
||||
|
||||
### `StoredToken` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L44" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Token storage format with absolute expiry time.
|
||||
|
||||
|
||||
### `FileTokenStorage` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L52" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `FileTokenStorage` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L59" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
File-based token storage implementation for OAuth credentials and tokens.
|
||||
|
|
@ -45,7 +51,7 @@ Each instance is tied to a specific server URL for proper token isolation.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `get_base_url` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L67" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_base_url` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L74" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_base_url(url: str) -> str
|
||||
|
|
@ -54,7 +60,7 @@ get_base_url(url: str) -> str
|
|||
Extract the base URL (scheme + host) from a URL.
|
||||
|
||||
|
||||
#### `get_cache_key` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L72" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_cache_key` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L79" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_cache_key(self) -> str
|
||||
|
|
@ -63,7 +69,7 @@ get_cache_key(self) -> str
|
|||
Generate a safe filesystem key from the server's base URL.
|
||||
|
||||
|
||||
#### `get_tokens` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L87" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_tokens` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L94" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_tokens(self) -> OAuthToken | None
|
||||
|
|
@ -72,7 +78,7 @@ get_tokens(self) -> OAuthToken | None
|
|||
Load tokens from file storage.
|
||||
|
||||
|
||||
#### `set_tokens` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L119" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_tokens` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L126" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_tokens(self, tokens: OAuthToken) -> None
|
||||
|
|
@ -81,7 +87,7 @@ set_tokens(self, tokens: OAuthToken) -> None
|
|||
Save tokens to file storage.
|
||||
|
||||
|
||||
#### `get_client_info` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L136" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_client_info` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L143" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_client_info(self) -> OAuthClientInformationFull | None
|
||||
|
|
@ -90,7 +96,7 @@ get_client_info(self) -> OAuthClientInformationFull | None
|
|||
Load client information from file storage.
|
||||
|
||||
|
||||
#### `set_client_info` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L164" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_client_info` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L171" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_client_info(self, client_info: OAuthClientInformationFull) -> None
|
||||
|
|
@ -99,7 +105,7 @@ set_client_info(self, client_info: OAuthClientInformationFull) -> None
|
|||
Save client information to file storage.
|
||||
|
||||
|
||||
#### `clear` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L170" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `clear` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L177" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
clear(self) -> None
|
||||
|
|
@ -108,7 +114,7 @@ clear(self) -> None
|
|||
Clear all cached data for this server.
|
||||
|
||||
|
||||
#### `clear_all` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L179" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `clear_all` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L186" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
clear_all(cls, cache_dir: Path | None = None) -> None
|
||||
|
|
@ -117,7 +123,7 @@ clear_all(cls, cache_dir: Path | None = None) -> None
|
|||
Clear all cached data for all servers.
|
||||
|
||||
|
||||
### `OAuth` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L222" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `OAuth` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L229" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
OAuth client provider for MCP servers with browser-based authentication.
|
||||
|
|
@ -128,16 +134,16 @@ 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#L302" 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#L309" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
redirect_handler(self, authorization_url: str) -> None
|
||||
```
|
||||
|
||||
Open browser for authorization.
|
||||
Open browser for authorization, with pre-flight check for invalid client.
|
||||
|
||||
|
||||
#### `callback_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L307" 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#L330" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
callback_handler(self) -> tuple[str, str | None]
|
||||
|
|
@ -145,3 +151,15 @@ callback_handler(self) -> tuple[str, str | None]
|
|||
|
||||
Handle OAuth callback and return (auth_code, state).
|
||||
|
||||
|
||||
#### `async_auth_flow` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L363" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
async_auth_flow(self, request: httpx.Request) -> AsyncGenerator[httpx.Request, httpx.Response]
|
||||
```
|
||||
|
||||
HTTPX auth flow with automatic retry on stale cached credentials.
|
||||
|
||||
If the OAuth flow fails due to invalid/stale client credentials,
|
||||
clears the cache and retries once with fresh registration.
|
||||
|
||||
|
|
|
|||
|
|
@ -127,7 +127,6 @@ OAuth Flow Implementation
|
|||
1. Client Registration (DCR):
|
||||
- Accept any client registration request
|
||||
- Store ProxyDCRClient that accepts dynamic redirect URIs
|
||||
- Return shared upstream credentials to all clients
|
||||
|
||||
2. Authorization:
|
||||
- Store transaction mapping client details to proxy flow
|
||||
|
|
@ -182,51 +181,33 @@ Handles provider-specific requirements:
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `get_client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L325" 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#L330" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_client(self, client_id: str) -> OAuthClientInformationFull | None
|
||||
```
|
||||
|
||||
Get client information by ID.
|
||||
Get client information by ID. This is generally the random ID
|
||||
provided to the DCR client during registration, not the upstream client ID.
|
||||
|
||||
For unregistered clients, returns a ProxyDCRClient that accepts
|
||||
any localhost redirect URI for DCR clients.
|
||||
|
||||
Even registered clients use ProxyDCRClient to ensure they can
|
||||
authenticate with different dynamic ports on reconnection. This
|
||||
handles the case where a client with cached tokens reconnects
|
||||
on a different port.
|
||||
For unregistered clients, returns None (which will raise an error in the SDK).
|
||||
|
||||
|
||||
#### `register_client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L358" 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#L340" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
register_client(self, client_info: OAuthClientInformationFull) -> None
|
||||
```
|
||||
|
||||
Register a client locally using fixed upstream credentials.
|
||||
Register a client locally
|
||||
|
||||
This implementation always uses the upstream client_id and client_secret
|
||||
regardless of what the client requests. It modifies the client_info object
|
||||
in place since the MCP framework ignores return values.
|
||||
|
||||
This ensures all clients use the same credentials that are registered
|
||||
with the upstream server.
|
||||
|
||||
Implementation Detail:
|
||||
We store a ProxyDCRClient (not the original client_info) to ensure
|
||||
the client can reconnect with different dynamic redirect URIs. This is
|
||||
essential for cached token scenarios where the client port changes.
|
||||
|
||||
The flow:
|
||||
1. Client provides its desired redirect URIs (dynamic localhost ports)
|
||||
2. We create a ProxyDCRClient that will accept ANY localhost URI
|
||||
3. We store this flexible client for future authentications
|
||||
4. When client reconnects with a different port, ProxyDCRClient accepts it
|
||||
When a client registers, we create a ProxyDCRClient that is more
|
||||
forgiving about validating redirect URIs, since the DCR client's
|
||||
redirect URI will likely be localhost or unknown to the proxied IDP. The
|
||||
proxied IDP only knows about this server's fixed redirect URI.
|
||||
|
||||
|
||||
#### `authorize` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L417" 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#L383" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str
|
||||
|
|
@ -240,7 +221,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#L480" 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#L439" 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 +233,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#L522" 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#L481" 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 +245,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#L589" 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#L548" 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 +254,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#L597" 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#L556" 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 +263,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#L672" 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#L631" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
load_access_token(self, token: str) -> AccessToken | None
|
||||
|
|
@ -294,7 +275,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#L689" 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#L648" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
revoke_token(self, token: AccessToken | RefreshToken) -> None
|
||||
|
|
@ -306,7 +287,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#L733" 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#L692" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_routes(self, mcp_path: str | None = None, mcp_endpoint: Any | None = None) -> list[Route]
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ validate_source(cls, v: dict | Source) -> SourceType
|
|||
Validate and convert source to proper format.
|
||||
|
||||
Supports:
|
||||
- Dict format: {"path": "server.py", "entrypoint": "app"}
|
||||
- Dict format: `{"path": "server.py", "entrypoint": "app"}`
|
||||
- FileSystemSource instance (passed through)
|
||||
|
||||
No string parsing happens here - that's only at CLI boundaries.
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ class MCPServerConfig(BaseModel):
|
|||
"""Validate and convert source to proper format.
|
||||
|
||||
Supports:
|
||||
- Dict format: {"path": "server.py", "entrypoint": "app"}
|
||||
- Dict format: `{"path": "server.py", "entrypoint": "app"}`
|
||||
- FileSystemSource instance (passed through)
|
||||
|
||||
No string parsing happens here - that's only at CLI boundaries.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue