## Summary - Adds `NetworkTransportError` — a new sibling to `UpstreamError` under `ToolExecutionError` — for failures where no complete HTTP response was received from the upstream service (timeouts, connection errors, pool exhaustion, DNS failures, decoding issues, redirect exhaustion) - Routes client-construction bugs (`InvalidURL`, `UnsupportedProtocol`, `MissingSchema`, `SSLError`, `InvalidHeader`, etc.) to existing `FatalToolError` instead of `UpstreamError` - Adds 3 new `ErrorKind` values: `NETWORK_TRANSPORT_RUNTIME_TIMEOUT`, `_UNREACHABLE`, `_UNMAPPED` — operationally distinct telemetry slices matching the UpstreamError pattern - `UpstreamError` is unchanged and reserved for real HTTP responses with status codes Addresses Eric's feedback on #820: the `include_status_code=False` post-init null-out workaround is replaced by a clean class hierarchy where `NetworkTransportError.status_code` is natively `None`. ### Changes | File | What | |---|---| | `arcade-core/errors.py` | 3 new `ErrorKind` values, `NetworkTransportError` class, `is_network_transport_error` helper | | `arcade-tdk/providers/http/error_adapter.py` | Full rewrite of httpx + requests exception routing with 3-way split | | `arcade-tdk/providers/graphql/error_adapter.py` | `TransportConnectionFailed`/`TransportProtocolError` → `NetworkTransportError` | | `arcade-tdk/errors.py`, `arcade-mcp-server/exceptions.py` | Re-exports | | `pyproject.toml` × 3 | Version bumps: core 4.7.0, tdk 3.7.0, mcp-server 1.20.0 | | Tests × 3 | 33 new tests, 3 updated (2659 passed, 0 failures) | ### Exception routing table | Exception | Target | Kind | can_retry | |---|---|---|---| | `httpx.HTTPStatusError`, `requests.HTTPError` (with response) | `UpstreamError` | status-derived | status-derived | | `httpx.TimeoutException`, `requests.Timeout` | `NetworkTransportError` | `TIMEOUT` | ✅ | | `httpx.TransportError`, `requests.ConnectionError` | `NetworkTransportError` | `UNREACHABLE` | ✅ | | `httpx.DecodingError`, `TooManyRedirects`, fallback | `NetworkTransportError` | `UNMAPPED` | varies | | `httpx.InvalidURL`/`UnsupportedProtocol`/`LocalProtocolError`, `requests.MissingSchema`/`SSLError`/etc. | `FatalToolError` | `TOOL_RUNTIME_FATAL` | ❌ | ### Engine companion PR ArcadeAI/monorepo — `feat/network-transport-error-kinds` adds the 3 `ErrorKind` constants to Go schemas + OpenAPI docs. No engine logic changes needed (ErrorKind is a string alias, retry uses `can_retry` flag only, telemetry auto-slices). ## Test plan - [x] 2659 existing tests pass (0 failures) - [x] 33 new routing + class tests added - [x] mypy clean on arcade-core, arcade-tdk - [ ] Verify engine telemetry dashboard auto-surfaces new `NETWORK_TRANSPORT_*` kinds after deploy 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes the error taxonomy and classification helpers used for retries/telemetry, so misclassification could affect operational behavior, but the change is additive and covered by new tests. > > **Overview** > Adds a new error category for outbound request failures that never yield a complete upstream response: `NetworkTransportError` (sibling to `UpstreamError`) plus `ErrorKind.NETWORK_TRANSPORT_RUNTIME_{TIMEOUT,UNREACHABLE,UNMAPPED}` and matching `is_network_transport_error` classification helpers on both `ToolkitError` and the wire-model `ToolCallError`. > > Re-exports `NetworkTransportError` from `arcade-tdk` and `arcade-mcp-server`, bumps package versions (`arcade-core` 4.7.0, `arcade-tdk` 3.7.0, `arcade-mcp-server` 1.20.0) and dependency minimums, and expands `core/test_errors.py` to cover the new kind invariants/defaults and classification behavior. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit d2b89078729c6a67ba42684dc98445352238bc1d. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .cursor/rules | ||
| .github | ||
| .vscode | ||
| examples | ||
| libs | ||
| schemas/preview | ||
| tests | ||
| .editorconfig | ||
| .gitignore | ||
| .pre-commit-config.yaml | ||
| .prettierignore | ||
| .prettierrc.toml | ||
| .ruff.toml | ||
| CLAUDE.md | ||
| CONTRIBUTING.md | ||
| cspell.config.yaml | ||
| LICENSE | ||
| Makefile | ||
| pyproject.toml | ||
| README.md | ||
| SECURITY.md | ||
| uv_setup.sh | ||
Arcade MCP Server Framework
-
To see example servers built with Arcade MCP Server Framework (this repo), check out our examples
-
To learn more about the Arcade MCP Server Framework (this repo), check out our Arcade MCP documentation
-
To learn more about other offerings from Arcade.dev, check out our documentation.
Pst. hey, you, give us a star if you like it!
Quick Start: Create a New Server
The fastest way to get started is with the arcade new CLI command, which creates a complete MCP server project:
# Install the CLI
uv tool install arcade-mcp
# Create a new server project
arcade new my_server
# Navigate to the project
cd my_server/src/my_server
This generates a project with:
-
server.py - Main server file with MCPApp and example tools
-
pyproject.toml - Dependencies and project configuration
-
.env.example - Example
.envfile containing a secret required by one of the generated tools inserver.py
The generated server.py includes proper command-line argument handling and three example tools:
#!/usr/bin/env python3
"""simple_server MCP server"""
import sys
from typing import Annotated
import httpx
from arcade_mcp_server import Context, MCPApp
from arcade_mcp_server.auth import Reddit
app = MCPApp(name="simple_server", version="1.0.0", log_level="DEBUG")
@app.tool
def greet(name: Annotated[str, "The name of the person to greet"]) -> str:
"""Greet a person by name."""
return f"Hello, {name}!"
# To use this tool locally, you need to either set the secret in the .env file or as an environment variable
@app.tool(requires_secrets=["MY_SECRET_KEY"])
def whisper_secret(context: Context) -> Annotated[str, "The last 4 characters of the secret"]:
"""Reveal the last 4 characters of a secret"""
# Secrets are injected into the context at runtime.
# LLMs and MCP clients cannot see or access your secrets
# You can define secrets in a .env file.
try:
secret = context.get_secret("MY_SECRET_KEY")
except Exception as e:
return str(e)
return "The last 4 characters of the secret are: " + secret[-4:]
# To use this tool locally, you need to install the Arcade CLI (uv tool install arcade-mcp)
# and then run 'arcade login' to authenticate.
@app.tool(requires_auth=Reddit(scopes=["read"]))
async def get_posts_in_subreddit(
context: Context, subreddit: Annotated[str, "The name of the subreddit"]
) -> dict:
"""Get posts from a specific subreddit"""
# Normalize the subreddit name
subreddit = subreddit.lower().replace("r/", "").replace(" ", "")
# Prepare the httpx request
# OAuth token is injected into the context at runtime.
# LLMs and MCP clients cannot see or access your OAuth tokens.
oauth_token = context.get_auth_token_or_empty()
headers = {
"Authorization": f"Bearer {oauth_token}",
"User-Agent": "{{ toolkit_name }}-mcp-server",
}
params = {"limit": 5}
url = f"https://oauth.reddit.com/r/{subreddit}/hot"
# Make the request
async with httpx.AsyncClient() as client:
response = await client.get(url, headers=headers, params=params)
response.raise_for_status()
# Return the response
return response.json()
# Run with specific transport
if __name__ == "__main__":
# Get transport from command line argument, default to "stdio"
# - "stdio" (default): Standard I/O for Claude Desktop, CLI tools, etc.
# Supports tools that require_auth or require_secrets out-of-the-box
# - "http": HTTPS streaming for Cursor, VS Code, etc.
# Does not support tools that require_auth or require_secrets unless the server is deployed
# using 'arcade deploy' or added in the Arcade Developer Dashboard with 'Arcade' server type
transport = sys.argv[1] if len(sys.argv) > 1 else "stdio"
# Run the server
app.run(transport=transport, host="127.0.0.1", port=8000)
This approach gives you:
-
Complete Project Setup - Everything you need in one command
-
Best Practices - Proper dependency management with pyproject.toml
-
Example Code - Learn from working examples of common patterns
-
Production Ready - Structured for growth and deployment
Running Your Server
Run your server directly with Python:
# Run with stdio transport (default)
uv run server.py
# Run with http transport via command line argument
uv run server.py http
# Or use python directly
python server.py http
python server.py stdio
Your server will start and listen for connections. With HTTP transport, you can access the API docs at http://127.0.0.1:8000/docs.
Configure MCP Clients
Once your server is running, connect it to your favorite AI assistant:
arcade configure claude # Configure Claude Desktop to connect to your stdio server in your current directory
arcade configure cursor --transport http --port 8080 # Configure Cursor to connect to your local HTTP server on port 8080
arcade configure vscode --entrypoint my_server.py # Configure VSCode to connect to your stdio server that will run when my_server.py is executed directly
Installing this Repo from Source
git clone https://github.com/ArcadeAI/arcade-mcp.git && cd arcade-mcp && make install
Support and Community
- Discord: Join our Discord community for real-time support and discussions.
- GitHub: Contribute or report issues on the Arcade GitHub repository.
- Documentation: Find in-depth guides and API references at Arcade Documentation.