From c6768dad5f321a6719a658d65b0da9a0e9322c40 Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Mon, 11 Aug 2025 13:01:44 -0400 Subject: [PATCH] Add documentation for get_access_token() dependency function (#1446) Co-authored-by: Jeremiah Lowin Co-authored-by: marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com> --- docs/docs.json | 1 - docs/patterns/http-requests.mdx | 87 ----------- docs/servers/context.mdx | 144 +++++++++++++++++- src/fastmcp/server/auth/__init__.py | 10 +- src/fastmcp/server/auth/providers/jwt.py | 3 +- src/fastmcp/server/auth/registry.py | 2 +- src/fastmcp/server/dependencies.py | 2 +- src/fastmcp/server/http.py | 2 +- src/fastmcp/server/server.py | 2 +- .../server/auth/test_remote_auth_provider.py | 2 +- .../server/auth/test_static_token_verifier.py | 2 +- tests/server/http/test_bearer_auth_backend.py | 2 +- 12 files changed, 159 insertions(+), 100 deletions(-) delete mode 100644 docs/patterns/http-requests.mdx diff --git a/docs/docs.json b/docs/docs.json index 7dd4b3961..157ff9b3f 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -162,7 +162,6 @@ "pages": [ "patterns/tool-transformation", "patterns/decorating-methods", - "patterns/http-requests", "patterns/testing", "patterns/cli", "patterns/contrib" diff --git a/docs/patterns/http-requests.mdx b/docs/patterns/http-requests.mdx deleted file mode 100644 index c9b412c5d..000000000 --- a/docs/patterns/http-requests.mdx +++ /dev/null @@ -1,87 +0,0 @@ ---- -title: HTTP Requests -sidebarTitle: HTTP Requests -description: Accessing and using HTTP requests in FastMCP servers -icon: network-wired ---- -import { VersionBadge } from '/snippets/version-badge.mdx' - - - -## Overview - -When running FastMCP as a web server, your MCP tools, resources, and prompts might need to access the underlying HTTP request information, such as headers, client IP, or query parameters. - -FastMCP provides a clean way to access HTTP request information through a dependency function. - -## Accessing HTTP Requests - -The recommended way to access the current HTTP request is through the `get_http_request()` dependency function: - -```python {2, 3, 11} -from fastmcp import FastMCP -from fastmcp.server.dependencies import get_http_request -from starlette.requests import Request - -mcp = FastMCP(name="HTTP Request Demo") - -@mcp.tool -async def user_agent_info() -> dict: - """Return information about the user agent.""" - # Get the HTTP request - request: Request = get_http_request() - - # Access request data - user_agent = request.headers.get("user-agent", "Unknown") - client_ip = request.client.host if request.client else "Unknown" - - return { - "user_agent": user_agent, - "client_ip": client_ip, - "path": request.url.path, - } -``` - -This approach works anywhere within a request's execution flow, not just within your MCP function. It's useful when: - -1. You need access to HTTP information in helper functions -2. You're calling nested functions that need HTTP request data -3. You're working with middleware or other request processing code - -## Accessing HTTP Headers Only - -If you only need request headers and want to avoid potential errors, you can use the `get_http_headers()` helper: - -```python {2} -from fastmcp import FastMCP -from fastmcp.server.dependencies import get_http_headers - -mcp = FastMCP(name="Headers Demo") - -@mcp.tool -async def safe_header_info() -> dict: - """Safely get header information without raising errors.""" - # Get headers (returns empty dict if no request context) - headers = get_http_headers() - - # Get authorization header - auth_header = headers.get("authorization", "") - is_bearer = auth_header.startswith("Bearer ") - - return { - "user_agent": headers.get("user-agent", "Unknown"), - "content_type": headers.get("content-type", "Unknown"), - "has_auth": bool(auth_header), - "auth_type": "Bearer" if is_bearer else "Other" if auth_header else "None", - "headers_count": len(headers) - } -``` - -By default, `get_http_headers()` excludes problematic headers like `host` and `content-length`. To include all headers, use `get_http_headers(include_all=True)`. - -## Important Notes - -- HTTP requests are only available when FastMCP is running as part of a web application -- Accessing the HTTP request with `get_http_request()` outside of a web request context will raise a `RuntimeError` -- The `get_http_headers()` function **never raises errors** - it returns an empty dict when no request context is available -- The `get_http_request()` function returns a standard [Starlette Request](https://www.starlette.io/requests/) object \ No newline at end of file diff --git a/docs/servers/context.mdx b/docs/servers/context.mdx index 5b766f458..c6dd8d10a 100644 --- a/docs/servers/context.mdx +++ b/docs/servers/context.mdx @@ -89,7 +89,7 @@ async def data_analysis_request(dataset: str, ctx: Context) -> str: ``` -### Via Dependency Function +### Via Runtime Dependency Function @@ -285,4 +285,144 @@ async def request_info(ctx: Context) -> dict: The MCP request is part of the low-level MCP SDK and intended for advanced use cases. Most users will not need to use it directly. - \ No newline at end of file + + +## Runtime Dependencies + +### HTTP Requests + + + +The recommended way to access the current HTTP request is through the `get_http_request()` dependency function: + +```python {2, 3, 11} +from fastmcp import FastMCP +from fastmcp.server.dependencies import get_http_request +from starlette.requests import Request + +mcp = FastMCP(name="HTTP Request Demo") + +@mcp.tool +async def user_agent_info() -> dict: + """Return information about the user agent.""" + # Get the HTTP request + request: Request = get_http_request() + + # Access request data + user_agent = request.headers.get("user-agent", "Unknown") + client_ip = request.client.host if request.client else "Unknown" + + return { + "user_agent": user_agent, + "client_ip": client_ip, + "path": request.url.path, + } +``` + +This approach works anywhere within a request's execution flow, not just within your MCP function. It's useful when: + +1. You need access to HTTP information in helper functions +2. You're calling nested functions that need HTTP request data +3. You're working with middleware or other request processing code + +### HTTP Headers + + +If you only need request headers and want to avoid potential errors, you can use the `get_http_headers()` helper: + +```python {2, 10} +from fastmcp import FastMCP +from fastmcp.server.dependencies import get_http_headers + +mcp = FastMCP(name="Headers Demo") + +@mcp.tool +async def safe_header_info() -> dict: + """Safely get header information without raising errors.""" + # Get headers (returns empty dict if no request context) + headers = get_http_headers() + + # Get authorization header + auth_header = headers.get("authorization", "") + is_bearer = auth_header.startswith("Bearer ") + + return { + "user_agent": headers.get("user-agent", "Unknown"), + "content_type": headers.get("content-type", "Unknown"), + "has_auth": bool(auth_header), + "auth_type": "Bearer" if is_bearer else "Other" if auth_header else "None", + "headers_count": len(headers) + } +``` + +By default, `get_http_headers()` excludes problematic headers like `host` and `content-length`. To include all headers, use `get_http_headers(include_all=True)`. + +### Access Tokens + + + +When using authentication with your FastMCP server, you can access the authenticated user's access token information using the `get_access_token()` dependency function: + +```python {2, 10} +from fastmcp import FastMCP +from fastmcp.server.dependencies import get_access_token, AccessToken + +mcp = FastMCP(name="Auth Token Demo") + +@mcp.tool +async def get_user_info() -> dict: + """Get information about the authenticated user.""" + # Get the access token (None if not authenticated) + token: AccessToken | None = get_access_token() + + if token is None: + return {"authenticated": False} + + return { + "authenticated": True, + "client_id": token.client_id, + "scopes": token.scopes, + "expires_at": token.expires_at, + "token_claims": token.claims, # JWT claims or custom token data + } +``` + +This is particularly useful when you need to: + +1. **Access user identification** - Get the `client_id` or subject from token claims +2. **Check permissions** - Verify scopes or custom claims before performing operations +3. **Multi-tenant applications** - Extract tenant information from token claims +4. **Audit logging** - Track which user performed which actions + +#### Working with Token Claims + +The `claims` field contains all the data from the original token (JWT claims for JWT tokens, or custom data for other token types): + +```python {2, 3, 9, 12, 15} +from fastmcp import FastMCP +from fastmcp.server.dependencies import get_access_token + +mcp = FastMCP(name="Multi-tenant Demo") + +@mcp.tool +async def get_tenant_data(resource_id: str) -> dict: + """Get tenant-specific data using token claims.""" + token: AccessToken | None = get_access_token() + + # Extract tenant ID from token claims + tenant_id = token.claims.get("tenant_id") if token else None + + # Extract user ID from standard JWT subject claim + user_id = token.claims.get("sub") if token else None + + # Use tenant and user info to authorize and filter data + if not tenant_id: + raise ValueError("No tenant information in token") + + return { + "resource_id": resource_id, + "tenant_id": tenant_id, + "user_id": user_id, + "data": f"Tenant-specific data for {tenant_id}", + } +``` diff --git a/src/fastmcp/server/auth/__init__.py b/src/fastmcp/server/auth/__init__.py index c0b25e049..cdc3310ae 100644 --- a/src/fastmcp/server/auth/__init__.py +++ b/src/fastmcp/server/auth/__init__.py @@ -1,13 +1,21 @@ -from .auth import OAuthProvider, TokenVerifier, RemoteAuthProvider +from .auth import ( + OAuthProvider, + TokenVerifier, + RemoteAuthProvider, + AccessToken, + AuthProvider, +) from .providers.jwt import JWTVerifier, StaticTokenVerifier __all__ = [ + "AuthProvider", "OAuthProvider", "TokenVerifier", "JWTVerifier", "StaticTokenVerifier", "RemoteAuthProvider", + "AccessToken", ] diff --git a/src/fastmcp/server/auth/providers/jwt.py b/src/fastmcp/server/auth/providers/jwt.py index bb4470603..0da7cdac1 100644 --- a/src/fastmcp/server/auth/providers/jwt.py +++ b/src/fastmcp/server/auth/providers/jwt.py @@ -15,8 +15,7 @@ from pydantic import AnyHttpUrl, SecretStr from pydantic_settings import BaseSettings, SettingsConfigDict from typing_extensions import TypedDict -from fastmcp.server.auth import TokenVerifier -from fastmcp.server.auth.auth import AccessToken +from fastmcp.server.auth import AccessToken, TokenVerifier from fastmcp.server.auth.registry import register_provider from fastmcp.utilities.logging import get_logger from fastmcp.utilities.types import NotSet, NotSetT diff --git a/src/fastmcp/server/auth/registry.py b/src/fastmcp/server/auth/registry.py index 9cbf9eb5e..8eab2ab76 100644 --- a/src/fastmcp/server/auth/registry.py +++ b/src/fastmcp/server/auth/registry.py @@ -6,7 +6,7 @@ from collections.abc import Callable from typing import TYPE_CHECKING, TypeVar if TYPE_CHECKING: - from fastmcp.server.auth.auth import AuthProvider + from fastmcp.server.auth import AuthProvider # Type variable for auth providers T = TypeVar("T", bound="AuthProvider") diff --git a/src/fastmcp/server/dependencies.py b/src/fastmcp/server/dependencies.py index 9e08622ce..5b54b80b0 100644 --- a/src/fastmcp/server/dependencies.py +++ b/src/fastmcp/server/dependencies.py @@ -7,7 +7,7 @@ from mcp.server.auth.middleware.auth_context import ( ) from starlette.requests import Request -from fastmcp.server.auth.auth import AccessToken +from fastmcp.server.auth import AccessToken if TYPE_CHECKING: from fastmcp.server.context import Context diff --git a/src/fastmcp/server/http.py b/src/fastmcp/server/http.py index 13c13ea73..83da5752d 100644 --- a/src/fastmcp/server/http.py +++ b/src/fastmcp/server/http.py @@ -23,7 +23,7 @@ from starlette.responses import Response from starlette.routing import BaseRoute, Mount, Route from starlette.types import Lifespan, Receive, Scope, Send -from fastmcp.server.auth.auth import AuthProvider +from fastmcp.server.auth import AuthProvider from fastmcp.utilities.logging import get_logger if TYPE_CHECKING: diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py index 6727428cd..40a5159dd 100644 --- a/src/fastmcp/server/server.py +++ b/src/fastmcp/server/server.py @@ -49,7 +49,7 @@ from fastmcp.prompts import Prompt, PromptManager from fastmcp.prompts.prompt import FunctionPrompt from fastmcp.resources import Resource, ResourceManager from fastmcp.resources.template import ResourceTemplate -from fastmcp.server.auth.auth import AuthProvider +from fastmcp.server.auth import AuthProvider from fastmcp.server.auth.registry import get_registered_provider from fastmcp.server.http import ( StarletteWithLifespan, diff --git a/tests/server/auth/test_remote_auth_provider.py b/tests/server/auth/test_remote_auth_provider.py index 8400452fb..dae2c39ec 100644 --- a/tests/server/auth/test_remote_auth_provider.py +++ b/tests/server/auth/test_remote_auth_provider.py @@ -3,7 +3,7 @@ import pytest from pydantic import AnyHttpUrl from fastmcp import FastMCP -from fastmcp.server.auth.auth import AccessToken, RemoteAuthProvider, TokenVerifier +from fastmcp.server.auth import AccessToken, RemoteAuthProvider, TokenVerifier class SimpleTokenVerifier(TokenVerifier): diff --git a/tests/server/auth/test_static_token_verifier.py b/tests/server/auth/test_static_token_verifier.py index e4f6df75a..2f07a674f 100644 --- a/tests/server/auth/test_static_token_verifier.py +++ b/tests/server/auth/test_static_token_verifier.py @@ -3,7 +3,7 @@ import httpx from fastmcp.server import FastMCP -from fastmcp.server.auth.auth import AccessToken +from fastmcp.server.auth import AccessToken from fastmcp.server.auth.providers.jwt import StaticTokenVerifier diff --git a/tests/server/http/test_bearer_auth_backend.py b/tests/server/http/test_bearer_auth_backend.py index a3469733d..a1aaf77aa 100644 --- a/tests/server/http/test_bearer_auth_backend.py +++ b/tests/server/http/test_bearer_auth_backend.py @@ -4,7 +4,7 @@ import pytest from mcp.server.auth.middleware.bearer_auth import BearerAuthBackend from starlette.requests import HTTPConnection -from fastmcp.server.auth.auth import AccessToken +from fastmcp.server.auth import AccessToken from fastmcp.server.auth.providers.jwt import JWTVerifier, RSAKeyPair