feat(tools): MCP elicitation handler with gateway-aware approval routing - #43112
Closed
lgalabru wants to merge 1 commit into
Closed
feat(tools): MCP elicitation handler with gateway-aware approval routing#43112lgalabru wants to merge 1 commit into
lgalabru wants to merge 1 commit into
Conversation
Wires support for the MCP `elicitation/create` request (Python SDK 1.11+) so MCP servers can ask the user to confirm sensitive operations mid-tool-call (payment authorization, OAuth confirmation, etc.) instead of failing closed or requiring out-of-band biometrics. Behavior: - `tools/mcp_tool.py` adds `ElicitationHandler`, attached per server task and passed to `ClientSession` as `elicitation_callback`. Form-mode requests route through the existing approval system; URL-mode requests decline cleanly (out of scope for this pass). - `tools/approval.py` adds `request_elicitation_consent()`, which dispatches to whichever surface owns the active session — `_await_gateway_decision` for Telegram / Slack / etc. (so the approval prompt lands on the right platform), `prompt_dangerous_approval` for CLI / TUI. Fails closed on timeout, missing notify_cb, or exception. - The MCP tool wrapper snapshots `contextvars.copy_context()` into `MCPServerTask._pending_call_context` before each `session.call_tool` and clears it after. The recv-loop task that dispatches incoming `elicitation/create` requests does not inherit the agent task's contextvars (HERMES_SESSION_PLATFORM and friends), so without the bridge `_is_gateway_approval_context()` returns False on every gateway session and the elicitation falls through to a CLI prompt that has no TTY → fail-closed decline. The handler now reads the snapshot via its `owner` back-reference and replays it through `Context.copy().run(...)` so attribution survives the task hop. Tests (`tests/tools/test_mcp_elicitation.py`): - form-mode accept / decline / cancel - URL-mode declined without prompting - exception in approval system → decline - timeout in approval → cancel - context-bridge regression tests (replay observed in consent call, missing-context fallback, multiple-replay safety, owner with cleared `_pending_call_context`) Verified end-to-end against pay's MCP server on macOS: agent message arrives via Telegram, agent calls `mcp_pay_curl` against a paid endpoint, pay returns 402, ElicitationHandler routes the approval prompt back to the originating Telegram chat, user replies in TG, the curl tool signs and completes. Platforms tested: macOS 14 (darwin/arm64). No Unix-only syscalls introduced; Windows footgun checker passes on the touched files.
Contributor
|
Verification review — no issues found. Well-structured implementation of MCP elicitation support with strong safety properties. Checked:
The |
teknium1
added a commit
that referenced
this pull request
Jun 19, 2026
Contributor
|
Merged via PR #49203. Your commit was cherry-picked onto current main with your authorship preserved in git log (commit 239740a). Thanks for the contribution — clean implementation that mirrors the existing SamplingHandler pattern, all 15 tests plus the broader MCP suite pass, and E2E-verified against the real approval routing. Closes this. |
gnalvesteffer
pushed a commit
to gnalvesteffer/hermes-agent
that referenced
this pull request
Jun 19, 2026
xyshanren
pushed a commit
to xyshanren/hermes-agent-cn
that referenced
this pull request
Jun 25, 2026
waefrebeorn
pushed a commit
to waefrebeorn/slermes
that referenced
this pull request
Jul 2, 2026
habarmc1223-sudo
pushed a commit
to habarmc1223-sudo/hermes-agent-fluxmem
that referenced
this pull request
Jul 8, 2026
santhreal
pushed a commit
to santhreal/hermes-agent
that referenced
this pull request
Jul 13, 2026
Gravezzz
pushed a commit
to Gravezzz/hermes-agent
that referenced
this pull request
Jul 21, 2026
leewenjie
pushed a commit
to leewenjie/hermes-agent
that referenced
this pull request
Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Adds support for the MCP
elicitation/createrequest (Python SDK ≥ 1.11) so MCP servers can ask the user to confirm sensitive operations mid-tool-call — payment authorization, OAuth confirmation, anything that previously failed closed or required local biometrics that don't exist on a server / VM / Telegram chat.The handler routes the prompt through Hermes's existing approval system, so the confirmation surfaces on whichever surface owns the active session (CLI, TUI, Telegram, Slack, etc.) — same UX as
prompt_dangerous_approvalfor shell commands.The non-obvious part is the contextvar bridge. The MCP recv-loop task that dispatches incoming
elicitation/createrequests is a different asyncio task from the one running the agent's tool call; it doesn't inheritHERMES_SESSION_PLATFORMor any of the other session-scoped contextvars. Without bridging, every gateway session was incorrectly classified as a CLI session, hitprompt_dangerous_approvalin a worker thread with no TTY, and fail-closed declined every elicitation. The fix snapshots the agent'scontextvars.copy_context()on the server task right beforesession.call_tool, then replays it inside the elicitation handler viaContext.copy().run(...)so attribution survives the task hop.Related Issue
No upstream issue — surfaced while integrating an MCP server (pay) whose payment authorization needs a user prompt on a remote messaging surface.
Type of Change
Changes Made
tools/mcp_tool.py:ElicitationHandlerclass,_format_elicitation_schema_summaryhelper,_pending_call_contextslot onMCPServerTask, contextvars snapshot aroundsession.call_tool, owner back-reference on the handler.tools/approval.py:request_elicitation_consent()— dispatches to_await_gateway_decisionfor gateway sessions,prompt_dangerous_approvalfor CLI/TUI; fails closed on timeout / missing notify_cb / exception.tests/tools/test_mcp_elicitation.py: 15 unit tests covering form-mode accept/decline/cancel, URL-mode decline, timeout, exception fail-closed, and four regression tests for the context bridge.How to Test
uv sync --extra mcp --extra devuv run pytest tests/tools/test_mcp_elicitation.py -v— 15 passed.elicitation/create):~/.hermes/config.yamlundermcp_servers:hermes gateway setupto wire Telegram (or any gateway platform)hermes gateway runVerified locally end-to-end against
pay(an MCP server that callselicitation/createto authorize a payment): agent message arrives via Telegram → agent callsmcp_pay_curlagainst a paid HTTP endpoint → pay returns 402, fireselicitation/create→ handler routes the approval back to the originating Telegram chat → reply approves → curl signs and completes.Checklist
Code
uv run pytest tests/tools/test_mcp_elicitation.py -vand all tests passDocumentation & Housekeeping
cli-config.yaml.examplechanges (the elicitation handler is configured per-server under the existingmcp_servers.<name>block; it activates automatically when the MCP SDK reports elicitation capability)scripts/check-windows-footguns.py tools/mcp_tool.py tests/tools/test_mcp_elicitation.pyis clean