opik-mcp

analytics

Purpose

opik-mcp sends product events to Comet’s BI endpoint and stack traces of unexpected failures to Sentry. This doc answers which events fire and what they carry, how a failure gets its error_kind, what may never be sent, and how to add an event or a property.

What it does now

Both channels default to on. OPIK_MCP_ANALYTICS_ENABLED=false and OPIK_MCP_SENTRY_ENABLED=false turn them off. Telemetry never fails a tool call or hides a startup error.

Events

Every name starts with opik_mcp_ (src/opik_mcp/analytics/events.py).

Event Fires when Emitted by
server_started the process starts serving main(), or the build_app() lifespan when main() did not run
startup_error settings fail validation, HTTP OAuth config is incomplete, or the transport crashes _emit_startup_error in src/opik_mcp/__main__.py
tools_listed the first tools/list of a session install_tools_listed_emitter in src/opik_mcp/analytics/wrappers.py
session_initialized the first tool call of a session instrument_tool in src/opik_mcp/analytics/wrappers.py
tool_called a call to read, list, write, schema or read_skill that passed argument validation instrument_tool
auth_rejected HTTP only: 401, 403 or 421 on an authenticated path AuthRejectionMiddleware in src/opik_mcp/server/http/middleware.py
server_shutdown the process stops, with a reason _emit_server_shutdown in src/opik_mcp/__main__.py, or the lifespan

initialize produces no event. tools_listed and session_initialized are sent once per MCP session, the ServerSession a connection opens. A stdio process serves one connection, so there it means once per process.

What an event carries

Error kinds

error_kind is a value of ErrorKind in src/opik_mcp/error_kinds.py. bucket_exception in src/opik_mcp/analytics/errors.py decides it:

  1. A bare ToolError is unwrapped to its cause. exception_type keeps the wrapper class, cause_type the leaf.
  2. A status on the instance (httpx.HTTPStatusError, BackendError) goes through bucket_http_status.
  3. The class’s error_kind: ClassVar[ErrorKind].
  4. Pydantic and httpx classes, then unknown.

Host cancellation is cancelled. The classifier never reads str(exc) or exc.args. invalid_config is used only by the startup error.

instrument_tool sends a failure to Sentry unless its kind is in _USER_SIDE_ERROR_KINDS or the cause is a MissingConfigError. Sentry sends no PII, and before_send caps events per process at _MAX_EVENTS (src/opik_mcp/error_tracking.py).

How it works

tool handler wrapped by instrument_tool
  -> _maybe_emit_session_initialized
  -> the tool; on failure bucket_exception, maybe _report_to_sentry
  -> track_event(EVENT_TOOL_CALLED) -> _build_event -> bounded queue
  -> daemon thread POSTs, retries, drops when the queue is full

To change something, start here:

To add a property:

  1. Return it from the props function, bucketed or allowlisted.
  2. For an enum, add a Literal in src/opik_mcp/analytics/events.py and a test in tests/analytics/test_events.py that it matches the classifier.
  3. Assert its output and a canary in tests/analytics/test_privacy.py, as test_read_props_buckets_id_kind_without_leaking does for _read_props.
  4. A new value is a BI schema change (the docstring in src/opik_mcp/analytics/events.py). Unverified: whether a new property counts too; say so in the PR for BI to be safe.

Boundaries:

Decisions

No ADR covers analytics. The reasons come from PRs.

Traps

Proven by

Log