diff --git a/docs/clients/sampling.mdx b/docs/clients/sampling.mdx
index a2e0e9942..1c7f37d1d 100644
--- a/docs/clients/sampling.mdx
+++ b/docs/clients/sampling.mdx
@@ -155,11 +155,11 @@ Install the Anthropic handler with `pip install fastmcp[anthropic]`.
```python
from fastmcp import Client
-from fastmcp.client.sampling.handlers.google_genai import GoogleGenAISamplingHandler
+from fastmcp.client.sampling.handlers.google_genai import GoogleGenaiSamplingHandler
client = Client(
"my_mcp_server.py",
- sampling_handler=GoogleGenAISamplingHandler(default_model="gemini-2.0-flash"),
+ sampling_handler=GoogleGenaiSamplingHandler(default_model="gemini-2.0-flash"),
)
```
diff --git a/docs/deployment/http.mdx b/docs/deployment/http.mdx
index d2078db1b..54132eb0e 100644
--- a/docs/deployment/http.mdx
+++ b/docs/deployment/http.mdx
@@ -660,17 +660,17 @@ FASTMCP_STATELESS_HTTP=true uvicorn app:app --host 0.0.0.0 --port 8000 --workers
Production deployments should never hardcode sensitive information like API keys or authentication tokens. Instead, use environment variables to configure your server at runtime. This keeps your code secure and makes it easy to deploy the same code to different environments with different configurations.
-Here's an example using bearer token authentication (though OAuth is recommended for production):
+Here's an example using static token authentication for development (OAuth is recommended for production):
```python
import os
from fastmcp import FastMCP
-from fastmcp.server.auth import BearerTokenAuth
+from fastmcp.server.auth import StaticTokenVerifier
# Read configuration from environment
auth_token = os.environ.get("MCP_AUTH_TOKEN")
if auth_token:
- auth = BearerTokenAuth(token=auth_token)
+ auth = StaticTokenVerifier(tokens={auth_token: {"sub": "admin", "client_id": "cli"}})
mcp = FastMCP("Production Server", auth=auth)
else:
mcp = FastMCP("Production Server")
diff --git a/docs/development/v3-notes/v3-features.mdx b/docs/development/v3-notes/v3-features.mdx
index da9fc87db..476349a01 100644
--- a/docs/development/v3-notes/v3-features.mdx
+++ b/docs/development/v3-notes/v3-features.mdx
@@ -29,7 +29,7 @@ FastMCP now includes a sampling handler for Google's Gemini models ([#2977](http
```python
from fastmcp import Client
-from fastmcp.client.sampling.handlers import GoogleGenaiSamplingHandler
+from fastmcp.client.sampling.handlers.google_genai import GoogleGenaiSamplingHandler
from google.genai import Client as GoogleGenaiClient
# Initialize the handler
@@ -386,7 +386,7 @@ Support for [MCP Apps](https://modelcontextprotocol.io/specification/2025-06-18/
```python
from fastmcp import FastMCP
-from fastmcp.server.apps import AppConfig, ResourceCSP, ResourcePermissions
+from fastmcp.apps import AppConfig, ResourceCSP, ResourcePermissions
mcp = FastMCP("My Server")
@@ -421,7 +421,7 @@ The `app=` parameter accepts `True` (enable with defaults), an `AppConfig` insta
```python
from fastmcp import Context
-from fastmcp.server.apps import AppConfig, UI_EXTENSION_ID
+from fastmcp.apps import AppConfig, UI_EXTENSION_ID
@mcp.tool(app=AppConfig(resource_uri="ui://dashboard"))
async def dashboard(ctx: Context) -> dict:
@@ -575,15 +575,14 @@ provider.add_transform(ToolTransform({
```python
from collections.abc import Sequence
-from fastmcp.server.transforms import Transform, ListToolsNext, GetToolNext
+from fastmcp.server.transforms import Transform, GetToolNext
from fastmcp.tools import Tool
class TagFilter(Transform):
def __init__(self, required_tags: set[str]):
self.required_tags = required_tags
- async def list_tools(self, call_next: ListToolsNext) -> Sequence[Tool]:
- tools = await call_next() # Get tools from downstream
+ async def list_tools(self, tools: Sequence[Tool]) -> Sequence[Tool]:
return [t for t in tools if t.tags & self.required_tags]
async def get_tool(self, name: str, call_next: GetToolNext) -> Tool | None:
diff --git a/docs/getting-started/upgrading/from-fastmcp-2.mdx b/docs/getting-started/upgrading/from-fastmcp-2.mdx
index f4795fe15..1e659e76a 100644
--- a/docs/getting-started/upgrading/from-fastmcp-2.mdx
+++ b/docs/getting-started/upgrading/from-fastmcp-2.mdx
@@ -264,7 +264,7 @@ auth = GitHubProvider(
The deprecated WebSocket client transport has been removed. Use `StreamableHttpTransport` instead:
-```python
+```python test="skip"
# Before
from fastmcp.client.transports import WSTransport
transport = WSTransport("ws://localhost:8000/ws")
@@ -357,7 +357,7 @@ main.mount(subserver)
The proxy and OpenAPI modules have moved under `providers` to reflect v3's provider-based architecture:
-```python
+```python test="skip"
# Deprecated
from fastmcp.server.proxy import FastMCPProxy
from fastmcp.server.openapi import FastMCPOpenAPI
@@ -369,7 +369,7 @@ from fastmcp.server.providers.openapi import OpenAPIProvider
`FastMCPOpenAPI` itself is deprecated — use `FastMCP` with an `OpenAPIProvider` instead:
-```python
+```python test="skip"
# Deprecated
from fastmcp.server.openapi import FastMCPOpenAPI
server = FastMCPOpenAPI(spec, client)
@@ -408,7 +408,7 @@ proxy = create_proxy("http://example.com/mcp")
The experimental OpenAPI parser is now standard. Update imports:
-```python
+```python test="skip"
# Before
from fastmcp.experimental.server.openapi import FastMCPOpenAPI
diff --git a/docs/integrations/anthropic.mdx b/docs/integrations/anthropic.mdx
index b8156ac95..08b9b2c9c 100644
--- a/docs/integrations/anthropic.mdx
+++ b/docs/integrations/anthropic.mdx
@@ -181,7 +181,7 @@ if __name__ == "__main__":
If you try to call the authenticated server with the same Anthropic code we wrote earlier, you'll get an error indicating that the server rejected the request because it's not authenticated.
-```python
+```text
Error code: 400 - {
"type": "error",
"error": {
diff --git a/docs/integrations/descope.mdx b/docs/integrations/descope.mdx
index dba9b3509..bfb6cd9c8 100644
--- a/docs/integrations/descope.mdx
+++ b/docs/integrations/descope.mdx
@@ -63,8 +63,8 @@ from fastmcp.server.auth.providers.descope import DescopeProvider
# The DescopeProvider automatically discovers Descope endpoints
# and configures JWT token validation
auth_provider = DescopeProvider(
- config_url=https://.../.well-known/openid-configuration, # Your MCP Server .well-known URL
- base_url=SERVER_URL, # Your server's public URL
+ config_url="https://.../.well-known/openid-configuration", # Your MCP Server .well-known URL
+ base_url=SERVER_URL, # Your server's public URL
)
# Create FastMCP server with auth
diff --git a/docs/integrations/fastapi.mdx b/docs/integrations/fastapi.mdx
index abdc1d0e7..83aa924f8 100644
--- a/docs/integrations/fastapi.mdx
+++ b/docs/integrations/fastapi.mdx
@@ -220,7 +220,7 @@ Because FastMCP's FastAPI integration is based on its [OpenAPI integration](/int
```python
# Assumes the FastAPI app from above is already defined
from fastmcp import FastMCP
-from fastmcp.server.openapi import RouteMap, MCPType
+from fastmcp.server.providers.openapi import RouteMap, MCPType
# Custom mapping rules
mcp = FastMCP.from_fastapi(
diff --git a/docs/integrations/openai.mdx b/docs/integrations/openai.mdx
index 6f88193f5..94ca82b40 100644
--- a/docs/integrations/openai.mdx
+++ b/docs/integrations/openai.mdx
@@ -178,8 +178,8 @@ if __name__ == "__main__":
If you try to call the authenticated server with the same OpenAI code we wrote earlier, you'll get an error like this:
-```python
-pythonAPIStatusError: Error code: 424 - {
+```text
+APIStatusError: Error code: 424 - {
"error": {
"message": "Error retrieving tool list from MCP server: 'dice_server'. Http status code: 401 (Unauthorized)",
"type": "external_connector_error",
diff --git a/docs/integrations/openapi.mdx b/docs/integrations/openapi.mdx
index 88dc19b14..f5f2b3dfa 100644
--- a/docs/integrations/openapi.mdx
+++ b/docs/integrations/openapi.mdx
@@ -85,7 +85,7 @@ Each `RouteMap` specifies a combination of methods, patterns, and tags, as well
Here is FastMCP's default rule:
```python
-from fastmcp.server.openapi import RouteMap, MCPType
+from fastmcp.server.providers.openapi import RouteMap, MCPType
DEFAULT_ROUTE_MAPPINGS = [
# All routes become tools
@@ -101,7 +101,7 @@ For example, prior to FastMCP 2.8.0, GET requests were automatically mapped to `
```python
from fastmcp import FastMCP
-from fastmcp.server.openapi import RouteMap, MCPType
+from fastmcp.server.providers.openapi import RouteMap, MCPType
# Restore pre-2.8.0 semantic mapping
semantic_maps = [
@@ -124,7 +124,7 @@ Here is a more complete example that uses custom route maps to convert all `GET`
```python
from fastmcp import FastMCP
-from fastmcp.server.openapi import RouteMap, MCPType
+from fastmcp.server.providers.openapi import RouteMap, MCPType
mcp = FastMCP.from_openapi(
openapi_spec=spec,
@@ -164,7 +164,7 @@ You can use this to remove sensitive or internal routes by targeting them specif
```python
from fastmcp import FastMCP
-from fastmcp.server.openapi import RouteMap, MCPType
+from fastmcp.server.providers.openapi import RouteMap, MCPType
mcp = FastMCP.from_openapi(
openapi_spec=spec,
@@ -180,7 +180,7 @@ Or you can use a catch-all rule to exclude everything that your maps don't handl
```python
from fastmcp import FastMCP
-from fastmcp.server.openapi import RouteMap, MCPType
+from fastmcp.server.providers.openapi import RouteMap, MCPType
mcp = FastMCP.from_openapi(
openapi_spec=spec,
@@ -212,7 +212,8 @@ The `route_map_fn` is called on all routes, even those that matched `MCPType.EXC
```python
from fastmcp import FastMCP
-from fastmcp.server.openapi import RouteMap, MCPType, HTTPRoute
+from fastmcp.server.providers.openapi import RouteMap, MCPType
+from fastmcp.utilities.openapi import HTTPRoute
def custom_route_mapper(route: HTTPRoute, mcp_type: MCPType) -> MCPType | None:
"""Advanced route type mapping."""
@@ -277,7 +278,7 @@ FastMCP provides several ways to add tags to your MCP components, allowing you t
You can add custom tags to components created from specific routes using the `mcp_tags` parameter in `RouteMap`. These tags will be applied to all components created from routes that match that particular route map.
```python
-from fastmcp.server.openapi import RouteMap, MCPType
+from fastmcp.server.providers.openapi import RouteMap, MCPType
mcp = FastMCP.from_openapi(
openapi_spec=spec,
@@ -368,12 +369,12 @@ Your `mcp_component_fn` is expected to modify the component in-place, not to ret
```python
-from fastmcp.server.openapi import (
- HTTPRoute,
+from fastmcp.server.providers.openapi import (
OpenAPITool,
OpenAPIResource,
OpenAPIResourceTemplate,
)
+from fastmcp.utilities.openapi import HTTPRoute
def customize_components(
route: HTTPRoute,
diff --git a/docs/patterns/contrib.mdx b/docs/patterns/contrib.mdx
index d2f812f52..da757662e 100644
--- a/docs/patterns/contrib.mdx
+++ b/docs/patterns/contrib.mdx
@@ -18,7 +18,7 @@ The available modules can be viewed in the [contrib directory](https://github.co
To use a contrib module, import it from the `fastmcp.contrib` package:
-```python
+```python test="skip"
from fastmcp.contrib import my_module
```
diff --git a/docs/servers/auth/authentication.mdx b/docs/servers/auth/authentication.mdx
index 9a26df138..d37c57f36 100644
--- a/docs/servers/auth/authentication.mdx
+++ b/docs/servers/auth/authentication.mdx
@@ -161,7 +161,7 @@ The implementation provides all required OAuth endpoints including authorization
```python
from fastmcp import FastMCP
-from fastmcp.server.auth.providers.oauth import MyOAuthProvider
+from fastmcp.server.auth import OAuthProvider
auth = MyOAuthProvider(
user_store=your_user_database,
diff --git a/docs/servers/prompts.mdx b/docs/servers/prompts.mdx
index bdfefc24e..41c50ce33 100644
--- a/docs/servers/prompts.mdx
+++ b/docs/servers/prompts.mdx
@@ -262,7 +262,7 @@ Message(["item1", "item2"])
`PromptResult` gives you explicit control over prompt responses: multiple messages, roles, and metadata at both the message and result level.
-```python
+```python test="skip"
from fastmcp import FastMCP
from fastmcp.prompts import PromptResult, Message
diff --git a/docs/servers/providers/filesystem.mdx b/docs/servers/providers/filesystem.mdx
index a5b798faa..95dba3b3b 100644
--- a/docs/servers/providers/filesystem.mdx
+++ b/docs/servers/providers/filesystem.mdx
@@ -114,7 +114,7 @@ The decorator supports: `uri` (required), `name`, `title`, `description`, `icons
Mark a function as a prompt template.
-```python
+```python test="skip"
from fastmcp.prompts import prompt
@prompt
diff --git a/docs/servers/storage-backends.mdx b/docs/servers/storage-backends.mdx
index e743b5dab..d13ce176d 100644
--- a/docs/servers/storage-backends.mdx
+++ b/docs/servers/storage-backends.mdx
@@ -248,7 +248,7 @@ The [FastMCP Client](/clients/client) uses storage for persisting OAuth tokens l
```python
from pathlib import Path
-from fastmcp.client.auth import OAuthClientProvider
+from fastmcp.client.auth import OAuth
from key_value.aio.stores.filetree import (
FileTreeStore,
FileTreeV1KeySanitizationStrategy,
@@ -263,7 +263,7 @@ token_storage = FileTreeStore(
collection_sanitization_strategy=FileTreeV1CollectionSanitizationStrategy(token_dir),
)
-oauth_provider = OAuthClientProvider(
+oauth_provider = OAuth(
mcp_url="https://your-mcp-server.com/mcp/sse",
token_storage=token_storage
)
diff --git a/docs/tutorials/rest-api.mdx b/docs/tutorials/rest-api.mdx
index a0857aca9..90872c950 100644
--- a/docs/tutorials/rest-api.mdx
+++ b/docs/tutorials/rest-api.mdx
@@ -152,7 +152,7 @@ Here’s how you can add custom route maps to turn `GET` requests into `Resource
```python api_server_with_resources.py {3, 37-42}
import httpx
from fastmcp import FastMCP
-from fastmcp.server.openapi import RouteMap, MCPType
+from fastmcp.server.providers.openapi import RouteMap, MCPType
# Create an HTTP client for the target API
diff --git a/docs/v2/deployment/http.mdx b/docs/v2/deployment/http.mdx
index eafa773b3..fa91c65e0 100644
--- a/docs/v2/deployment/http.mdx
+++ b/docs/v2/deployment/http.mdx
@@ -650,17 +650,17 @@ FASTMCP_STATELESS_HTTP=true uvicorn app:app --host 0.0.0.0 --port 8000 --workers
Production deployments should never hardcode sensitive information like API keys or authentication tokens. Instead, use environment variables to configure your server at runtime. This keeps your code secure and makes it easy to deploy the same code to different environments with different configurations.
-Here's an example using bearer token authentication (though OAuth is recommended for production):
+Here's an example using static token authentication for development (OAuth is recommended for production):
```python
import os
from fastmcp import FastMCP
-from fastmcp.server.auth import BearerTokenAuth
+from fastmcp.server.auth import StaticTokenVerifier
# Read configuration from environment
auth_token = os.environ.get("MCP_AUTH_TOKEN")
if auth_token:
- auth = BearerTokenAuth(token=auth_token)
+ auth = StaticTokenVerifier(tokens={auth_token: {"sub": "admin", "client_id": "cli"}})
mcp = FastMCP("Production Server", auth=auth)
else:
mcp = FastMCP("Production Server")
diff --git a/docs/v2/development/upgrade-guide.mdx b/docs/v2/development/upgrade-guide.mdx
index 2e98b4a02..93b314829 100644
--- a/docs/v2/development/upgrade-guide.mdx
+++ b/docs/v2/development/upgrade-guide.mdx
@@ -19,11 +19,11 @@ The experimental OpenAPI parser is now the standard implementation. The legacy p
**If you were using the experimental parser:** Update your imports from the experimental module to the standard location:
-```python Before
+```python test="skip" Before
from fastmcp.experimental.server.openapi import FastMCPOpenAPI, RouteMap, MCPType
```
-```python After
+```python test="skip" After
from fastmcp.server.openapi import FastMCPOpenAPI, RouteMap, MCPType
```
@@ -36,7 +36,7 @@ The following deprecated features have been removed in v2.14.0:
**BearerAuthProvider** (deprecated in v2.11):
-```python Before
+```python test="skip" Before
from fastmcp.server.auth.providers.bearer import BearerAuthProvider
```
@@ -47,7 +47,7 @@ from fastmcp.server.auth.providers.jwt import JWTVerifier
**Context.get_http_request()** (deprecated in v2.2.11):
-```python Before
+```python test="skip" Before
request = context.get_http_request()
```
@@ -59,7 +59,7 @@ request = get_http_request()
**Top-level Image import** (deprecated in v2.8.1):
-```python Before
+```python test="skip" Before
from fastmcp import Image
```
diff --git a/docs/v2/integrations/anthropic.mdx b/docs/v2/integrations/anthropic.mdx
index 7490bcf35..7d2d38dc1 100644
--- a/docs/v2/integrations/anthropic.mdx
+++ b/docs/v2/integrations/anthropic.mdx
@@ -181,7 +181,7 @@ if __name__ == "__main__":
If you try to call the authenticated server with the same Anthropic code we wrote earlier, you'll get an error indicating that the server rejected the request because it's not authenticated.
-```python
+```text
Error code: 400 - {
"type": "error",
"error": {
diff --git a/docs/v2/integrations/descope.mdx b/docs/v2/integrations/descope.mdx
index abba9069d..14bade5f4 100644
--- a/docs/v2/integrations/descope.mdx
+++ b/docs/v2/integrations/descope.mdx
@@ -64,8 +64,8 @@ from fastmcp.server.auth.providers.descope import DescopeProvider
# The DescopeProvider automatically discovers Descope endpoints
# and configures JWT token validation
auth_provider = DescopeProvider(
- config_url=https://.../.well-known/openid-configuration, # Your MCP Server .well-known URL
- base_url=SERVER_URL, # Your server's public URL
+ config_url="https://.../.well-known/openid-configuration", # Your MCP Server .well-known URL
+ base_url=SERVER_URL, # Your server's public URL
)
# Create FastMCP server with auth
diff --git a/docs/v2/integrations/fastapi.mdx b/docs/v2/integrations/fastapi.mdx
index 3737345f1..67d5d06de 100644
--- a/docs/v2/integrations/fastapi.mdx
+++ b/docs/v2/integrations/fastapi.mdx
@@ -216,7 +216,7 @@ Because FastMCP's FastAPI integration is based on its [OpenAPI integration](/v2/
```python
# Assumes the FastAPI app from above is already defined
from fastmcp import FastMCP
-from fastmcp.server.openapi import RouteMap, MCPType
+from fastmcp.server.providers.openapi import RouteMap, MCPType
# Custom mapping rules
mcp = FastMCP.from_fastapi(
diff --git a/docs/v2/integrations/openai.mdx b/docs/v2/integrations/openai.mdx
index 63b5e28b4..af41528f9 100644
--- a/docs/v2/integrations/openai.mdx
+++ b/docs/v2/integrations/openai.mdx
@@ -178,8 +178,8 @@ if __name__ == "__main__":
If you try to call the authenticated server with the same OpenAI code we wrote earlier, you'll get an error like this:
-```python
-pythonAPIStatusError: Error code: 424 - {
+```text
+APIStatusError: Error code: 424 - {
"error": {
"message": "Error retrieving tool list from MCP server: 'dice_server'. Http status code: 401 (Unauthorized)",
"type": "external_connector_error",
diff --git a/docs/v2/integrations/openapi.mdx b/docs/v2/integrations/openapi.mdx
index 8662a0c05..0fd38cbd0 100644
--- a/docs/v2/integrations/openapi.mdx
+++ b/docs/v2/integrations/openapi.mdx
@@ -81,7 +81,7 @@ Each `RouteMap` specifies a combination of methods, patterns, and tags, as well
Here is FastMCP's default rule:
```python
-from fastmcp.server.openapi import RouteMap, MCPType
+from fastmcp.server.providers.openapi import RouteMap, MCPType
DEFAULT_ROUTE_MAPPINGS = [
# All routes become tools
@@ -97,7 +97,7 @@ For example, prior to FastMCP 2.8.0, GET requests were automatically mapped to `
```python
from fastmcp import FastMCP
-from fastmcp.server.openapi import RouteMap, MCPType
+from fastmcp.server.providers.openapi import RouteMap, MCPType
# Restore pre-2.8.0 semantic mapping
semantic_maps = [
@@ -120,7 +120,7 @@ Here is a more complete example that uses custom route maps to convert all `GET`
```python
from fastmcp import FastMCP
-from fastmcp.server.openapi import RouteMap, MCPType
+from fastmcp.server.providers.openapi import RouteMap, MCPType
mcp = FastMCP.from_openapi(
openapi_spec=spec,
@@ -160,7 +160,7 @@ You can use this to remove sensitive or internal routes by targeting them specif
```python
from fastmcp import FastMCP
-from fastmcp.server.openapi import RouteMap, MCPType
+from fastmcp.server.providers.openapi import RouteMap, MCPType
mcp = FastMCP.from_openapi(
openapi_spec=spec,
@@ -176,7 +176,7 @@ Or you can use a catch-all rule to exclude everything that your maps don't handl
```python
from fastmcp import FastMCP
-from fastmcp.server.openapi import RouteMap, MCPType
+from fastmcp.server.providers.openapi import RouteMap, MCPType
mcp = FastMCP.from_openapi(
openapi_spec=spec,
@@ -208,7 +208,8 @@ The `route_map_fn` is called on all routes, even those that matched `MCPType.EXC
```python
from fastmcp import FastMCP
-from fastmcp.server.openapi import RouteMap, MCPType, HTTPRoute
+from fastmcp.server.providers.openapi import RouteMap, MCPType
+from fastmcp.utilities.openapi import HTTPRoute
def custom_route_mapper(route: HTTPRoute, mcp_type: MCPType) -> MCPType | None:
"""Advanced route type mapping."""
@@ -273,7 +274,7 @@ FastMCP provides several ways to add tags to your MCP components, allowing you t
You can add custom tags to components created from specific routes using the `mcp_tags` parameter in `RouteMap`. These tags will be applied to all components created from routes that match that particular route map.
```python
-from fastmcp.server.openapi import RouteMap, MCPType
+from fastmcp.server.providers.openapi import RouteMap, MCPType
mcp = FastMCP.from_openapi(
openapi_spec=spec,
@@ -364,12 +365,12 @@ Your `mcp_component_fn` is expected to modify the component in-place, not to ret
```python
-from fastmcp.server.openapi import (
- HTTPRoute,
+from fastmcp.server.providers.openapi import (
OpenAPITool,
OpenAPIResource,
OpenAPIResourceTemplate,
)
+from fastmcp.utilities.openapi import HTTPRoute
def customize_components(
route: HTTPRoute,
diff --git a/docs/v2/patterns/contrib.mdx b/docs/v2/patterns/contrib.mdx
index d2f812f52..da757662e 100644
--- a/docs/v2/patterns/contrib.mdx
+++ b/docs/v2/patterns/contrib.mdx
@@ -18,7 +18,7 @@ The available modules can be viewed in the [contrib directory](https://github.co
To use a contrib module, import it from the `fastmcp.contrib` package:
-```python
+```python test="skip"
from fastmcp.contrib import my_module
```
diff --git a/docs/v2/servers/auth/authentication.mdx b/docs/v2/servers/auth/authentication.mdx
index c42bfa8bf..c6b829bfe 100644
--- a/docs/v2/servers/auth/authentication.mdx
+++ b/docs/v2/servers/auth/authentication.mdx
@@ -161,7 +161,7 @@ The implementation provides all required OAuth endpoints including authorization
```python
from fastmcp import FastMCP
-from fastmcp.server.auth.providers.oauth import MyOAuthProvider
+from fastmcp.server.auth import OAuthProvider
auth = MyOAuthProvider(
user_store=your_user_database,
diff --git a/docs/v2/servers/proxy.mdx b/docs/v2/servers/proxy.mdx
index c60b628f7..b5ceb4b97 100644
--- a/docs/v2/servers/proxy.mdx
+++ b/docs/v2/servers/proxy.mdx
@@ -52,7 +52,7 @@ The recommended way to create a proxy is using `ProxyClient`, which provides ful
```python
from fastmcp import FastMCP
-from fastmcp.server.proxy import ProxyClient
+from fastmcp.server.providers.proxy import ProxyClient
# Create a proxy with full MCP feature support
proxy = FastMCP.as_proxy(
@@ -86,7 +86,7 @@ FastMCP proxies provide session isolation to ensure safe concurrent operations.
When you pass a disconnected client (which is the normal case), each request gets its own isolated backend session:
```python
-from fastmcp.server.proxy import ProxyClient
+from fastmcp.server.providers.proxy import ProxyClient
# Each request creates a fresh backend session (recommended)
proxy = FastMCP.as_proxy(ProxyClient("backend_server.py"))
@@ -121,7 +121,7 @@ A common use case is bridging transports - exposing a server running on one tran
```python
from fastmcp import FastMCP
-from fastmcp.server.proxy import ProxyClient
+from fastmcp.server.providers.proxy import ProxyClient
# Bridge remote SSE server to local stdio
remote_proxy = FastMCP.as_proxy(
@@ -164,7 +164,7 @@ if __name__ == "__main__":
- **Progress**: Forwards progress notifications during long operations
```python
-from fastmcp.server.proxy import ProxyClient
+from fastmcp.server.providers.proxy import ProxyClient
# ProxyClient automatically handles all these features
backend = ProxyClient("advanced_backend.py")
@@ -304,7 +304,7 @@ Internally, `FastMCP.as_proxy()` uses the `FastMCPProxy` class. You generally do
### Direct Usage
```python
-from fastmcp.server.proxy import FastMCPProxy, ProxyClient
+from fastmcp.server.providers.proxy import FastMCPProxy, ProxyClient
# Provide a client factory for explicit session control
def create_client():
diff --git a/docs/v2/servers/storage-backends.mdx b/docs/v2/servers/storage-backends.mdx
index cd14ab80e..25b8580b0 100644
--- a/docs/v2/servers/storage-backends.mdx
+++ b/docs/v2/servers/storage-backends.mdx
@@ -236,13 +236,13 @@ middleware = ResponseCachingMiddleware(cache_storage=namespaced_store)
The [FastMCP Client](/v2/clients/client) uses storage for persisting OAuth tokens locally. By default, tokens are stored in memory:
```python
-from fastmcp.client.auth import OAuthClientProvider
+from fastmcp.client.auth import OAuth
from key_value.aio.stores.disk import DiskStore
# Store tokens on disk for persistence across restarts
token_storage = DiskStore(directory="~/.local/share/fastmcp/tokens")
-oauth_provider = OAuthClientProvider(
+oauth_provider = OAuth(
mcp_url="https://your-mcp-server.com/mcp/sse",
token_storage=token_storage
)
diff --git a/docs/v2/tutorials/rest-api.mdx b/docs/v2/tutorials/rest-api.mdx
index 362eb0026..6524a2335 100644
--- a/docs/v2/tutorials/rest-api.mdx
+++ b/docs/v2/tutorials/rest-api.mdx
@@ -152,7 +152,7 @@ Here’s how you can add custom route maps to turn `GET` requests into `Resource
```python api_server_with_resources.py {3, 37-42}
import httpx
from fastmcp import FastMCP
-from fastmcp.server.openapi import RouteMap, MCPType
+from fastmcp.server.providers.openapi import RouteMap, MCPType
# Create an HTTP client for the target API
diff --git a/pyproject.toml b/pyproject.toml
index 3e44dfeeb..0ad8e258a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -89,6 +89,7 @@ dev = [
"prek>=0.2.12",
"loq>=0.1.0a3",
"opentelemetry-exporter-otlp-proto-grpc>=1.39.0",
+ "pytest-examples>=0.0.18",
]
[project.scripts]
@@ -222,4 +223,4 @@ known-first-party = ["fastmcp"]
[tool.codespell]
-ignore-words-list = "asend,shttp,te"
\ No newline at end of file
+ignore-words-list = "asend,shttp,te"
diff --git a/tests/docs/test_doc_examples.py b/tests/docs/test_doc_examples.py
new file mode 100644
index 000000000..9e1a8e4ea
--- /dev/null
+++ b/tests/docs/test_doc_examples.py
@@ -0,0 +1,146 @@
+"""Validate Python code examples in FastMCP documentation.
+
+Extracts code blocks from .mdx docs and checks:
+1. Syntax — every example parses as valid Python
+2. FastMCP imports — every ``from fastmcp.x import y`` resolves
+
+Supports tags in code fence prefix (for future use):
+ ```python test="skip" — skip all checks
+ ```python lint="skip" — skip all checks
+
+Run:
+ uv run pytest tests/docs/test_doc_examples.py -v -s
+"""
+
+from __future__ import annotations
+
+import ast
+from pathlib import Path
+from uuid import uuid4
+
+from pytest_examples import CodeExample
+from pytest_examples.find_examples import _extract_code_chunks
+
+DOCS_DIR = Path("docs")
+_SKIP_DIRS = {"python-sdk", "public"}
+
+# Snapshot baselines — ratchet DOWN as doc examples are fixed.
+MAX_SYNTAX_FAILURES = 0
+MAX_IMPORT_FAILURES = 0
+
+
+def _find_mdx_examples() -> list[CodeExample]:
+ """Find Python code examples in .mdx files.
+
+ pytest-examples only supports ``.md``; we call its internal
+ ``_extract_code_chunks`` directly so ``.mdx`` works without copying.
+ """
+ examples: list[CodeExample] = []
+ for mdx_file in sorted(DOCS_DIR.rglob("*.mdx")):
+ rel = mdx_file.relative_to(DOCS_DIR)
+ if rel.parts and rel.parts[0] in _SKIP_DIRS:
+ continue
+ code = mdx_file.read_text("utf-8")
+ group = uuid4()
+ examples.extend(_extract_code_chunks(mdx_file, code, group))
+ return examples
+
+
+def _should_skip(example: CodeExample) -> bool:
+ settings = example.prefix_settings()
+ return settings.get("lint") == "skip" or settings.get("test") == "skip"
+
+
+def _check_syntax(example: CodeExample) -> str | None:
+ """Return error description if syntax is invalid, else None."""
+ try:
+ ast.parse(example.source)
+ return None
+ except SyntaxError as e:
+ rel = Path(example.path).relative_to(DOCS_DIR)
+ return f"{rel}:{example.start_line}: line {e.lineno}: {e.msg}"
+
+
+def _check_fastmcp_imports(example: CodeExample) -> list[str]:
+ """Return list of broken fastmcp import descriptions."""
+ try:
+ tree = ast.parse(example.source)
+ except SyntaxError:
+ return []
+
+ errors: list[str] = []
+ rel = Path(example.path).relative_to(DOCS_DIR)
+
+ for node in ast.walk(tree):
+ if isinstance(node, ast.Import):
+ for alias in node.names:
+ if alias.name.startswith("fastmcp"):
+ try:
+ __import__(alias.name)
+ except ImportError:
+ errors.append(
+ f"{rel}:{example.start_line}: cannot import '{alias.name}'"
+ )
+ elif isinstance(node, ast.ImportFrom):
+ if node.module and node.module.startswith("fastmcp"):
+ names = [a.name for a in node.names]
+ try:
+ mod = __import__(node.module, fromlist=names)
+ except ImportError:
+ errors.append(
+ f"{rel}:{example.start_line}: "
+ f"cannot import module '{node.module}'"
+ )
+ continue
+ for name in names:
+ if not hasattr(mod, name):
+ errors.append(
+ f"{rel}:{example.start_line}: "
+ f"'{node.module}' has no '{name}'"
+ )
+ return errors
+
+
+def test_doc_examples_quality():
+ """Doc examples should not regress in syntax or import correctness.
+
+ Checks every Python code block in ``docs/*.mdx`` (excluding
+ auto-generated ``python-sdk/`` and ``public/`` directories).
+ Reports all failures and asserts counts don't exceed known baselines.
+ """
+ examples = _find_mdx_examples()
+ syntax_failures: list[str] = []
+ import_failures: list[str] = []
+
+ for ex in examples:
+ if _should_skip(ex):
+ continue
+
+ err = _check_syntax(ex)
+ if err:
+ syntax_failures.append(err)
+ continue
+
+ import_failures.extend(_check_fastmcp_imports(ex))
+
+ total = len(examples)
+ print(f"\nDoc examples checked: {total}")
+ print(f"Syntax failures: {len(syntax_failures)}")
+ print(f"Import failures: {len(import_failures)}")
+
+ if syntax_failures:
+ print("\nSyntax errors:")
+ for f in syntax_failures:
+ print(f" {f}")
+
+ if import_failures:
+ print("\nBroken imports:")
+ for f in import_failures:
+ print(f" {f}")
+
+ assert len(syntax_failures) <= MAX_SYNTAX_FAILURES, (
+ f"Syntax failures regressed: {len(syntax_failures)} > {MAX_SYNTAX_FAILURES}"
+ )
+ assert len(import_failures) <= MAX_IMPORT_FAILURES, (
+ f"Import failures regressed: {len(import_failures)} > {MAX_IMPORT_FAILURES}"
+ )
diff --git a/uv.lock b/uv.lock
index 6be552064..ecdefcde8 100644
--- a/uv.lock
+++ b/uv.lock
@@ -1,5 +1,5 @@
version = 1
-revision = 3
+revision = 2
requires-python = ">=3.10"
resolution-markers = [
"python_full_version >= '3.14'",
@@ -167,6 +167,50 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl", hash = "sha256:d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2", size = 1333658, upload-time = "2025-12-13T06:50:28.266Z" },
]
+[[package]]
+name = "black"
+version = "26.3.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "click" },
+ { name = "mypy-extensions" },
+ { name = "packaging" },
+ { name = "pathspec" },
+ { name = "platformdirs" },
+ { name = "pytokens" },
+ { name = "tomli", marker = "python_full_version < '3.11'" },
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e1/c5/61175d618685d42b005847464b8fb4743a67b1b8fdb75e50e5a96c31a27a/black-26.3.1.tar.gz", hash = "sha256:2c50f5063a9641c7eed7795014ba37b0f5fa227f3d408b968936e24bc0566b07", size = 666155, upload-time = "2026-03-12T03:36:03.593Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/32/a8/11170031095655d36ebc6664fe0897866f6023892396900eec0e8fdc4299/black-26.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:86a8b5035fce64f5dcd1b794cf8ec4d31fe458cf6ce3986a30deb434df82a1d2", size = 1866562, upload-time = "2026-03-12T03:39:58.639Z" },
+ { url = "https://files.pythonhosted.org/packages/69/ce/9e7548d719c3248c6c2abfd555d11169457cbd584d98d179111338423790/black-26.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5602bdb96d52d2d0672f24f6ffe5218795736dd34807fd0fd55ccd6bf206168b", size = 1703623, upload-time = "2026-03-12T03:40:00.347Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/0a/8d17d1a9c06f88d3d030d0b1d4373c1551146e252afe4547ed601c0e697f/black-26.3.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c54a4a82e291a1fee5137371ab488866b7c86a3305af4026bdd4dc78642e1ac", size = 1768388, upload-time = "2026-03-12T03:40:01.765Z" },
+ { url = "https://files.pythonhosted.org/packages/52/79/c1ee726e221c863cde5164f925bacf183dfdf0397d4e3f94889439b947b4/black-26.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:6e131579c243c98f35bce64a7e08e87fb2d610544754675d4a0e73a070a5aa3a", size = 1412969, upload-time = "2026-03-12T03:40:03.252Z" },
+ { url = "https://files.pythonhosted.org/packages/73/a5/15c01d613f5756f68ed8f6d4ec0a1e24b82b18889fa71affd3d1f7fad058/black-26.3.1-cp310-cp310-win_arm64.whl", hash = "sha256:5ed0ca58586c8d9a487352a96b15272b7fa55d139fc8496b519e78023a8dab0a", size = 1220345, upload-time = "2026-03-12T03:40:04.892Z" },
+ { url = "https://files.pythonhosted.org/packages/17/57/5f11c92861f9c92eb9dddf515530bc2d06db843e44bdcf1c83c1427824bc/black-26.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:28ef38aee69e4b12fda8dba75e21f9b4f979b490c8ac0baa7cb505369ac9e1ff", size = 1851987, upload-time = "2026-03-12T03:40:06.248Z" },
+ { url = "https://files.pythonhosted.org/packages/54/aa/340a1463660bf6831f9e39646bf774086dbd8ca7fc3cded9d59bbdf4ad0a/black-26.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bf162ed91a26f1adba8efda0b573bc6924ec1408a52cc6f82cb73ec2b142c", size = 1689499, upload-time = "2026-03-12T03:40:07.642Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/01/b726c93d717d72733da031d2de10b92c9fa4c8d0c67e8a8a372076579279/black-26.3.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:474c27574d6d7037c1bc875a81d9be0a9a4f9ee95e62800dab3cfaadbf75acd5", size = 1754369, upload-time = "2026-03-12T03:40:09.279Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/09/61e91881ca291f150cfc9eb7ba19473c2e59df28859a11a88248b5cbbc4d/black-26.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:5e9d0d86df21f2e1677cc4bd090cd0e446278bcbbe49bf3659c308c3e402843e", size = 1413613, upload-time = "2026-03-12T03:40:10.943Z" },
+ { url = "https://files.pythonhosted.org/packages/16/73/544f23891b22e7efe4d8f812371ab85b57f6a01b2fc45e3ba2e52ba985b8/black-26.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:9a5e9f45e5d5e1c5b5c29b3bd4265dcc90e8b92cf4534520896ed77f791f4da5", size = 1219719, upload-time = "2026-03-12T03:40:12.597Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/f8/da5eae4fc75e78e6dceb60624e1b9662ab00d6b452996046dfa9b8a6025b/black-26.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e6f89631eb88a7302d416594a32faeee9fb8fb848290da9d0a5f2903519fc1", size = 1895920, upload-time = "2026-03-12T03:40:13.921Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/9f/04e6f26534da2e1629b2b48255c264cabf5eedc5141d04516d9d68a24111/black-26.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cd2012d35b47d589cb8a16faf8a32ef7a336f56356babd9fcf70939ad1897f", size = 1718499, upload-time = "2026-03-12T03:40:15.239Z" },
+ { url = "https://files.pythonhosted.org/packages/04/91/a5935b2a63e31b331060c4a9fdb5a6c725840858c599032a6f3aac94055f/black-26.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f76ff19ec5297dd8e66eb64deda23631e642c9393ab592826fd4bdc97a4bce7", size = 1794994, upload-time = "2026-03-12T03:40:17.124Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/0a/86e462cdd311a3c2a8ece708d22aba17d0b2a0d5348ca34b40cdcbea512e/black-26.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ddb113db38838eb9f043623ba274cfaf7d51d5b0c22ecb30afe58b1bb8322983", size = 1420867, upload-time = "2026-03-12T03:40:18.83Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/e5/22515a19cb7eaee3440325a6b0d95d2c0e88dd180cb011b12ae488e031d1/black-26.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:dfdd51fc3e64ea4f35873d1b3fb25326773d55d2329ff8449139ebaad7357efb", size = 1230124, upload-time = "2026-03-12T03:40:20.425Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/77/5728052a3c0450c53d9bb3945c4c46b91baa62b2cafab6801411b6271e45/black-26.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:855822d90f884905362f602880ed8b5df1b7e3ee7d0db2502d4388a954cc8c54", size = 1895034, upload-time = "2026-03-12T03:40:21.813Z" },
+ { url = "https://files.pythonhosted.org/packages/52/73/7cae55fdfdfbe9d19e9a8d25d145018965fe2079fa908101c3733b0c55a0/black-26.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8a33d657f3276328ce00e4d37fe70361e1ec7614da5d7b6e78de5426cb56332f", size = 1718503, upload-time = "2026-03-12T03:40:23.666Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/87/af89ad449e8254fdbc74654e6467e3c9381b61472cc532ee350d28cfdafb/black-26.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1cd08e99d2f9317292a311dfe578fd2a24b15dbce97792f9c4d752275c1fa56", size = 1793557, upload-time = "2026-03-12T03:40:25.497Z" },
+ { url = "https://files.pythonhosted.org/packages/43/10/d6c06a791d8124b843bf325ab4ac7d2f5b98731dff84d6064eafd687ded1/black-26.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:c7e72339f841b5a237ff14f7d3880ddd0fc7f98a1199e8c4327f9a4f478c1839", size = 1422766, upload-time = "2026-03-12T03:40:27.14Z" },
+ { url = "https://files.pythonhosted.org/packages/59/4f/40a582c015f2d841ac24fed6390bd68f0fc896069ff3a886317959c9daf8/black-26.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:afc622538b430aa4c8c853f7f63bc582b3b8030fd8c80b70fb5fa5b834e575c2", size = 1232140, upload-time = "2026-03-12T03:40:28.882Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/da/e36e27c9cebc1311b7579210df6f1c86e50f2d7143ae4fcf8a5017dc8809/black-26.3.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2d6bfaf7fd0993b420bed691f20f9492d53ce9a2bcccea4b797d34e947318a78", size = 1889234, upload-time = "2026-03-12T03:40:30.964Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/7b/9871acf393f64a5fa33668c19350ca87177b181f44bb3d0c33b2d534f22c/black-26.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f89f2ab047c76a9c03f78d0d66ca519e389519902fa27e7a91117ef7611c0568", size = 1720522, upload-time = "2026-03-12T03:40:32.346Z" },
+ { url = "https://files.pythonhosted.org/packages/03/87/e766c7f2e90c07fb7586cc787c9ae6462b1eedab390191f2b7fc7f6170a9/black-26.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b07fc0dab849d24a80a29cfab8d8a19187d1c4685d8a5e6385a5ce323c1f015f", size = 1787824, upload-time = "2026-03-12T03:40:33.636Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/94/2424338fb2d1875e9e83eed4c8e9c67f6905ec25afd826a911aea2b02535/black-26.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:0126ae5b7c09957da2bdbd91a9ba1207453feada9e9fe51992848658c6c8e01c", size = 1445855, upload-time = "2026-03-12T03:40:35.442Z" },
+ { url = "https://files.pythonhosted.org/packages/86/43/0c3338bd928afb8ee7471f1a4eec3bdbe2245ccb4a646092a222e8669840/black-26.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:92c0ec1f2cc149551a2b7b47efc32c866406b6891b0ee4625e95967c8f4acfb1", size = 1258109, upload-time = "2026-03-12T03:40:36.832Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/0d/52d98722666d6fc6c3dd4c76df339501d6efd40e0ff95e6186a7b7f0befd/black-26.3.1-py3-none-any.whl", hash = "sha256:2bd5aa94fc267d38bb21a70d7410a89f1a1d318841855f698746f8e7f51acd1b", size = 207542, upload-time = "2026-03-12T03:36:01.668Z" },
+]
+
[[package]]
name = "cachetools"
version = "7.0.5"
@@ -851,6 +895,7 @@ dev = [
{ name = "pytest-asyncio" },
{ name = "pytest-cov" },
{ name = "pytest-env" },
+ { name = "pytest-examples" },
{ name = "pytest-flakefinder" },
{ name = "pytest-httpx" },
{ name = "pytest-report" },
@@ -914,6 +959,7 @@ dev = [
{ name = "pytest-asyncio", specifier = ">=1.2.0" },
{ name = "pytest-cov", specifier = ">=6.1.1" },
{ name = "pytest-env", specifier = ">=1.1.5" },
+ { name = "pytest-examples", specifier = ">=0.0.18" },
{ name = "pytest-flakefinder", specifier = ">=1.1.0" },
{ name = "pytest-httpx", specifier = ">=0.35.0" },
{ name = "pytest-report", specifier = ">=0.2.1" },
@@ -1626,6 +1672,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583, upload-time = "2025-03-14T23:51:03.016Z" },
]
+[[package]]
+name = "mypy-extensions"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
+]
+
[[package]]
name = "openai"
version = "2.30.0"
@@ -1766,6 +1821,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/52/96/5a770e5c461462575474468e5af931cff9de036e7c2b4fea23c1c58d2cbe/pathable-0.5.0-py3-none-any.whl", hash = "sha256:646e3d09491a6351a0c82632a09c02cdf70a252e73196b36d8a15ba0a114f0a6", size = 16867, upload-time = "2026-02-20T08:46:59.536Z" },
]
+[[package]]
+name = "pathspec"
+version = "1.0.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" },
+]
+
[[package]]
name = "pdbpp"
version = "0.12.1"
@@ -2402,6 +2466,20 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/27/16/ad52f56b96d851a2bcfdc1e754c3531341885bd7177a128c13ff2ca72ab4/pytest_env-1.6.0-py3-none-any.whl", hash = "sha256:1e7f8a62215e5885835daaed694de8657c908505b964ec8097a7ce77b403d9a3", size = 10400, upload-time = "2026-03-12T22:39:41.887Z" },
]
+[[package]]
+name = "pytest-examples"
+version = "0.0.18"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "black" },
+ { name = "pytest" },
+ { name = "ruff" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/af/71/4ae972fd95f474454aa450108ee1037830e7ba11840363e981b8d48fd16a/pytest_examples-0.0.18.tar.gz", hash = "sha256:9a464f007f805b113677a15e2f8942ebb92d7d3eb5312e9a405d018478ec9801", size = 21237, upload-time = "2025-05-06T07:46:10.705Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/09/52/7bbfb6e987d9a8a945f22941a8da63e3529465f1b106ef0e26f5df7c780d/pytest_examples-0.0.18-py3-none-any.whl", hash = "sha256:86c195b98c4e55049a0df3a0a990ca89123b7280473ab57608eecc6c47bcfe9c", size = 18169, upload-time = "2025-05-06T07:46:09.349Z" },
+]
+
[[package]]
name = "pytest-flakefinder"
version = "1.1.0"
@@ -2500,6 +2578,45 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" },
]
+[[package]]
+name = "pytokens"
+version = "0.4.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/42/24/f206113e05cb8ef51b3850e7ef88f20da6f4bf932190ceb48bd3da103e10/pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5", size = 161522, upload-time = "2026-01-30T01:02:50.393Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/e9/06a6bf1b90c2ed81a9c7d2544232fe5d2891d1cd480e8a1809ca354a8eb2/pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe", size = 246945, upload-time = "2026-01-30T01:02:52.399Z" },
+ { url = "https://files.pythonhosted.org/packages/69/66/f6fb1007a4c3d8b682d5d65b7c1fb33257587a5f782647091e3408abe0b8/pytokens-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c", size = 259525, upload-time = "2026-01-30T01:02:53.737Z" },
+ { url = "https://files.pythonhosted.org/packages/04/92/086f89b4d622a18418bac74ab5db7f68cf0c21cf7cc92de6c7b919d76c88/pytokens-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7", size = 262693, upload-time = "2026-01-30T01:02:54.871Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/7b/8b31c347cf94a3f900bdde750b2e9131575a61fdb620d3d3c75832262137/pytokens-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2", size = 103567, upload-time = "2026-01-30T01:02:56.414Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/92/790ebe03f07b57e53b10884c329b9a1a308648fc083a6d4a39a10a28c8fc/pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440", size = 160864, upload-time = "2026-01-30T01:02:57.882Z" },
+ { url = "https://files.pythonhosted.org/packages/13/25/a4f555281d975bfdd1eba731450e2fe3a95870274da73fb12c40aeae7625/pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc", size = 248565, upload-time = "2026-01-30T01:02:59.912Z" },
+ { url = "https://files.pythonhosted.org/packages/17/50/bc0394b4ad5b1601be22fa43652173d47e4c9efbf0044c62e9a59b747c56/pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d", size = 260824, upload-time = "2026-01-30T01:03:01.471Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/54/3e04f9d92a4be4fc6c80016bc396b923d2a6933ae94b5f557c939c460ee0/pytokens-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16", size = 264075, upload-time = "2026-01-30T01:03:04.143Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/1b/44b0326cb5470a4375f37988aea5d61b5cc52407143303015ebee94abfd6/pytokens-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6", size = 103323, upload-time = "2026-01-30T01:03:05.412Z" },
+ { url = "https://files.pythonhosted.org/packages/41/5d/e44573011401fb82e9d51e97f1290ceb377800fb4eed650b96f4753b499c/pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083", size = 160663, upload-time = "2026-01-30T01:03:06.473Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/e6/5bbc3019f8e6f21d09c41f8b8654536117e5e211a85d89212d59cbdab381/pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1", size = 255626, upload-time = "2026-01-30T01:03:08.177Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/3c/2d5297d82286f6f3d92770289fd439956b201c0a4fc7e72efb9b2293758e/pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1", size = 269779, upload-time = "2026-01-30T01:03:09.756Z" },
+ { url = "https://files.pythonhosted.org/packages/20/01/7436e9ad693cebda0551203e0bf28f7669976c60ad07d6402098208476de/pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9", size = 268076, upload-time = "2026-01-30T01:03:10.957Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/df/533c82a3c752ba13ae7ef238b7f8cdd272cf1475f03c63ac6cf3fcfb00b6/pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68", size = 103552, upload-time = "2026-01-30T01:03:12.066Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/dc/08b1a080372afda3cceb4f3c0a7ba2bde9d6a5241f1edb02a22a019ee147/pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b", size = 160720, upload-time = "2026-01-30T01:03:13.843Z" },
+ { url = "https://files.pythonhosted.org/packages/64/0c/41ea22205da480837a700e395507e6a24425151dfb7ead73343d6e2d7ffe/pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f", size = 254204, upload-time = "2026-01-30T01:03:14.886Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/d2/afe5c7f8607018beb99971489dbb846508f1b8f351fcefc225fcf4b2adc0/pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1", size = 268423, upload-time = "2026-01-30T01:03:15.936Z" },
+ { url = "https://files.pythonhosted.org/packages/68/d4/00ffdbd370410c04e9591da9220a68dc1693ef7499173eb3e30d06e05ed1/pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4", size = 266859, upload-time = "2026-01-30T01:03:17.458Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/c9/c3161313b4ca0c601eeefabd3d3b576edaa9afdefd32da97210700e47652/pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78", size = 103520, upload-time = "2026-01-30T01:03:18.652Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/a7/b470f672e6fc5fee0a01d9e75005a0e617e162381974213a945fcd274843/pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321", size = 160821, upload-time = "2026-01-30T01:03:19.684Z" },
+ { url = "https://files.pythonhosted.org/packages/80/98/e83a36fe8d170c911f864bfded690d2542bfcfacb9c649d11a9e6eb9dc41/pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa", size = 254263, upload-time = "2026-01-30T01:03:20.834Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/95/70d7041273890f9f97a24234c00b746e8da86df462620194cef1d411ddeb/pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d", size = 268071, upload-time = "2026-01-30T01:03:21.888Z" },
+ { url = "https://files.pythonhosted.org/packages/da/79/76e6d09ae19c99404656d7db9c35dfd20f2086f3eb6ecb496b5b31163bad/pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324", size = 271716, upload-time = "2026-01-30T01:03:23.633Z" },
+ { url = "https://files.pythonhosted.org/packages/79/37/482e55fa1602e0a7ff012661d8c946bafdc05e480ea5a32f4f7e336d4aa9/pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9", size = 104539, upload-time = "2026-01-30T01:03:24.788Z" },
+ { url = "https://files.pythonhosted.org/packages/30/e8/20e7db907c23f3d63b0be3b8a4fd1927f6da2395f5bcc7f72242bb963dfe/pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb", size = 168474, upload-time = "2026-01-30T01:03:26.428Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/81/88a95ee9fafdd8f5f3452107748fd04c24930d500b9aba9738f3ade642cc/pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3", size = 290473, upload-time = "2026-01-30T01:03:27.415Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", size = 303485, upload-time = "2026-01-30T01:03:28.558Z" },
+ { url = "https://files.pythonhosted.org/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", size = 306698, upload-time = "2026-01-30T01:03:29.653Z" },
+ { url = "https://files.pythonhosted.org/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", size = 116287, upload-time = "2026-01-30T01:03:30.912Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" },
+]
+
[[package]]
name = "pywin32"
version = "311"