Skip to content

MCP: print/headless sessions advertise elicitation and auto-accept yes/no forms without a human #4185

Closed

Description

@code-yeongyu

Summary

In print mode (senpi -p, --mode json) and in other sessions with no human UI, senpi still advertises the MCP elicitation client capability and answers server forms on the user's behalf. A form with only yes/no fields is accepted with false for every field, without anyone seeing it.

Reproduction

  1. Configure any MCP server whose tool sends an elicitation/create form request with one boolean property (for example "Delete the branch?").
  2. Run senpi -p "call that tool".
  3. The server receives { "action": "accept", "content": { "<field>": false } }.

Expected (MCP 2026-07-28, client/elicitation.mdx "Capabilities", "Response Actions", "Security Considerations")

  • A session where no human can answer does not declare the elicitation capability, so a compliant server does not ask.
  • If a request arrives anyway, senpi answers decline without filling any field.
  • Only a human answer may produce accept.

Actual

  • elicitation.ts:20 declares { elicitation: {} } for every connection; modern/dispatch.ts:20,47,54 does the same on the 2026-07-28 path.
  • Print mode binds the extension runner's no-op UI (core/extensions/runner.ts:470-473,861-863), whose confirm resolves false and whose input/select resolve undefined.
  • mcp/index.ts:145-147 hands that no-op UI to MCP at before_agent_start, and elicitation.ts:99-126 treats false as the user's boolean answer, so a boolean-only (or all-optional) form returns accept.

Root cause

MCP decides "a UI exists" from the presence of a UI object, not from whether a human can answer (ctx.hasUI). The no-op UI used by print mode looks like a real UI to it.

Scope and acceptance

  • No elicitation capability is advertised by a print/json-mode process (legacy initialize and 2026-07-28 per-request _meta).
  • When the bound context has no human UI (hasUI === false) an elicitation request is answered decline and no UI primitive is called.
  • TUI and RPC sessions with a UI behave as today.
  • RED test first: a boolean-only form in a UI-less session returns accept on current main.

Pool routing across shared connections, MRTR handling and URL mode are separate follow-ups (tracked in the MCP spec-sync report).

Related

Activity

  1. code-yeongyu commented on Oct 24, 2026

    @code-yeongyu
    OwnerAuthor

    Ruling correction (2026-10-24): an earlier note in this work said a session that cannot ask a human should answer "cancel/decline". That was wrong for decline. In MCP, decline means the user explicitly refused, so we never send it on anyone's behalf. The behavior for this issue is:

    Telling an unattended client's cancellation apart from a person's dismissal needs a cancellation-origin contract; that is tracked with the trusted-presenter work in #4197.

  2. code-yeongyu commented on Oct 24, 2026

    @code-yeongyu
    OwnerAuthor

    Fixed by #4196, merged as 400fce9 into main (ships with the senpi release after 24-3).

    Evidence:

    • RED on the pre-fix code: a yes/no-only form in a print-mode session was accepted with false and no human (3 failing); after the later rework, 14 tests expecting the no-human processing error failed on the earlier head.
    • GREEN: 56 focused tests at the reworked head; 53 focused MCP tests at the final merge head; CI green (21 checks); exact-head static receipt green (install, check, typecheck, changes, build); review CLEAR.
    • Behavior: print/json processes advertise no elicitation capability; when no human can answer at request time the request gets a body-free processing error; a shown dialog that ends without a choice (human dismissal or an automated cancel) answers cancel; no path sends decline on anyone's behalf; explicit Yes/No answers keep their meaning (yes/no fields now show as a two-option pick).

    Follow-ups: an explicit Decline control and re-asking on an empty required field (#4188/#4189), the cancellation-origin contract (#4197), and two minor review notes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions