ExMCP.Telemetry (ex_mcp v1.0.0-rc.8)

Copy Markdown View Source

Telemetry integration for ExMCP.

This module defines telemetry events emitted by ExMCP components and provides convenience functions for attaching handlers.

Events

All events follow the naming convention [:ex_mcp, :component, :action, :status].

Client Events

Connection

  • [:ex_mcp, :client, :connected] - Client connection established
    • Metadata: %{transport: module()}
  • [:ex_mcp, :client, :disconnected] - Client disconnected
    • Metadata: %{}

Request Lifecycle

  • [:ex_mcp, :client, :request, :sent] - Request sent to server
    • Metadata: %{method: String.t()}
  • [:ex_mcp, :client, :request, :completed] - Response matched to pending request
    • Metadata: %{method: String.t() | nil, request_id: integer()}

Receiver

  • [:ex_mcp, :client, :receiver, :started] - Receiver task started
    • Metadata: %{mode: :push | :pull}

  • [:ex_mcp, :client, :receiver, :message] - Message received in receive loop
    • Metadata: %{}

MCP 2026-07-28 Operations

  • [:ex_mcp, :client, :era, :settled] - Connection era/version selected
  • [:ex_mcp, :client, :era, :fallback] - Modern probe fell back to legacy
  • [:ex_mcp, :client, :era, :downgrade_attempt] - A pinned modern endpoint was observed as legacy
  • [:ex_mcp, :client, :era, :unsupported_version_retry] - Discovery retried a supported modern version
  • [:ex_mcp, :client, :mrtr, :round | :failure] - MRTR progress or bounded failure class

  • [:ex_mcp, :client, :subscription, :reconnect] - Subscription reconnect scheduled/completed
  • [:ex_mcp, :client, :http, :request, :retry] - Ambiguous HTTP stream reissue

These events use protocol method/version enums and classified reasons. They never contain request parameters, _meta, inputResponses, request state, credentials, or subscription identifiers.

State & Progress (via span/3)

  • [:ex_mcp, :request, :start] - Request processing starts
    • Measurements: %{system_time: integer()}
    • Metadata: %{request_id: String.t(), method: String.t(), server: atom()}
  • [:ex_mcp, :request, :stop] - Request processing completes
    • Measurements: %{duration: integer()}
    • Metadata: %{request_id: String.t(), method: String.t(), server: atom(), status: :ok | :error}

  • [:ex_mcp, :request, :exception] - Request processing fails
    • Measurements: %{duration: integer()}
    • Metadata: %{request_id: String.t(), method: String.t(), server: atom(), kind: atom(), error: term(), stacktrace: list()}

Server Events

Request Processing

  • [:ex_mcp, :server, :request, :received] - Transport message arrives
    • Metadata: %{method: String.t()}
  • [:ex_mcp, :server, :request, :completed] - Response sent back
    • Metadata: %{method: String.t()}
  • [:ex_mcp, :server, :request, :processed] - MessageProcessor.process/2 completes
    • Metadata: %{method: String.t(), has_response: boolean()}
  • [:ex_mcp, :server, :initialize, :completed] - Server initialization completes
    • Metadata: %{server_name: String.t()}

Tool Execution

  • [:ex_mcp, :server, :tool, :called] - Tool call dispatched
    • Metadata: %{tool_name: String.t(), mode: :handler}
  • [:ex_mcp, :tool, :start] - Tool execution starts (via span/3)
    • Measurements: %{system_time: integer()}
    • Metadata: %{tool_name: String.t(), request_id: String.t()}
  • [:ex_mcp, :tool, :stop] - Tool execution completes (via span/3)
    • Measurements: %{duration: integer()}
    • Metadata: %{tool_name: String.t(), request_id: String.t(), status: :ok | :error}

Resource Operations

  • [:ex_mcp, :server, :resource, :read] - Resource read dispatched
    • Metadata: %{uri: String.t(), mode: :handler}
  • [:ex_mcp, :resource, :read, :start] - Resource read starts (via span/3)
    • Measurements: %{system_time: integer()}
    • Metadata: %{uri: String.t(), request_id: String.t()}
  • [:ex_mcp, :resource, :read, :stop] - Resource read completes (via span/3)
    • Measurements: %{duration: integer(), bytes: integer() | nil}

    • Metadata: %{uri: String.t(), request_id: String.t(), status: :ok | :error}

Prompt Rendering

  • [:ex_mcp, :server, :prompt, :rendered] - Prompt get dispatched
    • Metadata: %{name: String.t(), mode: :handler}

HTTP Transport

  • [:ex_mcp, :server, :http, :request] - HTTP request received
    • Metadata: %{method: String.t(), path: String.t()}
  • [:ex_mcp, :server, :http, :response] - HTTP response sent
    • Metadata: %{status: integer()}

MCP 2026-07-28 Operations

  • [:ex_mcp, :server, :mrtr, :failure] - Sealing/resume/replay failure class
  • [:ex_mcp, :server, :subscription, :queue_pressure] - Coalescing or slow-consumer closure
  • [:ex_mcp, :server, :subscription, :fanout] - PubSub fan-out failure class

MRTR key IDs, JTIs and sealed tokens are deliberately omitted. Subscription filters, resource URIs, task IDs, principals and wire IDs are also omitted.

Transport Events (via span/3)

  • [:ex_mcp, :connection, :established] - Connection established
    • Measurements: %{system_time: integer()}
    • Metadata: %{transport: atom(), server: atom()}
  • [:ex_mcp, :connection, :lost] - Connection lost
    • Measurements: %{system_time: integer(), uptime: integer()}
    • Metadata: %{transport: atom(), server: atom(), reason: term()}

Authorization Events

Authorization events are emitted by the OAuth 2.1 subsystem:

  • [:ex_mcp, :authorization, :flow, :start | :stop] - OAuth flow lifecycle

  • [:ex_mcp, :authorization, :discovery, :start | :stop] - Metadata discovery

  • [:ex_mcp, :authorization, :token, :start | :stop] - Token exchange

  • [:ex_mcp, :authorization, :authorize, :start | :stop] - Authorization request

ACP Events

Agent Communication Protocol events:

  • [:ex_mcp, :acp, :session, :start | :stop] - ACP session lifecycle

  • [:ex_mcp, :acp, :prompt, :start | :stop] - ACP prompt processing

  • [:ex_mcp, :acp, :transport, :start | :stop] - ACP transport operations

Usage

# Attach a simple logger
ExMCP.Telemetry.attach_default_logger()

# Attach custom handlers
:telemetry.attach(
  "my-handler",
  [:ex_mcp, :server, :tool, :called],
  &MyApp.handle_event/4,
  nil
)

Summary

Functions

Attaches a default logger that logs all ExMCP events.

Detaches the default logger.

Executes a function and emits telemetry events.

Functions

attach_default_logger()

Attaches a default logger that logs all ExMCP events.

This is useful for debugging and development.

detach_default_logger()

Detaches the default logger.

span(event_prefix, metadata, fun)

Executes a function and emits telemetry events.

This is a convenience function for wrapping operations with telemetry.

Examples

ExMCP.Telemetry.span([:ex_mcp, :custom, :operation], %{id: "123"}, fn ->
  # Do some work
  {:ok, result}
end)