Back to Articles

Arcade MCP: The Flask-Inspired Framework That Makes Building Model Context Protocol Servers Actually Enjoyable

[ View on GitHub ]

Arcade MCP: The Flask-Inspired Framework That Makes Building Model Context Protocol Servers Actually Enjoyable

Hook

Building an MCP server used to mean handling transport layers, authentication flows, and secret management separately. Arcade MCP consolidates these concerns into a decorator-based API centered on @app.tool.

Context

Anthropic’s Model Context Protocol (MCP) provides a standardized way for LLMs to interact with external tools and data sources. Write a server once, and it works across Claude Desktop, Cursor, VS Code, and other MCP-compatible clients. The official MCP SDK requires implementing transport handlers, managing stdio streams versus HTTP endpoints, and wiring up authentication providers while ensuring secrets never leak into tool schemas.

Arcade MCP provides a developer experience layer on top of the standard protocol. It borrows patterns from web frameworks: sensible defaults, complexity hidden behind decorators, and a focus on tool logic rather than protocol plumbing. The framework handles the bifurcated transport reality of MCP—stdio for desktop applications like Claude, HTTP for editor integrations like Cursor—and includes production features like OAuth flows and secret injection.

Technical Insight

At its core, Arcade MCP provides an MCPApp class that follows a familiar application pattern. You instantiate an app, decorate functions with @app.tool, and the framework handles schema generation, transport negotiation, and runtime injection of sensitive data. Here’s what a complete server looks like:

from arcade_mcp_server import Context, MCPApp
from arcade_mcp_server.auth import Reddit

app = MCPApp(name="reddit_tools", version="1.0.0", log_level="DEBUG")

@app.tool
def greet(name: str) -> str:
    """Greet a person by name."""
    return f"Hello, {name}!"

@app.tool(requires_secrets=["API_KEY"])
def call_api(context: Context, endpoint: str) -> dict:
    """Call external API with authenticated credentials"""
    api_key = context.get_secret("API_KEY")
    # Secret never exposed to LLM—only injected at runtime
    return make_request(endpoint, headers={"Authorization": f"Bearer {api_key}"})

@app.tool(requires_auth=Reddit(scopes=["read"]))
async def get_posts(context: Context, subreddit: str) -> dict:
    """Fetch posts from a subreddit"""
    token = context.get_auth_token_or_empty()
    headers = {"Authorization": f"Bearer {token}"}
    async with httpx.AsyncClient() as client:
        response = await client.get(f"https://oauth.reddit.com/r/{subreddit}/hot", headers=headers)
        return response.json()

The architecture’s critical insight is the Context object. When you declare requires_secrets or requires_auth, Arcade intercepts the tool call and injects a Context instance as the first parameter. This context acts as a secure channel: secrets and OAuth tokens live there, populated at runtime by Arcade’s infrastructure, but they’re never serialized into the tool schema that gets sent to the LLM. From the model’s perspective, the tool signature for call_api is just (endpoint: str) -> dict—it has no visibility into the API_KEY requirement.

Transport flexibility happens through command-line arguments. The generated server.py includes a main block that reads sys.argv to determine whether to run in stdio mode (default, for Claude Desktop) or HTTP mode (for Cursor/VS Code). Same Python file, same tool definitions, different runtime behavior. The stdio transport uses standard input/output streams for JSON-RPC communication, while HTTP mode spins up an SSE-based streaming server. This means you can test with Claude Desktop during development, then deploy the identical code for editor integrations without modification.

The CLI tooling completes the developer experience. arcade new scaffolds a complete project with pyproject.toml, .env.example, and proper async support. arcade configure generates client configuration files—it’ll output the JSON needed for Claude Desktop’s config or the settings for Cursor, eliminating the manual copy-paste step. arcade deploy pushes to Arcade’s hosted infrastructure, where secrets and OAuth credentials are managed server-side. The deployment story is where vendor coupling enters: while you can run Arcade MCP servers locally via stdio without any Arcade services, HTTP transport with authentication or secrets requires either deployment through arcade deploy or manual configuration through Arcade’s dashboard.

Type hints drive the schema generation. Arcade uses Python’s Annotated type to extract parameter descriptions: name: Annotated[str, “The name of the person to greet”] becomes a tool parameter with that exact description in the MCP schema. Return type annotations similarly document output shapes. This is standard modern Python, and Arcade relies on it—poorly annotated functions produce poorly documented tools, and LLMs struggle to use them correctly.

Gotcha

The biggest limitation is documented in the README: HTTP transport “does not support tools that require_auth or require_secrets unless” you’ve deployed via arcade deploy or configured through Arcade’s dashboard. This creates a constrained local development story. You can test simple tools via HTTP locally, but the moment you add authentication or secret management—the features that make Arcade compelling—you’re limited to either stdio-only testing with Claude Desktop or deploying to Arcade’s infrastructure. There’s no documented path for running a fully-featured HTTP server on localhost without coupling to Arcade’s services.

The framework has 842 GitHub stars, which means the ecosystem of examples, troubleshooting guides, and battle-tested patterns is still developing compared to building directly on the official MCP SDK. When you hit edge cases—like managing stateful connections across tool calls or handling rate limiting from OAuth providers—you may need to pioneer solutions rather than finding established patterns. The abstraction is leaky enough that you’ll occasionally need to understand the underlying MCP protocol anyway, especially when debugging why a client isn’t recognizing your tools or when authentication flows fail silently.

Verdict

Use Arcade MCP if you’re building production-grade MCP servers with authentication or secret management requirements and you’re comfortable coupling to Arcade’s deployment platform. The decorator-based API and CLI tooling accelerate development—the framework handles transport negotiation, schema generation, and secure credential injection through a clean interface. It’s ideal for teams that want to ship tool integrations quickly, need OAuth providers like Reddit or GitHub available, and value developer experience over infrastructure control. The stdio transport works well for local development with Claude Desktop, making it effective for rapid prototyping.

Skip it if you need self-hosted production deployments without vendor dependencies, or if you’re building simple tools that don’t require authentication. For basic MCP servers that just wrap a few API calls with public endpoints, the official Python SDK or lighter alternatives give you more control without the Arcade coupling. Also skip if you’re committed to HTTP-based local development workflows—the authentication limitations make this frustrating. Teams with strict data residency requirements or those building commercial products where vendor coupling is unacceptable should evaluate whether Arcade’s hosted infrastructure aligns with their compliance needs before committing to this framework.

// QUOTABLE

Building an MCP server used to mean handling transport layers, authentication flows, and secret management separately. Arcade MCP consolidates these concerns into a decorator-based API centered on ...

[ Tweet This ]
// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/developer-tools/arcadeai-arcade-mcp.svg)](https://starlog.is/api/badge-click/developer-tools/arcadeai-arcade-mcp)