Wymcp.Telemetry (Wymcp v0.1.1)

View Source

Telemetry events emitted by Wymcp.

Consuming applications can attach handlers to these events for monitoring, logging, and metrics.

Events

  • [:wymcp, :session, :start] — session created during initialize

    • Measurements: %{system_time: integer()}
    • Metadata: %{session_id: String.t(), client_info: map(), era: :legacy} — always :legacy: session machinery is the legacy lane
  • [:wymcp, :session, :expired] — session terminated due to idle timeout

    • Measurements: %{system_time: integer()}
    • Metadata: %{session_id: String.t(), era: :legacy} — always :legacy: session machinery is the legacy lane
  • [:wymcp, :session, :not_found] — request bearing an unrecognised Mcp-Session-Id rejected with HTTP 404

    • Measurements: %{system_time: integer()}
    • Metadata: `%{session_id: String.t(), request_id: term()nil,
      method: String.t()nil, era: :legacy}— always:legacy`:
      session machinery is the legacy lane
  • [:wymcp, :tool, :start] — tool execution starting

    • Measurements: %{system_time: integer()}
    • Metadata: %{tool_name: String.t(), action: String.t() | nil, session_id: String.t() | nil, era: :modern | :legacy}

  • [:wymcp, :tool, :stop] — tool execution completed

    • Measurements: %{duration: integer()} (native time units)
    • Metadata: `%{tool_name: String.t(), action: String.t()nil,
      session_id: String.t()nil, is_error: boolean(),

      error_kind: :dispatch | :tool | nil, era: :modern | :legacy}`

  • [:wymcp, :tool, :error] — tool raised an exception

    • Measurements: %{duration: integer()}
    • Metadata: `%{tool_name: String.t(), action: String.t()nil,
      session_id: String.t()nil, request_id: term(),
      exception: String.t(), error: String.t(), era: :modern:legacy}`

For tool events, action is the raw "action" string from the call arguments — what the client actually sent — or nil when the arguments carried none (a help call's arguments carry the target action, which is echoed here). On [:wymcp, :tool, :stop], is_error mirrors the MCP result's isError flag for tool-returned errors, and error_kind classifies that error's origin: :dispatch — a gate rejected the call before the tool's action handler ran (wymcp's dispatch gate, or a hand-written run/2 classifying its own gate rejection); :tool — the tool ran and answered with an error. error_kind is nil exactly when is_error is false. Both keys are :stop-only — neither [:wymcp, :tool, :start] nor [:wymcp, :tool, :error] carries them, so read them with Map.get/3 from a handler attached to more than one of these events. The error_kind vocabulary may grow; match it with a fallback clause, never exhaustively.

Every event above carries era (docs/glossary.md, era) naming the lane that served the request — except the two auth events: the auth check is an era-invariant wire check and runs before era classification exists on the conn, so those two carry no era key. A tool called in-process, on a Wymcp.Context.t/0 that never reached the wire, had no lane to name; those calls report :modern. That covers the caller, not the vocabulary — the value set stays :modern | :legacy on every path, and carries no growth clause. Read era with Map.get/3 from any handler attached across event families.

era is legacy-only: it exists only because two eras do, and the key goes at the legacy decommission.

  • [:wymcp, :help, :called] — the help tool answered a call (including error answers), emitted after the answer is resolved so the metadata carries the outcome. The authoritative introspection record; the same call also emits the generic tool events above with tool_name: "help". Two cases drop this event. A call rejected by help's arguments gate never resolves a target, so it emits nothing here and surfaces only as [:wymcp, :tool, :stop] with error_kind: :dispatch — reconciling the two streams will show :tool-level rows with no :called row, and that is the reason. A raise inside help also drops it — the call still surfaces as [:wymcp, :tool, :error] with tool_name: "help" (the target tool echo is lost; action still carries the target action, per the note above).

    • Measurements: %{system_time: integer()}
    • Metadata: %{tool: String.t() | nil, action: String.t() | nil, level: :index | :tool | :action, session_id: String.t() | nil, is_error: boolean(), era: :modern | :legacy}tool/action echo the requested target exactly as sent, even when they name nothing (a probe for a nonexistent target is itself signal); level is which answer level the parameter shape addressed; is_error is whether the answer was an error answer.

  • [:wymcp, :auth, :reject] — auth module returned {:error, reason}

    • Measurements: %{system_time: integer()}
    • Metadata: %{auth_module: module(), reason: String.t(), request_id: term() | nil, method: String.t() | nil, http_method: String.t()}
  • [:wymcp, :auth, :error] — auth module raised an exception

    • Measurements: %{system_time: integer()}
    • Metadata: %{auth_module: module(), exception: String.t(), error: String.t(), request_id: term() | nil, method: String.t() | nil, http_method: String.t()}

For auth events, http_method is the conn's HTTP verb ("POST", "GET", "DELETE") — as this library observed it, so an upstream rewriter such as Plug.Head reports its rewritten verb. request_id and method come from the parsed request body and are nil off POST — http_method is what distinguishes a GET/DELETE reject from a POST reject whose body did not parse.

  • [:wymcp, :server, :reject] — the consumer's Wymcp.Server.init/2 returned {:error, reason}; the session is terminated and the notifications/initialized request answered with a JSON-RPC internal_error

    • Measurements: %{system_time: integer()}
    • Metadata: %{server: module(), session_id: String.t(), reason: term(), request_id: term() | nil, era: :legacy}reason is the raw term the callback returned; era is always :legacy (session machinery)
  • [:wymcp, :server, :error] — the consumer's Wymcp.Server.init/2 raised, exited, or threw; treated as a rejection (same session termination and internal_error answer)

    • Measurements: %{system_time: integer()}
    • Metadata: %{server: module(), session_id: String.t(), exception: String.t(), error: String.t(), request_id: term() | nil, era: :legacy}exception is the exception struct name for a raise, or "exit" / "throw" for the other kinds; era is always :legacy (session machinery)

Summary

Functions

emit(component, event, measurements \\ %{}, metadata \\ %{})