mirror of
https://github.com/mcp-use/mcp-use.git
synced 2026-09-14 18:06:01 +08:00
d789a92b4b
* Fix DNS rebinding protection for cloud/proxy deployments
- Add host/port parameters to MCPServer.__init__() and pass to FastMCP
- Default host to "0.0.0.0" (cloud-friendly, disables DNS protection)
- "127.0.0.1" auto-enables DNS protection (handled by FastMCP)
- run() can override host/port, falls back to __init__ values
- Add comprehensive docstring to __init__() explaining parameters
- Update documentation
This fixes 421 Misdirected Request errors when running behind reverse
proxies (Cloudflare, nginx, etc.) where the Host header doesn't match
the auto-configured allowed_hosts for localhost.
* Fix DNS rebinding protection for cloud/proxy deployments
- Add host/port parameters to MCPServer.__init__() and pass to FastMCP
- Default host to "0.0.0.0" (cloud-friendly, disables DNS protection)
- "127.0.0.1" auto-enables DNS protection (handled by FastMCP)
- run() can override host/port and reconfigures DNS protection accordingly
- Rebuild app when host changes in run() to apply new security settings
- Add comprehensive docstring to __init__() explaining parameters
- Add unit tests for host/port configuration and run() overrides
- Update documentation
This fixes 421 Misdirected Request errors when running behind reverse
proxies (Cloudflare, nginx, etc.) where the Host header doesn't match
the auto-configured allowed_hosts for localhost.
* ruff lint
* Bump mcp minimum version to 1.23.0
Required for TransportSecuritySettings and DNS rebinding protection
auto-configuration feature that was added in v1.23.0.
* feat(server): add bearer token authentication for Python MCP server
Add HTTP-layer bearer token authentication that returns proper 401 responses.
- BearerAuthProvider: abstract base class with verify_token() method
- AccessToken: model with token, claims, and scopes fields
- AuthMiddleware: Starlette middleware returning HTTP 401 for invalid tokens
- get_access_token() / require_auth(): context helpers for tools
Usage:
```python
class MyAuth(BearerAuthProvider):
async def verify_token(self, token: str) -> AccessToken | None:
if token != "secret":
return None
return AccessToken(token=token, claims={"user": "alice"})
server = MCPServer(name="my-server", auth=MyAuth())
```
Closes #840
* autogenerate docs
* test(server): add unit and integration tests for bearer token auth
Unit tests:
- AccessToken model creation and serialization
- BearerAuthProvider abstract class and subclassing
- get_access_token/require_auth context helpers
- AuthMiddleware 401 responses, path exclusion, CORS
Integration tests:
- MCPClient connecting to bearer auth server with valid token
- Connection failure with invalid/missing token
- Tool access to user claims and scopes
* fix(server): address code review feedback for bearer auth
- Fix CORS security: allow_credentials=False with allow_origins=["*"]
- Remove dead try/except import for auth module
- Remove PII (email) from debug logs
- Add comment explaining broad exception handling in verify_token
- Add /openmcp.json to docs excluded paths list
- Remove set_access_token from public API (internal only)
- Create AuthenticationError instead of using ValueError
- Skip middleware tests on older httpx versions (TestClient compat)
* docs: update API documentation for auth module
* fix(server): address Copilot review feedback
- Fix bearer.mdx: require_auth raises AuthenticationError, not ValueError
- Fix API docs: claims and scopes are optional with defaults, not required
- Remove deprecated SSE transport from bearer_auth_server.py test server
* autogenerate docs
* fix(server): address renvins review feedback
- Improve bearer.mdx claims documentation with explanation and examples
- Remove unnecessary cast() for AuthMiddleware in server.py
- Refactor auth tests to use pytest fixtures for cleaner setup/teardown
* docs(auth): clarify that all MCP endpoints are automatically protected
- Add "What Gets Protected" section explaining automatic protection
- Clarify that get_access_token/require_auth are for accessing token data, not protection
- Update examples to show proper usage pattern
- Improve Note at bottom to be clearer
* fix(auth): prevent path prefix bypass in auth middleware
Security fix: Use exact path matching or segment-based prefix matching
instead of simple startswith() to prevent auth bypass attacks.
Before: /docs-secret would bypass auth because it starts with /docs
After: Only /docs and /docs/* bypass auth, /docs-secret requires auth
Added tests for path matching security edge cases.
* refactor(auth): update import paths for authentication components
- Changed import statements in multiple documentation files and source files to reflect the new structure, moving authentication-related imports to the `auth` submodule.
- Updated examples to ensure clarity and consistency in usage patterns for `BearerAuthProvider`, `AccessToken`, `get_access_token`, and `require_auth`.
201 lines
5.3 KiB
Plaintext
201 lines
5.3 KiB
Plaintext
---
|
|
title: Bearer Token
|
|
description: Protect your MCP server with bearer token authentication
|
|
icon: key
|
|
---
|
|
|
|
Protect your MCP server with bearer token authentication. Invalid tokens return HTTP 401 responses before reaching your MCP handlers.
|
|
|
|
## What Gets Protected
|
|
|
|
When you configure `auth=MyAuthProvider()`, **all MCP endpoints are automatically protected**:
|
|
|
|
- All tools
|
|
- All resources
|
|
- All prompts
|
|
|
|
No additional code is needed in individual tools. The middleware validates the token at the HTTP layer and rejects invalid requests with 401 before your code runs.
|
|
|
|
## Quick Start
|
|
|
|
```python
|
|
from mcp_use.server import MCPServer
|
|
from mcp_use.server.auth import BearerAuthProvider, AccessToken, get_access_token
|
|
|
|
API_KEYS = {
|
|
"sk-abc123": {"email": "alice@example.com"},
|
|
"sk-def456": {"email": "bob@example.com"},
|
|
}
|
|
|
|
|
|
class MyAuthProvider(BearerAuthProvider):
|
|
async def verify_token(self, token: str) -> AccessToken | None:
|
|
if token not in API_KEYS:
|
|
return None
|
|
return AccessToken(token=token, claims=API_KEYS[token])
|
|
|
|
|
|
server = MCPServer(name="my-server", auth=MyAuthProvider())
|
|
|
|
|
|
@server.tool()
|
|
def whoami() -> str:
|
|
"""Get current user info."""
|
|
token = get_access_token()
|
|
return f"Hello {token.claims.get('email')}" if token else "Not authenticated"
|
|
```
|
|
|
|
## How It Works
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant Client
|
|
participant AuthMiddleware
|
|
participant BearerAuthProvider
|
|
participant Tool
|
|
|
|
Client->>AuthMiddleware: Request + Authorization: Bearer <token>
|
|
AuthMiddleware->>BearerAuthProvider: verify_token(token)
|
|
|
|
alt Valid token
|
|
BearerAuthProvider-->>AuthMiddleware: AccessToken
|
|
AuthMiddleware->>Tool: Request proceeds
|
|
Tool-->>Client: 200 Response
|
|
else Invalid/missing token
|
|
BearerAuthProvider-->>AuthMiddleware: None
|
|
AuthMiddleware-->>Client: 401 Unauthorized
|
|
end
|
|
```
|
|
|
|
## BearerAuthProvider
|
|
|
|
Extend this class and implement `verify_token()`:
|
|
|
|
```python
|
|
from mcp_use.server.auth import BearerAuthProvider, AccessToken
|
|
|
|
|
|
class MyAuthProvider(BearerAuthProvider):
|
|
async def verify_token(self, token: str) -> AccessToken | None:
|
|
# Return AccessToken if valid, None if invalid
|
|
...
|
|
```
|
|
|
|
## AccessToken
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `token` | `str` | The original bearer token |
|
|
| `claims` | `dict[str, Any]` | User info dictionary |
|
|
| `scopes` | `list[str]` | Permission list (default: `[]`) |
|
|
|
|
### claims
|
|
|
|
The `claims` dictionary stores user information that your tools can access during request handling. You decide what to include based on your application's needs.
|
|
|
|
**Common fields:**
|
|
- `sub` - User ID (standard JWT claim)
|
|
- `email` - User's email address
|
|
- `name` - Display name
|
|
- Custom fields like `plan`, `org_id`, `role`, etc.
|
|
|
|
```python
|
|
AccessToken(
|
|
token=token,
|
|
claims={
|
|
"sub": "user-123",
|
|
"email": "alice@example.com",
|
|
"name": "Alice",
|
|
"plan": "pro",
|
|
},
|
|
)
|
|
```
|
|
|
|
**Accessing claims in tools:**
|
|
|
|
```python
|
|
@server.tool()
|
|
def get_user_plan() -> str:
|
|
token = get_access_token()
|
|
if not token:
|
|
return "Not authenticated"
|
|
|
|
email = token.claims.get("email", "unknown")
|
|
plan = token.claims.get("plan", "free")
|
|
return f"{email} is on the {plan} plan"
|
|
```
|
|
|
|
### scopes
|
|
|
|
Use for permission checks in your tools:
|
|
|
|
```python
|
|
AccessToken(
|
|
token=token,
|
|
claims={"email": "alice@example.com"},
|
|
scopes=["read", "write", "admin"],
|
|
)
|
|
```
|
|
|
|
Check in tools via `"admin" in token.scopes`.
|
|
|
|
## Accessing Token Data in Tools
|
|
|
|
Since the middleware already validates authentication, these helpers are for **accessing user information** from the token - not for protection.
|
|
|
|
| Function | Purpose |
|
|
|----------|---------|
|
|
| `get_access_token()` | Returns `AccessToken` or `None`. Use when auth is optional or to access claims. |
|
|
| `require_auth()` | Returns `AccessToken` or raises `AuthenticationError`. Use as a safeguard. |
|
|
|
|
```python
|
|
from mcp_use.server.auth import get_access_token, require_auth
|
|
|
|
|
|
@server.tool()
|
|
def get_user_data() -> dict:
|
|
# User is already authenticated (middleware validated the token)
|
|
# We're just accessing their info here
|
|
token = get_access_token()
|
|
return {"email": token.claims.get("email")}
|
|
|
|
|
|
@server.tool()
|
|
def admin_action() -> str:
|
|
# require_auth() is a safeguard - useful if you want to be explicit
|
|
# or if the endpoint might be called from an excluded path
|
|
token = require_auth()
|
|
if "admin" not in token.scopes:
|
|
return "Admin scope required"
|
|
return "Done"
|
|
```
|
|
|
|
## Client Configuration
|
|
|
|
```json
|
|
{
|
|
"mcpServers": {
|
|
"my-server": {
|
|
"url": "http://localhost:8000/mcp",
|
|
"headers": {
|
|
"Authorization": "Bearer your-api-key"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## HTTP Responses
|
|
|
|
| Scenario | Status | Response |
|
|
|----------|--------|----------|
|
|
| No token | 401 | `{"error": "unauthorized", "error_description": "Authentication required"}` |
|
|
| Invalid token | 401 | `{"error": "invalid_token", "error_description": "Token is invalid or expired"}` |
|
|
| Valid token | 200 | Request proceeds |
|
|
|
|
All 401 responses include the `WWW-Authenticate: Bearer` header.
|
|
|
|
<Note>
|
|
All MCP protocol traffic (`/mcp/*`) requires authentication - this includes all tools, resources, and prompts. Debug paths like `/docs`, `/inspector`, `/health`, and `/openmcp.json` are excluded by default.
|
|
</Note>
|