refactor(mcp)!: make elicitation the only "prompt" consent channel - #73
Merged
Merged
Conversation
Drop the Claude Code-specific fallback that emitted _meta["anthropic/requiresUserInteraction"] on tools/list and sent consent: "client" on tools.call. A "prompt"-policy call from an MCP client that doesn't declare the elicitation capability is now denied with policy_denied (no_consent_channel), like the CLI. The daemon rejects consent: "client" as invalid_request, and the tool mapper no longer takes the emit-flag argument. BREAKING CHANGE: Claude Code clients without elicitation support can no longer call "prompt"-policy tools over MCP; set the tool's policy to "allow" in config.json to keep it callable.
…lid_request Address review of #73: an older MCP server talking to a newer daemon now lands on the audited no_consent_channel denial. Restore coverage for consent on the error outcome, and document elicitation auto-accept, the daemon restart the "allow" workaround needs, and the narrowed @appduct/shared types.
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.
Summary
Removes the Claude Code-specific consent fallback for
"prompt"-policy tools. MCP elicitation is now the only way to satisfy"prompt". If the client doesn't declare theelicitationcapability, the call is denied.This is the first step toward MCP discovery through a list/describe/call set of built-in tools, with app tools no longer listed one-to-one as MCP tools. The per-tool
_metaflag can't survive that change, but elicitation works through a generic call tool.What changed
requiresUserInteractionchannel: theclaude-code≥ 2.1.199clientInfocheck, the per-connectionemittedRequiresUserInteractionbookkeeping, and the_metaemission intools/list.resolveToolCallConsentnow either asks via elicitation or returns no consent.McpToolMapperno longer takes theemitRequiresUserInteractionFlagargument, andMcpToolSchemadrops_meta.consentis now typed"elicitation"only.consent: "client"from an older MCP server is accepted and ignored. The call then lands on the auditedpolicy_denied/no_consent_channeldenial. Any other unknown value is stillinvalid_request.no_consent_channelhint no longer mentions Claude Code."prompt"gate denies the call, including across mixed CLI and daemon versions. The MCP server returns the denial as a recoverableisError: trueresult that names the tool and says how to change its policy.ARCHITECTURE.md§1, §5, §9, §12 and §14 andSECURITY.mdnow describe one consent channel.Elicitationhook accepts,claude -pcancels.CHANGELOG.mdhas a breaking-change entry.Breaking change
A client that relied on the flag without supporting elicitation now gets
policy_deniedfor"prompt"tools. Current Claude Code (checked with 2.1.278) declareselicitation, so it gets the elicitation prompt instead and isn't affected. I haven't checked whether any released version between 2.1.199 and 2.1.278 lacked elicitation.To fix it:
"allow". That removes the gate for every caller, including the CLI. It only takes effect after a daemon restart (appduct daemon stop), becauseconfig.jsonis read once at daemon start.@appduct/shared'sToolsCallParams.consentand the auditconsentfield narrow to"elicitation". Existing audit files may still containconsent: "client", and nothing reads those files back.Review
An adversarial review (fresh context, given only this PR) found no consent bypass: every path fails closed. Its findings were:
consent: "client"asinvalid_request, which skipped the audit log"client"is treated as no consent, giving an auditedno_consent_channeldenial. Tested.SECURITY.mdandARCHITECTURE.mdnow document auto-accept (Elicitation hook) and auto-cancel (claude -p)"allow"workaround needs a daemon restart and applies to every callerconsenton a non-ok outcomeconsent: "elicitation"@appduct/sharedtypes changed without a CHANGELOG noteTesting
policy-and-audit.integration.test.ts, 24 passing:claude-code2.1.199 client without elicitation gets no_meta. Its"prompt"call is denied withno_consent_channel, notool_callframe reaches the app, and the audit record has noconsent.consent: "client"is denied and audited asno_consent_channel.consentvalue isinvalid_request.consent: "elicitation".denyandallowtests are kept.pnpm turbo run test --filter=appduct --filter=@appduct/sharedpasses after the review fixes: shared 272, appduct 777 plus 1 skipped.pnpm check:linkspasses.