Skip to content

feat: add public setter methods for ClientSession callbacks#2407

Open
dgenio wants to merge 1 commit intomodelcontextprotocol:mainfrom
dgenio:feat/client-session-set-callback-methods
Open

feat: add public setter methods for ClientSession callbacks#2407
dgenio wants to merge 1 commit intomodelcontextprotocol:mainfrom
dgenio:feat/client-session-set-callback-methods

Conversation

@dgenio
Copy link
Copy Markdown

@dgenio dgenio commented Apr 9, 2026

What changed

  • src/mcp/client/session.py: added three public methods to ClientSession:
    • set_sampling_callback(callback)
    • set_elicitation_callback(callback)
    • set_list_roots_callback(callback)
  • Removed # pragma: no cover from _default_elicitation_callback (now covered by test)
  • tests/client/test_list_roots_callback.py: added test_set_list_roots_callback
  • tests/client/test_sampling_callback.py: added test_set_sampling_callback
  • tests/client/test_elicitation_callback.py: new file with test_set_elicitation_callback

Why

ClientSession accepts callback parameters at initialization but provided no public API to update them at runtime. Any client needing to change callbacks (e.g., updating roots after a user changes working directory) was forced to mutate private attributes like _list_roots_callback directly — coupling consumers to implementation details. This was the pattern used in PrefectHQ/fastmcp#3714.

Each setter accepts callback | None. Passing None restores the default behaviour (returning an error to the server), consistent with the __init__ fallback pattern. The docstrings note that capabilities are advertised during initialize() and won't be re-negotiated by calling these setters.

How verified

uv run --frozen pytest tests/client/test_list_roots_callback.py tests/client/test_sampling_callback.py tests/client/test_elicitation_callback.py -v
# 7 passed

uv run --frozen pytest tests/client/ -q
# 188 passed, 3 skipped, 1 xfailed

uv run --frozen ruff format .   # 357 files left unchanged
uv run --frozen ruff check .    # All checks passed

uv run --frozen pyright src/mcp/client/session.py tests/client/test_elicitation_callback.py tests/client/test_list_roots_callback.py tests/client/test_sampling_callback.py
# 0 errors, 0 warnings, 0 informations

Tradeoffs / risks

  • Capability mismatch: if a callback is added after initialize(), the server won't know about the capability until a new session is created. This is documented in each setter's docstring.
  • logging_callback / message_handler: not included — the issue only names the three callbacks above. Can be added as a follow-up if desired.
  • strict-no-cover cannot run on Windows; CI will verify on Ubuntu.

Scope notes

Changes are strictly limited to the three callbacks named in #2379. No refactoring of existing code.

Add set_sampling_callback(), set_elicitation_callback(), and
set_list_roots_callback() methods to ClientSession, allowing callbacks
to be updated at runtime after initialization without mutating private
attributes directly.

Also removes the # pragma: no cover from _default_elicitation_callback
and adds coverage via the new test for set_elicitation_callback(None).

Reported-by: dgenio
Github-Issue: modelcontextprotocol#2379
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant