mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-22 05:24:18 +02:00
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Jeremiah Lowin <jlowin@users.noreply.github.com> Co-authored-by: Marvin Context Protocol <41898282+Marvin Context Protocol@users.noreply.github.com> Co-authored-by: voidborne-d <voidborne-d@users.noreply.github.com> Co-authored-by: marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: d 🔹 <258577966+voidborne-d@users.noreply.github.com> Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Co-authored-by: nightcityblade <nightcityblade@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Bill Easton <strawgate@users.noreply.github.com> Co-authored-by: Sumanshu Nankana <sumanshunankana@gmail.com> Co-authored-by: Eric Robinson <ericrobinson@indeed.com> Co-authored-by: Martim Santos <martimfasantos@gmail.com> Co-authored-by: d 🔹 <liusway405@gmail.com> Co-authored-by: Matthieu B <66959271+mtthidoteu@users.noreply.github.com> Co-authored-by: Sascha Buehrle <47737812+saschabuehrle@users.noreply.github.com> Co-authored-by: Hakancan <142545736+hkc5@users.noreply.github.com> Co-authored-by: nightcityblade <jackchen@haloailabs.com> Co-authored-by: Matt Hallowell <17804673+mhallo@users.noreply.github.com> Co-authored-by: nate nowack <thrast36@gmail.com> Co-authored-by: Bill Easton <williamseaston@gmail.com> Co-authored-by: Marcus Shu <46469249+shulkx@users.noreply.github.com> Co-authored-by: Rushabh Doshi <radoshi@gmail.com> Co-authored-by: AIKAWA Shigechika <shige@aikawa.jp> Co-authored-by: Jeremy Simon <simonjer805@gmail.com> Co-authored-by: Miguel Miranda Dias <7780875+pandego@users.noreply.github.com> Co-authored-by: Anthony James Padavano <padavano.anthony@gmail.com> Co-authored-by: Mostafa Kamal <hiremostafa@gmail.com> Fix auto-close MRE script posting comment without closing (#3386) Fix WorkOS token scope verification bypass 🤖 Generated with Codex (#3407) Fix initialize McpError fallthrough 🤖 Generated with Codex (#3413) Fix transform arg collisions with passthrough params (#3431) Fix get_* returning None when latest version is disabled (#3439) Fix get_* returning None when latest version is disabled (#3421) Fix server lifespan overlap teardown (#3415) Fix $ref output schema object detection regression (#3420) resolved annotations (#3429) Fix async partial callables rejected by iscoroutinefunction (#3438) Fix async partial callables rejected by iscoroutinefunction (#3423) fix: add version to components (#3458) fix: use intent-based flag for OIDC scope patch in load_access_token (#3465) Fixes #3461 fix: normalize Google scope shorthands and surface valid_scopes (#3477) fix: resolve ty 0.0.23 type-checking errors and bump pin (#3481) fix: shield lifespan teardown from cancellation (#3480) fix: forward custom_route endpoints from mounted servers (#3462) fix updates _get_additional_http_routes() to traverse providers, Fixes #3457 fix: remove hardcoded version from CLI help text (#3456) fix: monty 0.0.8 compatibility, drop external_functions from constructor (#3468) fix: task test teardown hanging 5s per test (#3499) Closes #3498 fix: validate workspace path is a directory before cursor install (#3440) Fixes #3426 fix: handle re.error from malformed URI templates in build_regex (#3501) fix: reject empty/OIDC-only required_scopes in AzureProvider (#3503) fix: restrict $ref resolution to local refs only (SSRF/LFI) (#3502) fix warnings and timeouts (#3504) close upgrade check issue when build passes (#3505) Closes #3484 fix: URL-encode path params to prevent SSRF/path traversal (GHSA-vv7q-7jx5-f767) (#3507) fix: prevent path traversal in skill download (#3493) fix: prefer IdP-granted scopes over client-requested scopes in OAuthProxy (#3492) fix: remove unrelated transform and http.py changes from PR scope fix: remove forced follow_redirects from httpx_client_factory calls (#3496) fix: stop passing follow_redirects to httpx_client_factory fix: restore follow_redirects=True for custom httpx client factories Closes #3509 fix: CSRF double-submit cookie check in consent flow (#3519) fix: validate server names in install commands (#3522) fix: use raw strings for regex in pytest.raises match (#3523) fix: reject refresh tokens used as Bearer access tokens (#3524) fix: route ResourcesAsTools/PromptsAsTools through server middleware (#3495) fix: resolve Pyright "Module is not callable" on @tool, @resource, @prompt decorators (#3540) fix: filter warnings by message in KEY_PREFIX test (#3549) fix: suppress output schema for ToolResult subclass annotations (#3548) fix: increase sleep duration in proxy cache tests (#3567) fix: store absolute token expiry to prevent stale expires_in on reload (#3572) fix: preserve tool properties named 'title' during schema compression (#3582) Fix loopback redirect URI port matching per RFC 8252 §7.3 (#3589) Fix app tool routing: visibility check and middleware propagation (#3591) Fix query parameter serialization to respect OpenAPI explode/style settings (#3595) Fix dev apps form: union types, textarea support, JSON parsing (#3597) fix(google): replace deprecated /oauth2/v1/tokeninfo with /oauth2/v3/userinfo (#3603) fix: resolve EntraOBOToken dependency injection through MultiAuth (#3609) fix(docs): correct misleading stateless_http header (#3622) fix: filesystem provider import machinery (#3626) Closes #3625 (issues 2, 3, 6) fix: recover StdioTransport after subprocess exits (#3630) fix(server): preserve mounted tool task metadata (#3632) fix: scope deprecation warning filter to FastMCPDeprecationWarning (#3649) fix imports, add PrefabAppConfig (#3650) fix: resolve CurrentFastMCP/ctx.fastmcp to child server in mounted background tasks (#3651) Fix blocking docs issues: chart imports, Select API, Rx consistency (#3652) closed by default (#3657) Fix prompt caching middleware missing wrap/unwrap round-trip (#3666) fix: serialize object query params per OpenAPI style/explode rules (#3662) Fixes #2857 fix: HTTP request headers not accessible in background task workers (#3631) fix: restore HTTP headers in worker execution path for background tasks (#3681) fix: strip discriminator after dereferencing schemas (#3682) fix: remove stale ty:ignore directives for ty 0.0.26 (#3684) Fix docs gaps in app provider pages (#3690) fix: dev apps log panel UX improvements (#3698) fix dev server empty string args (#3700)
433 lines
13 KiB
Text
433 lines
13 KiB
Text
---
|
|
title: Dependency Injection
|
|
sidebarTitle: Dependencies
|
|
description: Inject runtime values like HTTP requests, access tokens, and custom dependencies into your MCP components.
|
|
icon: syringe
|
|
tag: NEW
|
|
---
|
|
|
|
import { VersionBadge } from "/snippets/version-badge.mdx";
|
|
|
|
FastMCP uses dependency injection to provide runtime values to your tools, resources, and prompts. Instead of passing context through every layer of your code, you declare what you need as parameter defaults—FastMCP resolves them automatically when your function runs.
|
|
|
|
The dependency injection system is powered by [Docket](https://github.com/chrisguidry/docket) and its dependency system [uncalled-for](https://github.com/chrisguidry/uncalled-for). Core DI features like `Depends()` and `CurrentContext()` work without installing Docket. For background tasks and advanced task-related dependencies, install `fastmcp[tasks]`. For comprehensive coverage of dependency patterns, see the [Docket dependency documentation](https://docket.lol/en/latest/dependency-injection/).
|
|
|
|
<Note>
|
|
Dependency parameters are automatically excluded from the MCP schema—clients never see them as callable parameters. This separation keeps your function signatures clean while giving you access to the runtime context you need.
|
|
</Note>
|
|
|
|
## How Dependency Injection Works
|
|
|
|
Dependency injection in FastMCP follows a simple pattern: declare a parameter with a recognized type annotation or a dependency default value, and FastMCP injects the resolved value at runtime.
|
|
|
|
```python
|
|
from fastmcp import FastMCP
|
|
from fastmcp.server.context import Context
|
|
|
|
mcp = FastMCP("Demo")
|
|
|
|
|
|
@mcp.tool
|
|
async def my_tool(query: str, ctx: Context) -> str:
|
|
await ctx.info(f"Processing: {query}")
|
|
return f"Results for: {query}"
|
|
```
|
|
|
|
When a client calls `my_tool`, they only see `query` as a parameter. The `ctx` parameter is injected automatically because it has a `Context` type annotation—FastMCP recognizes this and provides the active context for the request.
|
|
|
|
This works identically for tools, resources, resource templates, and prompts.
|
|
|
|
### Explicit Dependencies with CurrentContext
|
|
|
|
For more explicit code, you can use `CurrentContext()` as a default value instead of relying on the type annotation:
|
|
|
|
```python
|
|
from fastmcp import FastMCP
|
|
from fastmcp.dependencies import CurrentContext
|
|
from fastmcp.server.context import Context
|
|
|
|
mcp = FastMCP("Demo")
|
|
|
|
|
|
@mcp.tool
|
|
async def my_tool(query: str, ctx: Context = CurrentContext()) -> str:
|
|
await ctx.info(f"Processing: {query}")
|
|
return f"Results for: {query}"
|
|
```
|
|
|
|
Both approaches work identically. The type-annotation approach is more concise; the explicit `CurrentContext()` approach makes the dependency injection visible in the signature.
|
|
|
|
## Built-in Dependencies
|
|
|
|
### MCP Context
|
|
|
|
The MCP Context provides logging, progress reporting, resource access, and other request-scoped operations. See [MCP Context](/servers/context) for the full API.
|
|
|
|
**Dependency injection:** Use a `Context` type annotation (FastMCP injects automatically) or `CurrentContext()`:
|
|
|
|
```python
|
|
from fastmcp import FastMCP
|
|
from fastmcp.server.context import Context
|
|
|
|
mcp = FastMCP("Demo")
|
|
|
|
|
|
@mcp.tool
|
|
async def process_data(data: str, ctx: Context) -> str:
|
|
await ctx.info(f"Processing: {data}")
|
|
return "Done"
|
|
|
|
|
|
# Or explicitly with CurrentContext()
|
|
from fastmcp.dependencies import CurrentContext
|
|
|
|
@mcp.tool
|
|
async def process_data(data: str, ctx: Context = CurrentContext()) -> str:
|
|
...
|
|
```
|
|
|
|
**Function:** Use `get_context()` in helper functions or middleware:
|
|
|
|
```python
|
|
from fastmcp.server.dependencies import get_context
|
|
|
|
async def log_something(message: str):
|
|
ctx = get_context()
|
|
await ctx.info(message)
|
|
```
|
|
|
|
### Server Instance
|
|
|
|
<VersionBadge version="2.14" />
|
|
|
|
Access the FastMCP server instance for introspection or server-level configuration.
|
|
|
|
**Dependency injection:** Use `CurrentFastMCP()`:
|
|
|
|
```python
|
|
from fastmcp import FastMCP
|
|
from fastmcp.dependencies import CurrentFastMCP
|
|
|
|
mcp = FastMCP("Demo")
|
|
|
|
|
|
@mcp.tool
|
|
async def server_info(server: FastMCP = CurrentFastMCP()) -> str:
|
|
return f"Server: {server.name}"
|
|
```
|
|
|
|
**Function:** Use `get_server()`:
|
|
|
|
```python
|
|
from fastmcp.server.dependencies import get_server
|
|
|
|
def get_server_name() -> str:
|
|
return get_server().name
|
|
```
|
|
|
|
### HTTP Request
|
|
|
|
<VersionBadge version="2.2.11" />
|
|
|
|
Access the Starlette Request when running over HTTP transports (SSE or Streamable HTTP).
|
|
|
|
**Dependency injection:** Use `CurrentRequest()`:
|
|
|
|
```python
|
|
from fastmcp import FastMCP
|
|
from fastmcp.dependencies import CurrentRequest
|
|
from starlette.requests import Request
|
|
|
|
mcp = FastMCP("Demo")
|
|
|
|
|
|
@mcp.tool
|
|
async def client_info(request: Request = CurrentRequest()) -> dict:
|
|
return {
|
|
"user_agent": request.headers.get("user-agent", "Unknown"),
|
|
"client_ip": request.client.host if request.client else "Unknown",
|
|
}
|
|
```
|
|
|
|
**Function:** Use `get_http_request()`:
|
|
|
|
```python
|
|
from fastmcp.server.dependencies import get_http_request
|
|
|
|
def get_client_ip() -> str:
|
|
request = get_http_request()
|
|
return request.client.host if request.client else "Unknown"
|
|
```
|
|
|
|
<Note>
|
|
Both raise `RuntimeError` when called outside an HTTP context (e.g., STDIO transport).
|
|
For background tasks created from an HTTP request, FastMCP restores a minimal request
|
|
backed by the originating request's snapshotted headers. Use HTTP Headers if you need
|
|
graceful fallback.
|
|
</Note>
|
|
|
|
### HTTP Headers
|
|
|
|
<VersionBadge version="2.2.11" />
|
|
|
|
Access HTTP headers with graceful fallback. When a background task originates from an
|
|
HTTP request, FastMCP restores the originating headers inside the worker. When no HTTP
|
|
request is available, this returns an empty dictionary, making it safe for code that
|
|
might run over any transport.
|
|
|
|
**Dependency injection:** Use `CurrentHeaders()`:
|
|
|
|
```python
|
|
from fastmcp import FastMCP
|
|
from fastmcp.dependencies import CurrentHeaders
|
|
|
|
mcp = FastMCP("Demo")
|
|
|
|
|
|
@mcp.tool
|
|
async def get_auth_type(headers: dict = CurrentHeaders()) -> str:
|
|
auth = headers.get("authorization", "")
|
|
return "Bearer" if auth.startswith("Bearer ") else "None"
|
|
```
|
|
|
|
**Function:** Use `get_http_headers()`:
|
|
|
|
```python
|
|
from fastmcp.server.dependencies import get_http_headers
|
|
|
|
def get_user_agent() -> str:
|
|
headers = get_http_headers()
|
|
return headers.get("user-agent", "Unknown")
|
|
```
|
|
|
|
By default, problematic headers like `host` and `content-length` are excluded. Use `get_http_headers(include_all=True)` to include all headers.
|
|
|
|
### Access Token
|
|
|
|
<VersionBadge version="2.11.0" />
|
|
|
|
Access the authenticated user's token when your server uses authentication.
|
|
|
|
**Dependency injection:** Use `CurrentAccessToken()` (raises if not authenticated):
|
|
|
|
```python
|
|
from fastmcp import FastMCP
|
|
from fastmcp.dependencies import CurrentAccessToken
|
|
from fastmcp.server.auth import AccessToken
|
|
|
|
mcp = FastMCP("Demo")
|
|
|
|
|
|
@mcp.tool
|
|
async def get_user_id(token: AccessToken = CurrentAccessToken()) -> str:
|
|
return token.claims.get("sub", "unknown")
|
|
```
|
|
|
|
**Function:** Use `get_access_token()` (returns `None` if not authenticated):
|
|
|
|
```python
|
|
from fastmcp.server.dependencies import get_access_token
|
|
|
|
@mcp.tool
|
|
async def get_user_info() -> dict:
|
|
token = get_access_token()
|
|
if token is None:
|
|
return {"authenticated": False}
|
|
return {"authenticated": True, "user": token.claims.get("sub")}
|
|
```
|
|
|
|
The `AccessToken` object provides:
|
|
|
|
- **`client_id`**: The OAuth client identifier
|
|
- **`scopes`**: List of granted permission scopes
|
|
- **`expires_at`**: Token expiration timestamp (if available)
|
|
- **`claims`**: Dictionary of all token claims (JWT claims or provider-specific data)
|
|
|
|
### Token Claims
|
|
|
|
When you need just one specific value from the token—like a user ID or tenant identifier—`TokenClaim()` extracts it directly without needing the full token object.
|
|
|
|
```python
|
|
from fastmcp import FastMCP
|
|
from fastmcp.server.dependencies import TokenClaim
|
|
|
|
mcp = FastMCP("Demo")
|
|
|
|
|
|
@mcp.tool
|
|
async def add_expense(
|
|
amount: float,
|
|
user_id: str = TokenClaim("oid"), # Azure object ID
|
|
) -> dict:
|
|
await db.insert({"user_id": user_id, "amount": amount})
|
|
return {"status": "created", "user_id": user_id}
|
|
```
|
|
|
|
`TokenClaim()` raises a `RuntimeError` if the claim doesn't exist, listing available claims to help with debugging.
|
|
|
|
Common claims vary by identity provider:
|
|
|
|
| Provider | User ID Claim | Email Claim | Name Claim |
|
|
|----------|--------------|-------------|------------|
|
|
| Azure/Entra | `oid` | `email` | `name` |
|
|
| GitHub | `sub` | `email` | `name` |
|
|
| Google | `sub` | `email` | `name` |
|
|
| Auth0 | `sub` | `email` | `name` |
|
|
|
|
### Background Task Dependencies
|
|
|
|
<VersionBadge version="2.3.0" />
|
|
|
|
For background task execution, FastMCP provides dependencies that integrate with [Docket](https://github.com/chrisguidry/docket). These require installing `fastmcp[tasks]`.
|
|
|
|
```python
|
|
from fastmcp import FastMCP
|
|
from fastmcp.dependencies import CurrentDocket, CurrentWorker, Progress
|
|
|
|
mcp = FastMCP("Task Demo")
|
|
|
|
|
|
@mcp.tool(task=True)
|
|
async def long_running_task(
|
|
data: str,
|
|
docket=CurrentDocket(),
|
|
worker=CurrentWorker(),
|
|
progress=Progress(),
|
|
) -> str:
|
|
await progress.set_total(100)
|
|
|
|
for i in range(100):
|
|
# Process chunk...
|
|
await progress.increment()
|
|
await progress.set_message(f"Processing chunk {i + 1}")
|
|
|
|
return "Complete"
|
|
```
|
|
|
|
- **`CurrentDocket()`**: Access the Docket instance for scheduling additional background work
|
|
- **`CurrentWorker()`**: Access the worker processing tasks (name, concurrency settings)
|
|
- **`Progress()`**: Track task progress with atomic updates
|
|
|
|
<Note>
|
|
Task dependencies require `pip install 'fastmcp[tasks]'`. They're only available within task-enabled components (`task=True`). For comprehensive task patterns, see the [Docket documentation](https://chrisguidry.github.io/docket/dependencies/).
|
|
</Note>
|
|
|
|
## Custom Dependencies
|
|
|
|
Beyond the built-in dependencies, you can create your own to inject configuration, database connections, API clients, or any other values your functions need.
|
|
|
|
### Using Depends()
|
|
|
|
The `Depends()` function wraps any callable and injects its return value. This works with synchronous functions, async functions, and async context managers.
|
|
|
|
```python
|
|
from fastmcp import FastMCP
|
|
from fastmcp.dependencies import Depends
|
|
|
|
mcp = FastMCP("Custom Deps Demo")
|
|
|
|
|
|
def get_config() -> dict:
|
|
return {"api_url": "https://api.example.com", "timeout": 30}
|
|
|
|
|
|
async def get_user_id() -> int:
|
|
# Could fetch from database, external service, etc.
|
|
return 42
|
|
|
|
|
|
@mcp.tool
|
|
async def fetch_data(
|
|
query: str,
|
|
config: dict = Depends(get_config),
|
|
user_id: int = Depends(get_user_id),
|
|
) -> str:
|
|
return f"User {user_id} fetching '{query}' from {config['api_url']}"
|
|
```
|
|
|
|
### Caching
|
|
|
|
Dependencies are cached per-request. If multiple parameters use the same dependency, or if nested dependencies share a common dependency, it's resolved once and the same instance is reused.
|
|
|
|
```python
|
|
from fastmcp import FastMCP
|
|
from fastmcp.dependencies import Depends
|
|
|
|
mcp = FastMCP("Caching Demo")
|
|
|
|
|
|
def get_db_connection():
|
|
print("Connecting to database...") # Only printed once per request
|
|
return {"connection": "active"}
|
|
|
|
|
|
def get_user_repo(db=Depends(get_db_connection)):
|
|
return {"db": db, "type": "user"}
|
|
|
|
|
|
def get_order_repo(db=Depends(get_db_connection)):
|
|
return {"db": db, "type": "order"}
|
|
|
|
|
|
@mcp.tool
|
|
async def process_order(
|
|
order_id: str,
|
|
users=Depends(get_user_repo),
|
|
orders=Depends(get_order_repo),
|
|
) -> str:
|
|
# Both repos share the same db connection
|
|
return f"Processed order {order_id}"
|
|
```
|
|
|
|
### Resource Management
|
|
|
|
For dependencies that need cleanup—database connections, file handles, HTTP clients—use an async context manager. The cleanup code runs after your function completes, even if an error occurs.
|
|
|
|
```python
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastmcp import FastMCP
|
|
from fastmcp.dependencies import Depends
|
|
|
|
mcp = FastMCP("Resource Demo")
|
|
|
|
|
|
@asynccontextmanager
|
|
async def get_database():
|
|
db = await connect_to_database()
|
|
try:
|
|
yield db
|
|
finally:
|
|
await db.close()
|
|
|
|
|
|
@mcp.tool
|
|
async def query_users(sql: str, db=Depends(get_database)) -> list:
|
|
return await db.execute(sql)
|
|
```
|
|
|
|
### Nested Dependencies
|
|
|
|
Dependencies can depend on other dependencies. FastMCP resolves them in the correct order and applies caching across the dependency tree.
|
|
|
|
```python
|
|
from fastmcp import FastMCP
|
|
from fastmcp.dependencies import Depends
|
|
|
|
mcp = FastMCP("Nested Demo")
|
|
|
|
|
|
def get_base_url() -> str:
|
|
return "https://api.example.com"
|
|
|
|
|
|
def get_api_client(base_url: str = Depends(get_base_url)) -> dict:
|
|
return {"base_url": base_url, "version": "v1"}
|
|
|
|
|
|
@mcp.tool
|
|
async def call_api(endpoint: str, client: dict = Depends(get_api_client)) -> str:
|
|
return f"Calling {client['base_url']}/{client['version']}/{endpoint}"
|
|
```
|
|
|
|
For advanced dependency patterns—like `TaskArgument()` for accessing task parameters, or custom `Dependency` subclasses—see the [Docket dependency documentation](https://chrisguidry.github.io/docket/dependencies/).
|