Skip to content

docs: correct ROADMAP's elicitation/sampling/roots next-tier bullet (#411) - #413

Merged
chuofringer merged 4 commits into
mainfrom
auto/issue-411
Aug 25, 2026
Merged

docs: correct ROADMAP's elicitation/sampling/roots next-tier bullet (#411)#413
chuofringer merged 4 commits into
mainfrom
auto/issue-411

Conversation

@chuofringer

@chuofringer chuofringer commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Summary

Closes the investigation half of #411. Part 1 (wiring MRTR/Resolve/Elicit into the cold-graph needs_confirm gates) was investigated against the pinned SDK and stopped short of implementation — see the finding below. Part 2 (ROADMAP correction) ships in this PR.

Part 1 finding: SDK's Resolve/Elicit cannot express this without breaking the hard requirements

Investigated mcp 2.0.0's Annotated[T, Resolve(fn)] + Elicit(message, schema) MRTR support (.venv/lib/python3.11/site-packages/mcp/server/mcpserver/resolve.py) end-to-end, including a throwaway in-process proof registering a real MCPServer tool with a Resolve-annotated confirm parameter.

Two facts, confirmed both by reading the SDK and by running the proof:

  1. The resolved parameter is dropped from the published inputSchema entirely. Tool.from_function in mcp/server/mcpserver/tools/base.py:92-99 does skip_names.extend(resolved_params) before calling func_metadata(fn, skip_names=skip_names, ...). In the proof, a tool declared with lat, lon, mode, confirm: Annotated[bool, Resolve(...)] published an inputSchema with only lat, lon, modeconfirm vanished. It cannot be set by the caller at all once Resolve-annotated.
  2. A capability-less client gets a hard protocol error, not a graceful fallback. resolve.py:665-700 (_require_capability) raises MCPError(MISSING_REQUIRED_CLIENT_CAPABILITY) when the client hasn't declared the elicitation capability. In the proof, calling resolve_arguments with confirm=True already in the tool args (simulating a legacy client explicitly retrying with the hand-rolled protocol) still raised:
    MCPError: Client did not declare the form elicitation capability required by resolver '...'
    
    because the argument is never consulted — the resolver runs unconditionally, and lacking capability is a hard error, not a "the resolver never runs" no-op.

Both of these directly conflict with #411's acceptance criteria: confirm must stay client-suppliable, and capability-less clients must keep today's needs_confirm envelope byte-identically. There is no way to keep confirm in the published schema and have it resolver-filled — the SDK's skip_names mechanism is unconditional. And there is no documented/observed fallback path when the client lacks the capability — it's a hard MCPError, which would change behavior for every existing capability-less client calling route/from_to/isochrone/find_places(within=...) today.

This also rules out reusing the two existing func_metadata monkeypatches (_publish_from_keyword, _publish_output_schemas in src/placeroot/server.py:5364/5432) to paper over the schema-visibility gap: _publish_from_keyword rebuilds tool.parameters/tool.fn_metadata after registration, but tool.resolved_params/tool.resolver_plans (which drive the actual resolve-vs-error-vs-fallback behavior at call time) are fixed at Tool.from_function time and are untouched by that patch — so even forcing confirm back into the displayed schema wouldn't fix the hard-error-on-missing-capability problem underneath it.

Per #411's own stop condition ("If investigation reveals the SDK's Resolve/Elicit CANNOT express this without breaking schema publication or the existing monkeypatches, STOP part 1... An honest negative result is acceptable; a contorted implementation is not"), Part 1 is not shipped. Ambiguity-candidate elicitation (ambiguous_place/ambiguous_area) was out of scope per the issue and wasn't separately investigated — noted as an open follow-up, though it would hit the same schema/capability wall for any parameter that must stay settable by capability-less clients.

Part 2: ROADMAP correction

North-star: this is both a spec-change opportunity (MRTR is new, real SDK surface) and competitive tracking against mapbox/mcp-server#245, which tracks the same 2026-07-28 migration.

Test plan

  • uv run pytest -q — 2368 passed, 0 failed (baseline for this branch, off main at 2790e91; matches, no regressions)
  • uv run ruff check . — clean
  • No mass reformat — diff is CHANGELOG.md + docs/ROADMAP.md only
  • No schema/docstring changes, so no docs/benchmarks.md/docs/benchmarks-vs.md regen needed
  • Docs-only PR: no tool behavior changed, so no new tests needed beyond the throwaway (not committed) proof script that produced the finding above
  • CHANGELOG.md ### Changed entry added (docs-only correction, not a feature)

🤖 Generated with Claude Code

Closes #411 — part 1 is a documented negative result (revisit condition recorded in the corrected ROADMAP bullet); part 2 shipped.

…411)

Investigated wiring the pinned SDK's MRTR support (Resolve/Elicit,
mcp 2.0.0) into the needs_confirm cold-graph gates and stopped short of
implementing: a Resolve-annotated parameter is dropped from the tool's
published inputSchema and is always resolver-filled, so a capability-less
client gets a hard MCPError instead of today's needs_confirm envelope —
incompatible with keeping confirm client-suppliable. Spec rev 2026-07-28
also deprecated sampling and roots (SEP-2577), so the roadmap's roots
geofence bullet is corrected to point at #409's PLACEROOT_HOME instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@chuofringer

Copy link
Copy Markdown
Owner Author

Lead review (loop)

Docs-only diff verified; suite green on the branch (2368 passed, ruff clean, matching its off-main baseline).

This is the right outcome and the right write-up: the two SDK facts (Resolve-annotated params dropped from published inputSchema via skip_names, base.py:92-99; hard MISSING_REQUIRED_CLIENT_CAPABILITY with no fallback path, resolve.py:665-700) were confirmed empirically, not just read, and each directly violates the issue's hard requirements — confirm staying client-suppliable and capability-less clients keeping byte-identical needs_confirm behavior. Declining to contort the architecture and instead recording the negative with a revisit condition is exactly what the roadmap correction needed. The stale roots-geofence bullet is also now accurately tied to what #409 actually shipped.

I added Closes #411 to the body — the issue's deliverable (investigation + correction) is complete. Ready for owner merge (loop does not merge).

claude added 3 commits August 25, 2026 04:01
… ROADMAP note

list_roots's deprecation marker sits on mcp.server.session.ServerSession
(mcp/server/session.py:316), not under mcp.client.session; and isochrone
carries no needs_confirm gate — the gated tools are route/from_to/
meeting_point/suggest_areas/find_places-within plus warmup_city's own
confirm gate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YCuTkstNj5KscUQ9u8cLMK
@chuofringer
chuofringer merged commit 394d731 into main Aug 25, 2026
8 checks passed
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.

MRTR/elicitation for confirm gates + ROADMAP correction for deprecated elicitation/sampling/roots

2 participants