MCP Server Framework and Tool Development library for building custom capabilities into agents.
Find a file
Sankara R. Avula 78c8e6fb99
feat: Add TelemetryPassbackMiddleware for serverExecutionTelemetry capability (#797)
**Implements**: [SEP-2448: server execution telemetry]
(https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2448)



**Description:**

**The Observability Gap (The Problem)**

MCP clients propagate trace context to servers, but server-side
execution remains a black box. The client sees a single tools/call or
resources/read span; everything the server does (auth checks, policy
evaluation, API calls, sub-tool invocations) is invisible. In
cross-organization deployments, clients and servers use separate
observability backends with no shared collector access, making
traditional span export useless.

<img width="1015" height="450" alt="Screenshot 2026-03-23 at 3 43 21 PM"
src="https://github.com/user-attachments/assets/58c817b5-fee6-46a3-9877-d523a25368ad"
/>


**Server Execution Telemetry (The Solution)**

Servers advertise serverExecutionTelemetry and return a curated slice of
their execution spans directly in _meta.otel of the response. Clients
ingest these verbatim OTLP spans into their own collector, stitching
server-side execution into their distributed trace; no shared
infrastructure required. The black box becomes transparent.

<img width="945" height="574" alt="Screenshot 2026-03-23 at 3 43 44 PM"
src="https://github.com/user-attachments/assets/38d97c94-aa73-4e62-9b4e-3264600e5ed0"
/>

.
**Summary:**

Implement MCP serverExecutionTelemetry capability that enables
cross-organization distributed tracing by returning server-side
OpenTelemetry spans to clients inline via _meta.otel.traces.

  Server-side (middleware):
  - TelemetryPassbackMiddleware intercepts tools/call and resources/read
- ContextVarSpanCollector isolates span collection per-request via
ContextVar
- Propagates traceparent from client request for distributed trace
stitching
- Serializes collected spans to verbatim OTLP JSON (resourceSpans
format), directly POSTable to /v1/traces
- Top-level span filtering by default; full span tree via detailed
opt-in
- Middleware advertises capabilities via get_capabilities() on the
Middleware base class
  - Provisional API: FutureWarning emitted until SEP-2448 is ratified

  Client-side (reference agent):
- LangChain ReAct agent connects to MCP server via
streamable_http_client with OAuth 2.1
  - Detects serverExecutionTelemetry capability at initialization
- Dynamically wraps discovered MCP tools with traceparent propagation
and _meta.otel span request
- Ingests returned server spans into Jaeger (OTLP JSON) and Galileo
(OTLP protobuf)
- Two-act demo: --no-passback (black box) vs default (full server-side
visibility)

  Dependencies:
  - opentelemetry-api and opentelemetry-sdk added to arcade-mcp-server

  Bump arcade-mcp-server version to 1.18.0.
2026-03-25 15:57:50 -07:00
.cursor/rules Cursor versioning rules (#715) 2025-12-09 11:31:15 -08:00
.github Remove toolkits (#784) 2026-02-26 09:09:46 -08:00
.vscode Rename some 'toolkit' references to 'server' (#624) 2025-10-14 18:42:27 -07:00
examples feat: Add TelemetryPassbackMiddleware for serverExecutionTelemetry capability (#797) 2026-03-25 15:57:50 -07:00
libs feat: Add TelemetryPassbackMiddleware for serverExecutionTelemetry capability (#797) 2026-03-25 15:57:50 -07: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 Remove toolkits (#784) 2026-02-26 09:09:46 -08:00
CONTRIBUTING.md [TOO-326] Windows papercuts (#768) 2026-02-25 13:18:16 -03:00
core Dependency conflict rich library (#729) 2025-12-14 10:46:03 -08: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 Bump authlib dep (#795) 2026-03-16 17:50:29 -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