listo-mcp-observability
Lightweight telemetry SDK for Python MCP servers. Captures MCP tool invocations, HTTP requests, business events, and UI interactions with built-in payload sanitization.
Python port of @listo-labs-ltd/mcp-observability (TypeScript).
Features
- Easy setup — 3 lines with
create_mcp_observability_easy() - Local dashboard — Real-time metrics at
/telemetry/dashboard - Centralized analytics — Send to Listo in production
- MCP tool tracking — Decorator-based (
@obs.track_tool()) or programmatic wrapping - Payload sanitization — Redacts password, token, apiKey, secret, authorization, userid, email, phone, and more
- Sampling — Configurable rates with guaranteed error/session capture
- Zero runtime dependencies — Core uses only Python stdlib
- Event categories — Classify business and UI events as conversion, engagement, impression, navigation, or system
- Batch ingestion —
POST /telemetry/eventsfor efficient multi-event ingestion - Platform detection — Identify which AI platform (Claude, ChatGPT, etc.) initiated each session and tool call
- Sink composition —
combine_sinks()to route events to multiple destinations
Installation
# From PyPI
pip install listo-mcp-observability
# With uv
uv add listo-mcp-observability
# With optional MCP SDK support
pip install "listo-mcp-observability[mcp]"
# With optional Starlette dashboard support
pip install "listo-mcp-observability[starlette]"
Quick Start
from mcp.server.fastmcp import FastMCP
from mcp_observability import create_mcp_observability_easy
mcp = FastMCP("my-server")
obs = create_mcp_observability_easy(service_name="my-server")
@mcp.tool()
@obs.track_tool("search_hotels")
async def search_hotels(query: str) -> str:
return f"Results for {query}"
Environment Variables
| Variable | Description | Default |
|---|---|---|
LISTO_API_URL |
Listo API endpoint (primary) | — |
LISTO_API_KEY |
API key for authentication (primary) | — |
INSIGHTS_API_URL |
Deprecated, use LISTO_API_URL |
— |
INSIGHTS_API_KEY |
Deprecated, use LISTO_API_KEY |
— |
HMAC_SECRET |
Secret key for HMAC-SHA-256 hashing of session_id and user_id |
Falls back to SHA-256 |
ENVIRONMENT or PYTHON_ENV |
Controls dev/production defaults | dev |
API
create_mcp_observability_easy()
Recommended entry point with sensible defaults:
obs = create_mcp_observability_easy(
service_name="my-server",
service_version="1.0.0",
environment="dev", # "dev" | "staging" | "production"
sample_rate=1.0, # 0.0 to 1.0 (errors always captured)
)
Dev mode (default): Console logging (errors only), in-memory dashboard, 100% sampling.
Production mode: No console, remote sink only, 10% sampling.
@obs.track_tool(name)
Decorator for tracking MCP tool calls:
@mcp.tool()
@obs.track_tool("my_tool")
async def my_tool(arg: str) -> str:
...
obs.wrap_mcp_handler(request_kind, handler, context)
Programmatic handler wrapping:
wrapped = obs.wrap_mcp_handler(
"CallTool",
original_handler,
context=lambda *args, **kwargs: McpTrackingContext(
tool_name="search",
args=kwargs,
),
)
obs.record_business_event(name, *, ...)
obs.record_business_event(
"purchase",
properties={"amount": 99.99},
status="ok",
category="conversion",
session_id="sess-123",
user_id="user-456",
tenant_id="tenant-789",
)
detect_platform(hints)
Detect which AI platform is connecting to your MCP server:
from mcp_observability import detect_platform, PlatformHints, McpClientInfo
platform = detect_platform(PlatformHints(
headers={"User-Agent": "Claude-Desktop/2.0"},
))
# → "claude"
platform = detect_platform(PlatformHints(
client_info=McpClientInfo(name="chatgpt"),
))
# → "chatgpt"
Three-tier detection (first match wins):
- HTTP headers — user-agent and vendor-specific headers (
x-openai-*,x-anthropic-*) _metakey prefixes —openai/,anthropic/,claude/clientInfo.name— exact or prefix match
Returns "claude", "chatgpt", or "other".
obs.record_session(action, session_id, *, platform=None)
obs.record_session("open", "session-123", platform="claude")
obs.record_ui_event(name, *, ...)
obs.record_ui_event(
"click",
action="button_press",
category="engagement",
widget_id="w1",
user_id="user-456",
)
EventCategory
Type alias for valid event categories:
from mcp_observability import EventCategory
# "conversion" | "engagement" | "impression" | "navigation" | "system"
combine_sinks(*sinks)
Combine multiple sinks into one with error isolation:
from mcp_observability import combine_sinks, ConsoleSink, InMemorySink
combined = combine_sinks(ConsoleSink(), InMemorySink())
Dashboard Endpoints (Starlette)
from mcp_observability import create_telemetry_routes
routes = create_telemetry_routes()
# GET /telemetry — JSON metrics
# GET /telemetry/dashboard — HTML dashboard
# POST /telemetry/event — Single UI event ingestion (8KB max)
# POST /telemetry/events — Batch UI event ingestion (max 100 events)
Remote Sink
from mcp_observability import RemoteSink, RemoteSinkOptions
sink = RemoteSink(RemoteSinkOptions(
endpoint="https://api.listoai.co/v1/events/batch",
api_key="your-key",
batch_size=50,
flush_interval=5.0,
max_retries=3,
))
Payload Sanitization
The SDK automatically redacts sensitive fields (case-insensitive):
password,token,apiKey,secret,authorizationuserId,userLocation,email,userEmail,phone,phoneNumber
Redaction is recursive (up to 6 levels deep) and limits arrays to 20 elements.
Custom redaction keys:
obs = McpObservability(ObservabilityOptions(
service_name="my-server",
redact_keys=["password", "token", "credit_card", "ssn"],
))
Event Types
The SDK captures five event types:
| Type | Description |
|---|---|
http_request |
HTTP request lifecycle |
mcp_request |
MCP tool call, resource read, prompt get |
mcp_session |
Session open/close (always captured) |
business_event |
Domain-specific events with optional category, session, tenant tracking |
ui_event |
Frontend interactions with optional category |
Security
- Sampling: Errors and sessions are always captured. All other events are subject to
sample_rate. - Redaction: Sensitive keys are recursively replaced with
[redacted]up to depth 6. - Data minimization:
user_idandsession_idare automatically hashed before emission. Nouser_locationfield exists in event types. User query text is truncated and hashed.
Documentation
Full documentation is available in the docs/ directory:
| Page | Description |
|---|---|
| Overview | Architecture, quick start |
| Installation | pip install, prerequisites |
| Easy Setup | create_mcp_observability_easy() deep-dive |
| Configuration | Both init methods, full config tables |
| Wrap MCP Handlers | wrap_mcp_handler(), track_tool(), context fields |
| Sessions | record_session(), always-capture behavior |
| Business Events | record_business_event(), EventCategory |
| UI Events | record_ui_event() + POST endpoint |
| Sinks | InMemorySink, RemoteSink, ConsoleSink, combine_sinks |
| Endpoints | Dashboard, JSON API, event ingestion |
| Sampling & Privacy | Sampling, redaction, data minimization |
| Advanced | Custom sinks, metrics API |
| Platform Detection | Detect Claude, ChatGPT, etc. |
| Troubleshooting | Common issues, migration from v0.0.x |
Development
# Install dependencies
uv sync --all-extras
# Run tests
uv run pytest -v
# Lint
uv run ruff check src/ tests/
# Type check
uv run mypy src/
# Build
uv build
License
MIT
Metadata
Release files for listo-mcp-observability 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| listo_mcp_observability-0.4.0.tar.gz | 45.5 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| listo_mcp_observability-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 67.8 kB
Release files / listo_mcp_observability-0.4.0.tar.gz
| Download URL | listo_mcp_observability-0.4.0.tar.gz |
|---|---|
| Size | 45.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d90e3215d5e22dc8404de92b955ca037dbafe4d0d3d59d828ebfe15e8f239714
|
|
BLAKE2b-256 checksum How to use checksums |
715f481a51beb4ab11db9ce3dcad53df8c9295fd172d7dd30f703927e4298314
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 7, 2026.
Transparency logRelease files / listo_mcp_observability-0.4.0-py3-none-any.whl
| Download URL | listo_mcp_observability-0.4.0-py3-none-any.whl |
|---|---|
| Size | 22.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6cfb23bc51f05c8fd26ec792b944009ed097e12ee531266cffc7c5eab65413d4
|
|
BLAKE2b-256 checksum How to use checksums |
b49d3b9bfa591177b85e48bee7677d3b695c1ae2afb0fea59dbfb749834b620c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 7, 2026.
Transparency logSecurity for listo-mcp-observability 0.4.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for listo_mcp_observability-0.4.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 7, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.