Spanly Docs

Quickstart

Instrument a Python MCP server with spanly in one line.

This walks through mounting SpanlyMiddleware in front of a Python MCP server that speaks ASGI (FastAPI, Starlette, or the ASGI app the mcp package's streamable_http_app() returns). SpanlyMiddleware inspects requests under /mcp and /sse by default, tees the request and response bytes, and never mutates a body or delays a message.

1. Install

pip install spanly

2. Set the API key

export SPANLY_API_KEY=spanly_us_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Get a key by signing in at spanly.com, opening your project, and going to Settings → API keys. The region is encoded in the key prefix (spanly_us_ / spanly_eu_) and auto-detected, so there is nothing else to configure.

3. Mount the middleware

import osfrom spanly import SpanlyMiddlewareapp.add_middleware(SpanlyMiddleware, api_key=os.environ["SPANLY_API_KEY"])

add_middleware defers construction until the app builds its middleware stack, so SpanlyMiddleware receives your ASGI app as its first positional argument automatically. If your framework does not expose add_middleware (a bare ASGI app, for instance), wrap it directly instead:

import os

from spanly import SpanlyMiddleware

app = SpanlyMiddleware(app, api_key=os.environ["SPANLY_API_KEY"])

Either way, traffic on /mcp and /sse is now reported to your Spanly project.

Full example: FastMCP over Streamable HTTP

import os
from mcp.server.fastmcp import FastMCP
from spanly import SpanlyMiddleware

mcp = FastMCP("demo-http-server")


@mcp.tool()
async def echo(input: str) -> str:
    return input


app = mcp.streamable_http_app()
app = SpanlyMiddleware(app, api_key=os.environ["SPANLY_API_KEY"])

if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=3000)

Full example: FastAPI

import os
from fastapi import FastAPI
from spanly import SpanlyMiddleware

app = FastAPI()
# … mount your MCP server's ASGI app at /mcp …

app.add_middleware(SpanlyMiddleware, api_key=os.environ["SPANLY_API_KEY"])

Sessionless servers

When your server runs stateless (no Mcp-Session-Id on its initialize response), SpanlyMiddleware injects a synthetic one (prefixed spanly-) on the response so requests still group into sessions. This is a real, on-the-wire header, unlike the TypeScript SDK's telemetry-only grouping. See Session tracking for the full behavior, and opt out with inject_session_id=False if you'd rather Spanly never touch a response header.

Next steps

  • The API reference covers every option SpanlyMiddleware accepts.
  • Examples cover end-user attribution, error reporting, and a local test harness.
  • If you'd rather not change code, the CLI wraps the same server with the same capture behavior.
  • Nothing showing up in the dashboard? See the troubleshooting guide.

On this page