# `Anubis.Telemetry`

Telemetry events emitted by Anubis.

Every event is namespaced under `[:anubis_mcp | event]`. The client emits
them, the server emits them, and the transports emit their own.

## Attaching handlers

    :telemetry.attach_many(
      "anubis-metrics",
      [
        [:anubis_mcp, :server, :request],
        [:anubis_mcp, :server, :tool_call, :stop]
      ],
      &MyApp.Metrics.handle_event/4,
      %{}
    )

## Events

Most events carry `%{system_time: System.system_time()}` as their only
measurement; the interesting payload is in the metadata. The
`:response` and `:error` events also carry `%{duration: elapsed_ms}`.

### Client

| Event | Metadata |
| --- | --- |
| `[:anubis_mcp, :client, :init]` | `client_name`, `transport`, `protocol_version`, `capabilities` |
| `[:anubis_mcp, :client, :request]` | `method`, `request_id` |
| `[:anubis_mcp, :client, :response]` | `id`, `method`, `status`, `transport` |
| `[:anubis_mcp, :client, :notification]` | `method`, `uri` |
| `[:anubis_mcp, :client, :error]` | `id`, `method`, `error` |
| `[:anubis_mcp, :client, :terminate]` | `client_name`, `reason`, `pending_requests` |
| `[:anubis_mcp, :client, :roots]` | `action`, `request_id` |

### Server

| Event | Metadata |
| --- | --- |
| `[:anubis_mcp, :server, :init]` | `session_id`, `capabilities`, `server_info`, `module` |
| `[:anubis_mcp, :server, :request]` | `id` |
| `[:anubis_mcp, :server, :response]` | `id`, `method`, `level` |
| `[:anubis_mcp, :server, :notification]` | `method`, `server_module`, `started_at` |
| `[:anubis_mcp, :server, :error]` | `id`, `error`, `message`, `session_id`, `stateless` |
| `[:anubis_mcp, :server, :terminate]` | `reason` |

### Tool calls

`[:anubis_mcp, :server, :tool_call]` is a `:telemetry.span/3`, so it emits
`:start`, `:stop` and `:exception` events. See `span_tool_call/3`.

### Authorization

`[:anubis_mcp, :server, :authorization]` is a `:telemetry.span/3` wrapping
the Streamable HTTP bearer authorization flow, so it emits `:start`, `:stop`
and `:exception` events. See `span_authorization/2`. The `:stop` metadata
carries `outcome`, one of `:ok`, `:missing_token`, `:invalid_token`,
`:invalid_issuer`, `:invalid_audience`, `:token_expired`, `:invalid_expiry`,
`:validator_unavailable`, `:other`. Authorization metadata never includes
tokens, claims, or raw validator error reasons.

| Event | Metadata |
| --- | --- |
| `[:anubis_mcp, :server, :authorization, :start]` | `validator` |
| `[:anubis_mcp, :server, :authorization, :stop]` | `validator`, `outcome` |
| `[:anubis_mcp, :server, :authorization, :exception]` | `validator`, `kind`, `reason`, `stacktrace` |
| `[:anubis_mcp, :server, :authorization, :validate]` (deprecated) | `validator` |

### Transport

Transport events always carry `transport` (the layer, e.g. `:stdio` or
`:streamable_http`). The rest varies by transport:

| Event | Metadata |
| --- | --- |
| `[:anubis_mcp, :transport, :init]` | `transport`, `client`, `io_device` |
| `[:anubis_mcp, :transport, :connect]` | `transport`, `client` |
| `[:anubis_mcp, :transport, :send]` | `transport`, `client` |
| `[:anubis_mcp, :transport, :receive]` | `transport`, `client`, `message_size` |
| `[:anubis_mcp, :transport, :disconnect]` | `transport`, `client`, `reason` |
| `[:anubis_mcp, :transport, :terminate]` | `transport`, `client`, `reason` |
| `[:anubis_mcp, :transport, :error]` | `transport`, `client`, `error` |
| `[:anubis_mcp, :transport, :sse_handler, :registered]` | `session_id`, `handler_pid`, `handler_count` (measurement) |

Treat metadata as open: new keys may be added, so match on the ones you
need and ignore the rest.

# `event_client_error`

# `event_client_init`

# `event_client_notification`

# `event_client_request`

# `event_client_response`

# `event_client_roots`

# `event_client_terminate`

# `event_server_authorization`

# `event_server_authorization_validate`

Deprecated single-shot event emitted before the validator runs.

Superseded by the `[:server, :authorization]` span: the `:stop` event's
bounded `outcome` reports whether validation succeeded and why it failed.
Kept during a deprecation window; attach handlers to the span instead.

# `event_server_error`

# `event_server_init`

# `event_server_notification`

# `event_server_request`

# `event_server_response`

# `event_server_terminate`

# `event_server_tool_call`

# `event_transport_connect`

# `event_transport_disconnect`

# `event_transport_error`

# `event_transport_init`

# `event_transport_receive`

# `event_transport_send`

# `event_transport_sse_handler_registered`

# `event_transport_terminate`

# `execute`

```elixir
@spec execute([atom()], map(), map()) :: :ok
```

Execute a telemetry event with the Anubis MCP namespace.

## Parameters
- `event_name` - List of atoms for the event name, excluding the :anubis_mcp prefix
- `measurements` - Map of measurements for the event
- `metadata` - Map of metadata for the event

# `span_authorization`

```elixir
@spec span_authorization(map(), (-&gt; {result, Anubis.Server.Authorization.outcome()})) ::
  result
when result: var
```

Wraps a bearer authorization flow in the `[:server, :authorization]`
telemetry span.

Emits `:start`, `:stop` and `:exception` events under
`[:anubis_mcp, :server, :authorization]`. The `start_metadata` map (typically
`%{validator: module}`) is used for the `:start` event and, with `outcome`
added, for the `:stop` event. The `:stop` measurements carry `duration`.

The fun returns `{result, outcome}` where `outcome` must be drawn from the
bounded set in `Anubis.Server.Authorization.outcome/1`. Tokens, claims, and
raw validator error reasons must not be added to the metadata.

## Examples

    Telemetry.span_authorization(%{validator: MyValidator}, fn ->
      {{:ok, conn, claims}, :ok}
    end)

# `span_tool_call`

```elixir
@spec span_tool_call(String.t() | nil, map() | nil, (-&gt; result)) :: result
when result: var
```

Wraps a tool call handler invocation in the `[:server, :tool_call]`
telemetry span.

Shared by the synchronous scheduler dispatch and the task-augmented
`tools/call` worker path so both surface identical span data regardless
of which route a given request took.

The span's `:start` metadata always carries `tool`; the `:stop` metadata
always carries `tool` and `is_error`. When
`:telemetry_capture_tool_payload` is enabled (defaults to `false`), the
`:start` metadata also carries `arguments` and the `:stop` metadata also
carries `result`. See `pages/testing.md` for the rationale behind the
opt-in default.

## Examples

    iex> Anubis.Telemetry.span_tool_call("get_weather", %{"city" => "NYC"}, fn -> :ok end)
    :ok

---

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