# `ConduitMcp.Log`
[🔗](https://github.com/nyo16/conduit_mcp/blob/v0.11.0/lib/conduit_mcp/log.ex#L1)

Log notifications (`notifications/message`) from inside a running tool.

On the modern (`2026-07-28`) era a client opts into log messages per
request, with `_meta["io.modelcontextprotocol/logLevel"]`. Such a
`tools/call` is answered over Streamable HTTP as a Server-Sent Events
stream, and `log/4` sends a `notifications/message` ahead of the response
when `level` is at least as severe as the requested level:

    handle fn conn, %{"query" => query} ->
      ConduitMcp.Log.log(conn, :info, %{"query" => "planning"}, logger: "sql")
      text(run(query))
    end

The specification forbids log notifications for a request that did not
ask for them, so `log/4` sends nothing — and returns `:ok` — when the
request carried no `logLevel`, when `level` is below it
(`ConduitMcp.Ctx.log?/2`), and whenever there is no stream to send on: a
legacy-era request (answered with a single JSON body), a client that did
not accept `text/event-stream`, or a call running through
`ConduitMcp.Handler.handle_request/3`. Tools can therefore call it
unconditionally.

Never log credentials or other secrets: the client receives `data` as is.

# `level`

```elixir
@type level() ::
  :debug
  | :info
  | :notice
  | :warning
  | :error
  | :critical
  | :alert
  | :emergency
  | String.t()
```

An MCP logging level, as an atom or a string (`:info` or `"info"`).

# `log`

```elixir
@spec log(Plug.Conn.t(), level(), term(), keyword()) :: :ok
```

Sends a `notifications/message` at `level` with JSON-serializable `data`
on the request `conn` belongs to, when that request asked for it.

## Options

  * `:logger` — the name of the logger issuing the message

Raises `ArgumentError` for a level that is not an MCP logging level.

---

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