# `ExMCP.Telemetry`
[🔗](https://github.com/azmaveth/ex_mcp/blob/v1.0.0/lib/ex_mcp/telemetry.ex#L1)

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
    )

# `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`

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)

---

*Consult [api-reference.md](api-reference.md) for complete listing*
