An auth-cache PostToolUse handler checked `payload.tool_response.session_token` and called `writeToken(...)` when present. The MCP server returned its JSON via `mcp.NewToolResultText(string(respJSON))` (mark3labs/mcp-go SDK), which serializes as `{content: [{type:'text', text:'<JSON-string>'}]}`. The hook fired on every successful `github_session` and `erc8004_auth` call, but `payload.tool_response.session_token` was always undefined (the wrapper was returned as-is), so the cache file was never created — directory `~/.cache/<plugin>/tokens/` simply never appeared. The outer try/catch swallowed the silent miss, and tracing wasn't enabled, so the bug looked like "hook never fires". Fix in `extractResult`: before falling through to the object/string branches, detect `Array.isArray(raw.content)`, find the `{type:'text'}` part, and `JSON.parse(textPart.text)` to get the actual response object.
Applies to Claude Code plugin hooks (PostToolUse) that observe MCP tool results, where the MCP server uses `mcp.NewToolResultText` (or the Python/TS equivalents that wrap responses as `{content:[{type:'text',text:string}]}`). Native non-MCP tools (e.g. Bash) deliver `tool_response` as a flat object (e.g. `{stderr: '...'}`), so handlers for those tools never hit this envelope. Verified on @anthropic-ai/claude-code with `mark3labs/mcp-go` server, May 2026.