Three stacked bugs blocked LinkedIn posts from an MCP server
Author אחיה כהן debugged three compounding `isTrusted:false` failures in Safari MCP's `safari_fill` tool that silently prevented LinkedIn posts from ever succeeding.
Score breakdown
Developers building MCP servers or browser-automation agents that target rich-text editors should audit their fill strategies for `isTrusted:false` rejections and focus-steal side effects, and consider targeting framework-internal APIs (like Lexical's `__lexicalEditor`) instead of synthetic DOM events.
- 01Safari MCP is an MCP server that drives a logged-in Safari browser and exposes 80 tools, with `safari_fill` being the most-used.
- 02Three stacked bugs — not one — caused LinkedIn posts to silently fail while the agent reported success each time.
- 03Bug 1: a `blur()` call at the end of the fill path triggered LinkedIn's `focusout`-based modal dismissal, destroying the text ~40ms after it was written.
אחיה כהן describes how `safari_fill`, the most-used tool in their Safari MCP server, worked reliably across Gmail, GitHub, Google Docs, Shopify admin, and other sites for three months — until LinkedIn's share composer was tested. The agent consistently reported success, but posts never appeared. The root cause was three independent bugs that each looked like success to standard automation tooling.
The first bug was a `blur()` call at the end of the fill path, originally included to trigger React state commits.
The first bug was a `blur()` call at the end of the fill path, originally included to trigger React state commits. LinkedIn's composer listens for `focusout` on any descendant to detect "clicked outside" and dismiss the modal, so the `blur()` caused the dialog — and the filled text — to vanish roughly 40ms after being written. Removing `blur()` fixed this layer, since modern contenteditable React components commit state from `input` events alone.
The second bug was `isTrusted:false` rejection. LinkedIn's composer used ProseMirror at the time of initial debugging, and ProseMirror's paste handler explicitly checks `event.isTrusted`, rejecting any event not dispatched by the browser itself. Every synthetic approach — `new ClipboardEvent('paste')`, `execCommand('insertText')`, character-by-character `beforeinput` dispatch — produced `isTrusted:false` and was silently rejected. The fix was routing through a real OS paste via AppleScript setting the system clipboard and firing a macOS `CGEvent` Cmd+V, which the browser sees as a genuine user action. However, this triggered the third bug: activating Safari via `NSApplication activateIgnoringOtherApps` caused a brief focus steal during which the composer's `focusout` listener fired again, dismissing the dialog before the paste landed. The final, stable solution bypassed all of this by targeting LinkedIn's current Lexical editor directly — querying the DOM root with `[data-lexical-editor="true"]`, accessing the `__lexicalEditor` instance, and calling `editor.setEditorState()` with a parsed state object. This produces zero synthetic events, zero focus shifts, and zero clipboard interaction, letting Lexical update its own internal state through its normal React diff path.
Key facts
- 01Safari MCP is an MCP server that drives a logged-in Safari browser and exposes 80 tools, with `safari_fill` being the most-used.
- 02Three stacked bugs — not one — caused LinkedIn posts to silently fail while the agent reported success each time.
- 03Bug 1: a `blur()` call at the end of the fill path triggered LinkedIn's `focusout`-based modal dismissal, destroying the text ~40ms after it was written.