MCP Browser Automation: Why Rich-Text Editors Fail Silently
Every browser automation tool — Playwright, Puppeteer, Chrome DevTools MCP, Browserbase — dispatches synthetic DOM events when you call fill() or type(). For plain <input> elements and ordinary forms, this works. For rich-text editors sitting inside dialogs — LinkedIn’s share composer, Notion’s page editor, JIRA’s comment box, Google Docs — it silently fails in a way that is almost impossible to detect from the outside.
Your agent reports success. The log shows “Filled”. The screenshot even shows the text briefly on screen. Then the dialog closes, the text is not there, and the user wonders why their AI cannot post.
Here is the anatomy of that failure — and why fixing it requires understanding three separate security boundaries most automation tools do not account for.
The three boundaries
Boundary 1: focusout dismisses the dialog
Modern rich-text composers in dialogs (LinkedIn’s share box, Shopify’s description editor, Notion’s inline editor) listen for the focusout event on their content root. The intent is UX — if you click outside the editor, close the modal.
Browser automation tools that end their fill flow with element.blur() (to trigger React’s commit cycle) accidentally fire focusout. The modal treats it as “user clicked away” and dismisses. The text that was just filled disappears with the dialog.
Many teams have spent hours debugging “my text appears for a moment then vanishes.” The fix is to not call .blur() — React detects the change from input events alone.
Boundary 2: isTrusted:false rejection in editor paste handlers
event.isTrusted is a security property. It is true only when the browser itself dispatches an event (real keystroke, real paste, real click). Every new Event() or dispatchEvent() call from JavaScript produces isTrusted:false.
Rich-text editors check this deliberately. ProseMirror’s paste handler, Lexical’s clipboard plugin, Draft.js’s beforeinput processor — all of them reject isTrusted:false events as a policy decision. This is not a bug; it prevents malicious scripts from secretly replacing content in an editor a user is about to sign or publish.
Synthetic ClipboardEvent('paste'), synthetic InputEvent('beforeinput'), execCommand('insertText') — none of these produce trusted events. So every automation tool’s fill path quietly bounces off the editor and reports success, because dispatchEvent itself returns normally even when the handler did nothing.
Boundary 3: Native OS paste triggers Boundary 1
The classic workaround for Boundary 2 is a real operating-system paste — AppleScript setting the clipboard, macOS CGEvent firing a real Cmd+V, or the Windows SendInput equivalent.
This works — the browser sees a real paste event, isTrusted is true, the editor accepts the content. But to deliver the keystroke to the right window, the automation tool must make the target browser the frontmost window. That activation causes the previous frontmost app to lose focus momentarily, which in turn fires a focusout event on the editor — and we are back at Boundary 1.
The paste lands, but on the wrong target. The dialog has already closed.
Why this matters for AI agents
Agents driving browsers to create content (post to LinkedIn, comment on GitHub PRs, draft Notion pages, file JIRA tickets) hit all three boundaries constantly. The observable symptoms look identical to success: the automation returns “filled”, the visible DOM briefly shows the text, the next step proceeds. Only the target application’s internal state — the thing the user actually cares about — reflects nothing happened. This is the same class of issue we discuss in our broader AI agents for business guide — agentic systems that report success without verifying the underlying state are dangerous in production.
This is particularly bad for agentic workflows because the error is not caught. Every downstream step continues as if the content was posted. The agent reports completion. The user discovers the failure hours later, when a customer expects the LinkedIn announcement that was never published.
The real fix: editor-native API access
The boundaries above all assume the automation tool is pretending to be a user. The solution is to stop pretending.
Lexical (LinkedIn, Meta, Shopify) exposes the editor instance on its DOM root:
const editorEl = document.querySelector('[data-lexical-editor="true"]');
const editor = editorEl.__lexicalEditor;
const newState = editor.parseEditorState(lexicalJson);
editor.setEditorState(newState);
ProseMirror exposes it through pmViewDesc:
const view = editorEl.pmViewDesc.view;
const tr = view.state.tr.insertText(text, view.state.selection.from);
view.dispatch(tr);
Draft.js — now deprecated but still in GitHub, Reddit, and older Facebook surfaces — exposes it through React refs via Fiber walking.
None of these calls generate synthetic events. The editor updates its own internal state directly. React re-renders through its normal diff path. Lexical’s invariants hold. The dialog stays open because no focusout was dispatched.
This is not a hack. These are the same APIs that the editor’s own plugins, autosave, and undo/redo use internally. They are undocumented but stable — the editors themselves have been in production for years with these surfaces.
What to verify when evaluating MCP browser tools
If you are selecting a browser automation MCP server for agent workflows that involve content creation, test the following specifically:
- Can it post to LinkedIn from an agent prompt?
- Can it type a multi-line comment on a GitHub pull request and submit?
- Can it fill a Notion page body with formatted text?
- Can it create a JIRA ticket with a description that is not just plain text?
Silent failure on any of these — “success” reported but target app shows nothing — indicates one of the three boundaries is blocking the tool.
Safari MCP and the Lexical path
Safari MCP ships the Lexical-native fill path in version 2.9.4. It is the only MCP server I am aware of that handles the LinkedIn composer end-to-end without user intervention. The ProseMirror path ships in the same release.
Safari MCP is open source (MIT), runs via npx safari-mcp, and works with Claude Code, Cursor, VS Code, Windsurf, Claude Desktop, and any other MCP-compatible agent on macOS.
Every major MCP browser tool will need to solve this eventually — the rich-text surface is too common to leave as silent-failure territory. In the meantime, if you are seeing “agent reports success but nothing posted,” the three boundaries above are where to start looking.
For business contexts where these automations actually run, see our WhatsApp Business automation guide and the broader business automation framework covering how to design verification into agent workflows.
Losing leads because no one's answering?
A WhatsApp bot answers, schedules, and captures leads 24/7 — from $1,000 one-time. Free consultation →
Chat on WhatsAppSee full pricing · our projects · how it works
Ready to automate your business?
50+ businesses already save 15 hours/week. Tell me about yours — I'll show you exactly what we can automate.
Free Consultation on WhatsAppResponse within hours · No commitment