MCP Server Framework and Tool Development library for building custom capabilities into agents.
Find a file
Francisco Or Something d9812621de
feat: add NetworkTransportError for no-response HTTP failures (#823)
## 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>
2026-04-16 18:29:13 -03:00
.cursor/rules Cursor versioning rules (#715) 2025-12-09 11:31:15 -08:00
.github Add state github bot (#812) 2026-04-07 17:48:06 -07:00
.vscode Rename some 'toolkit' references to 'server' (#624) 2025-10-14 18:42:27 -07:00
examples Don't return structuredContent when error (#817) 2026-04-10 15:27:07 -07:00
libs feat: add NetworkTransportError for no-response HTTP failures (#823) 2026-04-16 18:29:13 -03:00
schemas/preview Tool Metadata (#357) 2025-04-16 19:17:36 -08:00
tests Improve .env discovery (#737) 2026-02-25 23:20:28 -08:00
.editorconfig Fix ruff (#64) 2024-09-25 09:47:30 -07:00
.gitignore feat: Add TelemetryPassbackMiddleware for serverExecutionTelemetry capability (#797) 2026-03-25 15:57:50 -07:00
.pre-commit-config.yaml Left over fixes for Windows Papercut PR (#781) 2026-02-26 13:24:15 -03:00
.prettierignore Fix ruff (#64) 2024-09-25 09:47:30 -07:00
.prettierrc.toml Fix ruff (#64) 2024-09-25 09:47:30 -07:00
.ruff.toml [TOO-522] Supress chardet noizy versioning warning (#792) 2026-03-13 15:56:15 -07:00
CLAUDE.md Update CLAUDE.md (#815) 2026-04-08 16:49:58 -07:00
CONTRIBUTING.md [TOO-326] Windows papercuts (#768) 2026-02-25 13:18:16 -03:00
cspell.config.yaml Replace arcade.client with arcadepy (#119) 2024-10-23 15:29:02 -07:00
LICENSE Update README and LICENSE (#220) 2025-01-23 19:43:48 -08:00
Makefile Remove toolkits (#784) 2026-02-26 09:09:46 -08:00
pyproject.toml feat: added connect cli command (#819) 2026-04-15 13:16:50 -07:00
README.md Fix broken links (#738) 2026-01-05 13:27:16 -08:00
SECURITY.md Fix broken links (#738) 2026-01-05 13:27:16 -08:00
uv_setup.sh MCP Local (#563) 2025-09-25 15:28:15 -07:00

Prebuilt ToolsContact Us

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!

GitHub stars

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 .env file containing a secret required by one of the generated tools in server.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