Add logging utility (logging/setLevel, notifications/message) #8

Closed
opened 2026-05-17 15:56:08 +00:00 by claude-noether · 1 comment
Collaborator

Add the Logging utility — client-settable log level and a server→client log-message notification stream.

Goal

Today lmcp writes diagnostics to io.stderr, which is invisible to the client. Tools like shell/fetch/web_search that decide to log "DDG returned 202 anti-bot" or "renderer cache miss" have nowhere to surface that. The spec's logging utility provides a structured channel.

Methods to add

Method Notes
logging/setLevel Args { level: "debug"|"info"|"notice"|"warning"|"error"|"critical"|"alert"|"emergency" }{}
notifications/message Server-initiated. { level, logger?: string, data: any }.

API for lmcp

server:log("info", "ddg", { event = "anti_bot", status = 202 })
-- Suppressed if below client-set level. Falls back to stderr if no client
-- has called logging/setLevel yet (preserves current behaviour for stdio
-- debugging).

Capabilities

capabilities.logging = {}

Scope (v1)

  • All 8 RFC-5424 levels.
  • Server-side filter: drop messages below the client's set level.
  • Default level: "warning" until the client sets one.
  • Best-effort delivery — if there's no open SSE connection, log to stderr as today. Don't queue.

Depends on

  • Real server-initiated transport for the notification to actually reach the client. Lives behind the Streamable HTTP issue. v1 of this issue can ship with stderr-fallback only and start sending the notification once Streamable HTTP lands.

Priority

Medium-low. Real value gated on bidirectional transport. Half a day with stderr fallback; rest unlocks naturally with the transport rewrite.

Add the **Logging** utility — client-settable log level and a server→client log-message notification stream. ## Goal Today lmcp writes diagnostics to `io.stderr`, which is invisible to the client. Tools like `shell`/`fetch`/`web_search` that decide to log "DDG returned 202 anti-bot" or "renderer cache miss" have nowhere to surface that. The spec's logging utility provides a structured channel. ## Methods to add | Method | Notes | |---|---| | `logging/setLevel` | Args `{ level: "debug"\|"info"\|"notice"\|"warning"\|"error"\|"critical"\|"alert"\|"emergency" }` → `{}` | | `notifications/message` | Server-initiated. `{ level, logger?: string, data: any }`. | ## API for lmcp ```lua server:log("info", "ddg", { event = "anti_bot", status = 202 }) -- Suppressed if below client-set level. Falls back to stderr if no client -- has called logging/setLevel yet (preserves current behaviour for stdio -- debugging). ``` ## Capabilities ``` capabilities.logging = {} ``` ## Scope (v1) - All 8 RFC-5424 levels. - Server-side filter: drop messages below the client's set level. - Default level: `"warning"` until the client sets one. - Best-effort delivery — if there's no open SSE connection, log to stderr as today. Don't queue. ## Depends on - Real server-initiated transport for the notification to actually reach the client. Lives behind the **Streamable HTTP** issue. v1 of this issue can ship with stderr-fallback only and start sending the notification once Streamable HTTP lands. ## Priority **Medium-low**. Real value gated on bidirectional transport. Half a day with stderr fallback; rest unlocks naturally with the transport rewrite.
Author
Collaborator

Implemented (with stderr fallback). Added in lmcp.lua:

  • RFC-5424 level table (debug=8 .. emergency=1)
  • server:log(level, logger, data) emits to stderr (immediate) AND enqueues notifications/message for future Streamable-HTTP delivery (issue #16)
  • Server-side filter drops messages below the client-set level
  • logging/setLevel dispatch with -32602 on missing/unknown level; returns {} (json.empty_object sentinel) on success
  • Capability logging:{} advertised iff opts.logging = true at construction (opt-in, mirrors resources/prompts/completions discipline)
  • Default level warning until the client picks one

Verified live over stdio: setLevel(info) accepts; setLevel(bogus) → -32602; emit cycle drops below threshold; queue accumulates emitted entries; bad level / missing level both → -32602.

Delivery of notifications/message to the client remains gated on issue #16. Stderr fallback is the user-visible behaviour today.

Implemented (with stderr fallback). Added in lmcp.lua: - RFC-5424 level table (debug=8 .. emergency=1) - `server:log(level, logger, data)` emits to stderr (immediate) AND enqueues `notifications/message` for future Streamable-HTTP delivery (issue #16) - Server-side filter drops messages below the client-set level - `logging/setLevel` dispatch with -32602 on missing/unknown level; returns `{}` (json.empty_object sentinel) on success - Capability `logging:{}` advertised iff `opts.logging = true` at construction (opt-in, mirrors resources/prompts/completions discipline) - Default level `warning` until the client picks one Verified live over stdio: setLevel(info) accepts; setLevel(bogus) → -32602; emit cycle drops below threshold; queue accumulates emitted entries; bad level / missing level both → -32602. Delivery of `notifications/message` to the client remains gated on issue #16. Stderr fallback is the user-visible behaviour today.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marfrit/lmcp#8