The answer, before the server gets it
A person fills in an Elicitation dialog and presses submit. That answer does not go straight back to the MCP server. ElicitationResult runs first, on the far side of the same request, and a hook here can read what was typed, replace it, or downgrade the response before the server sees any of it. The matcher works as it does on Elicitation: mcp_server_name, or omit it for every server.
The payload carries permission_mode, mcp_server_name, and action, plus optional mode, elicitation_id, and content, the form values the person submitted.
Replace, downgrade, or pass through
The JSON answer lives under hookSpecificOutput with hookEventName: "ElicitationResult", an action of accept, decline, or cancel, and optional content that replaces the submitted form values when the action is accept. systemMessage and continue are discarded. Exit 0 sends the person’s own response unless the JSON overrides it. Exit 1 is the same, with the JSON still read.
Exit 2 means decline, whatever else you said
Exit 2 sets the effective action to decline, regardless of what the person chose. Stderr is shown nowhere, and hookSpecificOutput is ignored even when the hook wrote valid JSON. The hooks reference makes this event and Elicitation the two where a non-zero code discards the JSON rather than reading it.
You write a hook that strips one field from the submitted form. It reads content, removes the field, prints accept with the trimmed values, and exits 2 because it changed something.
The server receives decline. The person filled in the form, pressed submit, and the server was told no. The trimmed values were discarded with the rest of the JSON.
Exit 0 with the JSON is the override path. On this event exit 2 is decline and nothing else.