Skip to content

Migrate wait_for_text off the deprecated MCP Logging capability (SEP-2577) #109

Description

@tony

Summary

MCP has deprecated the protocol-level Logging capability. wait_for_text is the only tool in this server that uses it, via three _maybe_log calls that emit notifications/message. This server is stdio-only, so the specified migration path — log to stderr — is a direct swap onto the stdlib loggers the codebase already uses everywhere else.

Upstream status

MCP adopted a feature lifecycle and deprecation policy in SEP-2596, which defines Active/Deprecated/Removed states, a twelve-month minimum deprecation window, and a canonical deprecated features registry.

SEP-2577 (merged 2026-05-15) deprecates Roots, Sampling, and Logging, effective with the 2026-07-28 revision. The registry gives Logging's migration path as "Log to stderr for stdio transports; use OpenTelemetry for observability", and the rationale as "overlaps with stderr and OpenTelemetry". Earliest removal is the first revision released on or after 2027-07-28.

Separately, SEP-2575 removes logging/setLevel in the same revision and moves log level to a per-request _meta key, with a hard constraint: "servers MUST NOT emit notifications/message for requests that did not include this field". Under 2026-07-28 the current unconditional emissions are therefore not merely deprecated but non-conforming.

This is not urgent — the pinned SDK negotiates 2025-11-25 (mcp.types.LATEST_PROTOCOL_VERSION), where Logging is still Active — but the migration is small and there is no reason to carry it.

Affected code

All three call sites are in wait_for_text:

Site What it reports Already reaches the agent?
wait.py#L434 invalid regex Yes — identical text is raised as ExpectedToolError on the next line
wait.py#L730 history-limit trim-risk band No
wait.py#L838 no match before timeout Yes — WaitForTextResult.outcome == "timeout" plus elapsed

The helper is _maybe_log at wait.py#L124-L143; its own docstring names the wire method at wait.py#L133.

Why stderr rather than a result field

The two tests covering this behavior state the intent: the warnings exist "so MCP client log panels record the cause independent of the tool result" (test_pane_tools.py#L3997-L3999) and for "human log readers" (test_pane_tools.py#L4040-L4044). That is an operator/observability role, which is precisely what the spec redirects to stderr — and under stdio, MCP clients surface server stderr in those same log panels.

Promoting them to WaitForTextResult fields instead would contradict that model's stated design constraint: "Field count is load-bearing. outputSchema is re-sent on every request of every session, so a field that no agent branches on is a permanent tax." (models.py#L243-L246). Two of the three warnings are already redundant with the tool result or the raised error, and the third is documented as a caveat in the tool docstring at wait.py#L555-L561, where the agent meets it at tool-selection time.

Proposed change

  • Replace _maybe_log with stderr logging through the existing module logger; drop the transport-closed suppression, which has no analogue for stdlib logging.
  • Drop the now-unused ctx parameter from _compile_patterns.
  • Reword the ctx.warning reference in the wait_for_text docstring at wait.py#L559.
  • Rewrite the two tests to assert on captured log records rather than a recording context stub.
  • Add a MIGRATION note: MCP clients that displayed these three warnings in a log panel sourced from notifications/message will now receive them on stderr.

ctx.report_progress at wait.py#L114 is not affected. Progress notifications are not deprecated, and the 2026-07-28 draft explicitly keeps them flowing on the response stream of the request they relate to.

Out of scope

Checked and unaffected: no Roots, no Sampling, no includeContext, no HTTP+SSE transport, and no Dynamic Client Registration anywhere in src/ — the server is stdio-only at server.py#L468.

Two adjacent observations, deliberately not bundled here:

  • _meta keys minted at middleware.py#L313-L343 (error_type, expected, suggestion) are bare names. That is legal — the prefix is optional — but reverse-DNS namespacing is the spec's SHOULD, and bare names risk collision with future MCP-reserved keys.
  • SEP-2596 obliges Tier 1 SDKs to emit a runtime DeprecationWarning when a deprecated feature is exercised. pyproject.toml currently silences DeprecationWarning for libtmux.*, libtmux_mcp.*, and tests, which would mask exactly that signal once the SDK starts emitting it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions