diff --git a/app/en/references/mcp/python/errors/page.mdx b/app/en/references/mcp/python/errors/page.mdx
index ba240f2e7..308b676d5 100644
--- a/app/en/references/mcp/python/errors/page.mdx
+++ b/app/en/references/mcp/python/errors/page.mdx
@@ -3,6 +3,8 @@ title: "Errors - Arcade MCP Python Reference"
description: "Domain-specific error types raised by the MCP server and components"
---
+import { Callout } from "nextra/components";
+
# Errors
Domain-specific error types raised by the MCP server and components.
@@ -103,35 +105,162 @@ Error in session management.
Error in transport layer (stdio, HTTP, etc).
+## `arcade_core.errors`
+
+Tool execution errors with retry semantics. Re-exported via `arcade_mcp_server.exceptions`.
+
+### `RetryableToolError`
+
+**Bases:** `ToolExecutionError`
+
+The operation failed but can be retried.
+
+```python
+from arcade_mcp_server.exceptions import RetryableToolError
+
+# Simple retry
+raise RetryableToolError("Service temporarily unavailable")
+
+# With retry delay
+raise RetryableToolError(
+ "Rate limited",
+ retry_after_ms=5000
+)
+
+# With guidance for the AI
+raise RetryableToolError(
+ "Search returned no results",
+ additional_prompt_content="Try broader search terms or check spelling."
+)
+```
+
+### `FatalToolError`
+
+**Bases:** `ToolExecutionError`
+
+Unrecoverable error — the AI should not retry.
+
+```python
+from arcade_mcp_server.exceptions import FatalToolError
+
+raise FatalToolError("Account has been permanently deleted")
+
+# With developer-only details
+raise FatalToolError(
+ "Configuration error",
+ developer_message="Missing required API key in environment"
+)
+```
+
+### `ContextRequiredToolError`
+
+**Bases:** `ToolExecutionError`
+
+The operation needs additional context from the user before it can proceed.
+
+```python
+from arcade_mcp_server.exceptions import ContextRequiredToolError
+
+raise ContextRequiredToolError(
+ "Multiple users found matching 'John'",
+ additional_prompt_content="Please specify: John Smith (john@work.com) or John Doe (john@home.com)"
+)
+```
+
+### `UpstreamError`
+
+**Bases:** `ToolExecutionError`
+
+Error from an external API or service.
+
+```python
+from arcade_mcp_server.exceptions import UpstreamError
+
+raise UpstreamError(
+ "Slack API error: channel_not_found",
+ status_code=404
+)
+```
+
+### `UpstreamRateLimitError`
+
+**Bases:** `UpstreamError`
+
+Rate limit from an external API. Includes retry information.
+
+```python
+from arcade_mcp_server.exceptions import UpstreamRateLimitError
+
+raise UpstreamRateLimitError(
+ "Rate limited by Slack",
+ retry_after_ms=60_000
+)
+```
+
+
+ You rarely throw `UpstreamError` manually. Use [error adapters](/references/mcp/python/types#error-adapters) to automatically translate SDK errors.
+
+
+## Error Hierarchy
+
+```text
+MCPError (base)
+├── MCPContextError (user/input caused the error)
+│ ├── AuthorizationError
+│ ├── NotFoundError
+│ ├── PromptError
+│ └── ResourceError
+└── MCPRuntimeError (server/infrastructure caused the error)
+ ├── ProtocolError
+ ├── TransportError
+ └── ServerError
+ ├── RequestError
+ │ └── ServerRequestError
+ ├── ResponseError
+ ├── SessionError
+ └── LifespanError
+
+ToolkitError (base for tool errors)
+└── ToolError
+ └── ToolRuntimeError
+ └── ToolExecutionError
+ ├── RetryableToolError (can retry)
+ ├── FatalToolError (do not retry)
+ ├── ContextRequiredToolError (needs user input)
+ └── UpstreamError (external API failure)
+ └── UpstreamRateLimitError
+```
+
## Examples
### Raising exceptions for common error scenarios
```python
from arcade_mcp_server.exceptions import (
- MCPError,
NotFoundError,
- ValidationError,
- ToolError,
+ RetryableToolError,
+ FatalToolError,
)
-# Raising a not-found when a resource is missing
+# Resource not found
async def read_resource_or_fail(uri: str) -> str:
if not await exists(uri):
raise NotFoundError(f"Resource not found: {uri}")
return await read(uri)
-# Validating input
-def validate_age(age: int) -> None:
- if age < 0:
- raise ValidationError("age must be non-negative")
-
-# Handling tool execution errors in middleware or handlers
-async def call_tool_safely(call):
+# Retryable failure
+async def fetch_data(url: str):
try:
- return await call()
- except ToolError as e:
- # Convert to an error result or re-raise
- raise MCPError(f"Tool failed: {e}")
+ return await http_client.get(url)
+ except ConnectionError as e:
+ raise RetryableToolError(
+ "Failed to connect. Please try again.",
+ developer_message=str(e)
+ )
+
+# Unrecoverable failure
+def validate_config(config: dict):
+ if "api_key" not in config:
+ raise FatalToolError("Missing required configuration: api_key")
```
diff --git a/app/en/references/mcp/python/overview/page.mdx b/app/en/references/mcp/python/overview/page.mdx
index b4af2d23d..be86ed877 100644
--- a/app/en/references/mcp/python/overview/page.mdx
+++ b/app/en/references/mcp/python/overview/page.mdx
@@ -139,6 +139,84 @@ Decorator for adding tools with optional parameters.
#### Examples
+##### With OAuth Authentication
+
+Use `requires_auth` when your tool needs to act on behalf of a user. Add a `context: Context` parameter to access the authorization token.
+
+```python
+from arcade_mcp_server import MCPApp, Context
+from arcade_mcp_server.auth import Google
+from typing import Annotated
+
+app = MCPApp(name="my_server")
+
+@app.tool(requires_auth=Google(scopes=["profile", "email"]))
+async def get_profile(
+ user_id: Annotated[str, "The user ID to look up"],
+ context: Context,
+) -> dict:
+ """Get user profile from Google."""
+ token = context.authorization.token # OAuth access token
+ # Use token to call Google APIs...
+ return {"user_id": user_id}
+```
+
+Available auth providers:
+
+```python
+from arcade_mcp_server.auth import (
+ Google,
+ GitHub,
+ Slack,
+ Microsoft,
+ Discord,
+ LinkedIn,
+ Dropbox,
+ # ... and more
+)
+```
+
+##### With Secrets
+
+Use `requires_secrets` for API keys your server needs. Add a `context: Context` parameter to access secrets.
+
+```python
+from arcade_mcp_server import MCPApp, Context
+from typing import Annotated
+
+app = MCPApp(name="my_server")
+
+@app.tool(requires_secrets=["API_KEY"])
+async def call_external_api(
+ query: Annotated[str, "Search query"],
+ context: Context,
+) -> str:
+ """Call an external API."""
+ api_key = context.get_secret("API_KEY")
+ # Use api_key to call external service...
+ return f"Results for: {query}"
+```
+
+##### With Lifecycle Hooks
+
+Use `@app.on_event` for startup/shutdown logic.
+
+```python
+from arcade_mcp_server import MCPApp
+
+app = MCPApp(name="my_server")
+
+@app.on_event("startup")
+async def on_startup():
+ await db.connect()
+
+@app.on_event("shutdown")
+async def on_shutdown():
+ await db.disconnect()
+```
+
+##### Full Example
+
```python
# --- server.py ---
# Programmatic server creation with a simple tool and HTTP transport
diff --git a/app/en/references/mcp/python/transports/page.mdx b/app/en/references/mcp/python/transports/page.mdx
index 07a70e46a..c8dca7d01 100644
--- a/app/en/references/mcp/python/transports/page.mdx
+++ b/app/en/references/mcp/python/transports/page.mdx
@@ -103,10 +103,19 @@ app.run(transport="http", host="0.0.0.0", port=8080)
When running in HTTP mode, the server provides:
-- `GET /worker/health` - Health check endpoint
-- `GET /mcp` - SSE endpoint for MCP protocol
-- `GET /docs` - Swagger UI documentation (debug mode)
-- `GET /redoc` - ReDoc documentation (debug mode)
+| Endpoint | Method | Description |
+|----------|--------|-------------|
+| `/worker/health` | GET | Health check (returns 200 OK) |
+| `/mcp` | GET | SSE stream for server-initiated messages |
+| `/mcp` | POST | Send JSON-RPC message |
+| `/mcp` | DELETE | Terminate session (when using session IDs) |
+| `/docs` | GET | Swagger UI documentation (debug mode) |
+| `/redoc` | GET | ReDoc documentation (debug mode) |
+
+
+ POST requests may return `application/json` (single response) or `text/event-stream` (SSE stream).
+ Clients should include `Accept: application/json, text/event-stream` in requests.
+
### Development Features
diff --git a/app/en/references/mcp/python/types/page.mdx b/app/en/references/mcp/python/types/page.mdx
index ba9947cb2..7ad5b3733 100644
--- a/app/en/references/mcp/python/types/page.mdx
+++ b/app/en/references/mcp/python/types/page.mdx
@@ -1,3 +1,8 @@
+---
+title: "Types - Arcade MCP Python Reference"
+description: "Core types, models, and error adapters for MCP servers"
+---
+
# Types
Core Pydantic models and enums for the MCP protocol shapes.
@@ -54,3 +59,55 @@ result = CallToolResult(
)
```
+## Error Adapters
+
+Error adapters translate vendor-specific exceptions into Arcade errors automatically.
+
+### `arcade_tdk.error_adapters`
+
+#### `ErrorAdapter`
+
+**Type:** Protocol
+
+Base interface for error adapters.
+
+```python
+class ErrorAdapter(Protocol):
+ slug: str
+ def from_exception(self, error: Exception) -> MCPError | None: ...
+```
+
+### Built-in Adapters
+
+| Adapter | Handles |
+|---------|---------|
+| `SlackErrorAdapter` | Slack SDK errors |
+| `GoogleErrorAdapter` | Google API Client errors |
+| `MicrosoftGraphErrorAdapter` | Microsoft Graph SDK errors |
+| `HTTPErrorAdapter` | fetch/HTTP library errors |
+| `GraphQLErrorAdapter` | GraphQL client errors |
+
+### Using Error Adapters
+
+Pass adapters to `@app.tool` to automatically translate exceptions:
+
+```python
+from arcade_mcp_server import MCPApp
+from arcade_tdk.error_adapters import SlackErrorAdapter
+from typing import Annotated
+
+app = MCPApp(name="slack_bot")
+
+@app.tool(adapters=[SlackErrorAdapter()])
+def send_message(
+ channel: Annotated[str, "Slack channel ID"],
+ text: Annotated[str, "Message text"]
+) -> str:
+ """Send a message to Slack."""
+ # SlackApiError → UpstreamError automatically
+ slack_client.chat_postMessage(channel=channel, text=text)
+ return "Sent!"
+```
+
+Adapters are tried in order. First match wins. `HTTPErrorAdapter` is always added as fallback.
+