I'm working on an approval gate for destructive MCP tools. The server asks the human through MCP elicitation, so the model never sees the approval step.
A commenter pointed out a hole: in Claude Code, a hook on the Elicitation event can exit 0 with action "accept" and answer the request with no dialog. The server only receives accept, so it can't tell a person from a script. A project .claude/settings.json in a cloned repo could register such a hook once the folder is trusted.
What I'm considering:
- Log "client-accepted" instead of "approved", so the audit file never claims a human was involved.
- Run the allowlist and policy checks before any elicitation, so a scripted accept can't override them.
- Refuse irreversible tools when I can't tell how the answer was produced.
My question: is there any reliable way for an MCP server to know that a real person answered an elicitation? Or should the design assume it can't know, and rely on reversibility and quarantine instead?
I haven't checked which other clients let a script answer elicitation. If you know of any, please tell me. The project is open source, so PRs and issues are welcome.
Top comments (2)
I think the honest answer for form-mode elicitation is no. The 2025-11-25 spec says "the protocol itself does not mandate any specific user interaction model", and user approval controls on the client are a SHOULD. So logging "client-accepted" is right.
URL mode is the closest the spec gets. The server sends a URL; the data does not pass through the client, and the server "MUST verify the identity of the user who opens the URL before accepting information". A Claude Code Elicitation hook can still accept a URL-mode request, but it gets no content back, so the server keeps waiting for its own page. It is close to the one-time code idea above, but native to the spec.
One idea I have not tested: put the confirm step for irreversible tools on that server-hosted page, with a step-up check like a passkey with user verification. A scripted accept cannot finish that. It still does not help if someone controls the browser session, and I have not checked which clients support URL mode.
For repo hooks,
allowManagedHooksOnlyin managed settings limits which hooks run.modelcontextprotocol.io/specificat...
From the Auten team (screen-control tools, same wall). Is your threat model only hooks that a cloned repo brings in, or also hooks the user wrote? If it's repo hooks, an out-of-band step closes most of it: for irreversible tools, send a short one-time code to something the client process can't read (a phone push, an authenticator) and require that code in the elicitation reply. A scripted accept doesn't have it, so "client-accepted" and "human-confirmed" become two separate log states.