I configured a production WordPress plugin without opening the browser once. Login, settings, widget placement, plugin upload, cache purge, debugging – all from Claude Code.
I needed to configure a multilingual plugin on our production WordPress blog. The plugin is CM Multilingual, which I built with Claude Code in 2 days. That normally means: open the admin, log in, navigate to settings, click through tabs, configure languages, add widgets, test the frontend, go back, adjust, test again.
Instead, I typed this into Claude Code:
let's add the language switcher and only show in home posts of that given language
And Claude did the rest. Not by generating a script – by actually using the browser.
How Chrome DevTools MCP reads the page
The Chrome DevTools MCP gives Claude access to your browser through the Chrome DevTools Protocol. Claude can navigate pages, read the DOM as an accessibility tree, click elements, fill forms, upload files, take screenshots, and run JavaScript.
Here's what the interaction actually looks like. I said:
hey claude i want you to go over the whole process with this plugin we have a theme prepared for it. You can use chrome devtools mcp
Claude started by listing the open browser pages:
chrome-devtools - list_pages
⎿ ## Pages
1: chrome://new-tab-page/ [selected]
No WordPress open yet. So Claude navigated to the login page, took a snapshot of the form:
chrome-devtools - take_snapshot
⎿ uid=2_5 textbox "Username or Email Address"
uid=2_7 textbox "Password"
uid=2_11 button "Log In"
Then filled in the fields and clicked login:
chrome-devtools - fill (uid: "2_5", value: "edy@codeminer42.com")
chrome-devtools - fill (uid: "2_7", value: "supersecretpassword123")
chrome-devtools - click (uid: "1_11")


That's it. No CSS selectors. No XPath. Claude reads the accessibility tree (the same structure screen readers use) and identifies elements by their semantic role and content.
Configuring languages on production
Claude navigated to Settings > CM Multilingual. A take_snapshot revealed the settings form. Claude selected "English" from a dropdown by referencing uid=2_134, clicked "Add Language", saw the notice about unassigned posts, clicked "Assign all to English", then added Portuguese.
I could follow along through screenshots Claude took at each step. But I didn't need to. Claude reported what it found and what it did.
After configuring both languages, I said:
i would like to see you translating a post using AI, also, i want you to add a language switcher to the home page.
Claude navigated to the Widgets page, found the "Language Switcher" widget in the available widgets list (uid=1_322), clicked to expand it, selected "Sidebar" as the target, and clicked "Add Widget". The widget appeared with flags and language names.
When I said the switcher should be at the top of the sidebar, not the bottom, Claude used the move controls in the WordPress widget UI to reorder it.
Uploading plugin updates
I needed to deploy a code fix. Claude:
- Ran
npm run buildto compile the JS - Created a zip file with
zip -r ~/Downloads/cm-multilingual.zip ... - Navigated to Plugins > Add Plugin > Upload Plugin
- Uploaded the zip file from my Downloads folder
chrome-devtools - upload_file (uid: "6_272", filePath: "/Users/edy/Downloads/cm-multilingual.zip")
⎿ File uploaded from /Users/edy/Downloads/cm-multilingual.zip.
- Clicked "Install Now", then "Replace current with uploaded"
- Purged the cache via the Caching dropdown in the admin toolbar
No FTP. No SSH. No file manager. Claude picked up a local file, uploaded it through the WordPress UI, and replaced the running plugin. We did this five times in one session as we iterated on fixes.
The diagnosis I couldn't find myself
The homepage was showing Portuguese posts mixed with English posts. I was stuck. When I tested locally with a clean WordPress install (same theme, just the plugin), everything worked fine. But production had dozens of plugins, custom block configurations, and layers of caching. Finding where the query was being modified in that environment was a different problem. I said:
it's appearing at https://blog.codeminer42.com/
Claude navigated to the Reading Settings page and found the site uses a static front page (page #6982). Then opened that page in the block editor and ran JavaScript to extract the Query Loop block configuration:
() => {
const content = wp.data.select('core/editor').getEditedPostContent();
return content.substring(0, 1000);
}


The response revealed the homepage has two Query Loop blocks:
- Block 1:
taxQuery: {"category": [465]}– Dev Weekly banner (1 post) - Block 2:
taxQuery: {"category": [21]}– Main post listing ("Posts" category only)
Our plugin was replacing the block's tax_query with the language filter, wiping the category filter. That's why Dev Weekly posts appeared – the category exclusion was gone.
This is what makes MCP work. A clean local install is easy to navigate. A production WordPress website with dozens of plugins and custom blocks is a different beast. Claude navigated the mess, inspected the block editor's internal state through wp.data, and found the root cause in one query. It learned where things were faster than I could.
How a hidden cookie silently overwrote the language detection
After deploying the language filter, the homepage started showing only Portuguese posts. I said:
home page is still showing portuguese posts, are u really sure this can't be configured in the Page?
Claude checked the cookies:
() => {
const cookies = document.cookie.split(';').map(c => c.trim());
return cookies.find(c => c.startsWith('cm_ml_lang='));
}
// Result: "cm_ml_lang=pt_BR"
A cm_ml_lang cookie from a previous /pt-br/ visit was overriding the URL-based language detection. Claude cleared the cookie, reloaded, and confirmed the fix. We updated the code to use URL-only detection for front-page secondary queries.
The hidden setting
The English language switcher link was pointing to /en/ instead of /. Claude navigated to Settings > CM Multilingual > URLs:
chrome-devtools - take_screenshot
⎿ [Screenshot showing "Hide language prefix for default language" UNCHECKED]
One checkbox. That was it. The setting was unchecked on production, even though it was checked on our local dev environment. Claude checked it, clicked "Save Changes", and both the switcher links and the homepage language detection started working correctly.
Without MCP, I would have been Alt-Tabbing between terminal and browser, manually checking settings pages. With MCP, Claude navigated directly to the setting and read the form state.
Testing what Facebook actually sees vs. what the browser renders
Someone shared our /pt-br/ homepage and the preview showed a random post's title and image. I said:
when i share the link https://blog.codeminer42.com/pt-br/ it shows the preview of a post, it shouldn't be like that
Claude started by inspecting the OG meta tags in the DOM via evaluate_script. That confirmed the problem – but then came the key insight: social crawlers don't execute JavaScript. We needed to test what Facebook actually sees:
curl -s https://blog.codeminer42.com/pt-br/ \
-H 'User-Agent: facebookexternalhit/1.1' | grep -E 'og:title|og:image'
The fix went through three iterations, each following the same loop: edit PHP locally, run tests, build zip, upload via Plugins page (Claude handled the upload each time), purge cache, curl to verify. First the title/description, then discovering AIOSEO has no filter for og:image, then using output buffering to replace the image URL in the raw HTML.
What I actually type
Here's the thing people ask: do I write detailed prompts? No. Here are actual things I typed during the session:
- "let's add the language switcher and only show in home posts of that given language"
- "language switcher should appear at the beginning of the sidebar not at the end. the readers must see it"
- "i just published the pt br post, i can tell you. it's appearing at https://blog.codeminer42.com/"
- "no it's not. language switcher, when I click on English, is going to /en, it should be /"
- "do it" (when Claude asked if it should be uploaded)
Claude figures out the rest. It reads the page, identifies what needs to happen, and executes. When something doesn't work, I describe the symptom and Claude diagnoses.
What makes this different from browser automation
I've used Puppeteer, Playwright, Selenium. This isn't automation – it's collaboration.
With Puppeteer, you write a script that assumes the page structure won't change. If the admin UI updates, your script breaks. With MCP, Claude reads the page on each run, adapts to UI changes, and handles unexpected dialogs. For something like "check the theme customizer for category exclusion options," you'd need to hardcode every DOM path in advance with Puppeteer. Claude navigated there, opened each section, read the options, and reported back that there were none.
Combined with code editing. In the same session, Claude edited PHP files locally, ran 156 PHPUnit tests, built the plugin, AND uploaded it to production through the browser. The feedback loop between writing code and seeing it work is instant.
Accessibility tree, not selectors. Elements are identified by semantic role and content (uid=1_11 button "Log In"), not by CSS classes or DOM paths that break when the page changes.
Setting it up
The MCP server is at github.com/anthropics/anthropic-mcp-tools/chrome-devtools. Install it:
npm install -g @anthropic/chrome-devtools-mcp
Then add it to your Claude Code MCP config (.claude/settings.json or .claude/settings.local.json):
{
"mcpServers": {
"chrome-devtools": {
"command": "npx",
"args": ["-y", "@anthropic/chrome-devtools-mcp"]
}
}
}
Chrome needs to be running with the DevTools Protocol enabled on port 9222. You can launch it manually:
open -na 'Google Chrome' --args \
--remote-debugging-port=9222 \
--user-data-dir=/tmp/chrome-debug-profile \
--no-first-run \
--no-default-browser-check
But here's the thing – you'll forget. Every time. And when Claude tries to use a chrome-devtools tool without a debug instance running, it fails silently or throws a cryptic error. So I added a Claude Code hook that auto-launches Chrome before any MCP tool call:
{
"hooks": {
"PreToolUse": [
{
"matcher": "mcp__chrome-devtools__",
"hooks": [
{
"type": "command",
"command": "if ! lsof -i :9222 -sTCP:LISTEN 2>/dev/null | grep -q '^Google'; then open -na 'Google Chrome' --args --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug-profile --no-first-run --no-default-browser-check >/dev/null 2>&1 & sleep 2; fi",
"timeout": 10,
"statusMessage": "Ensuring debug Chrome is running..."
}
]
}
]
}
}
This goes in your ~/.claude/settings.json under the hooks key. The matcher ensures it only runs when Claude is about to use a chrome-devtools tool. The command checks if port 9222 is already listening – if not, it launches a fresh debug Chrome instance and waits 2 seconds for it to start.
Rough edges I hit
The Chrome DevTools MCP is still relatively new. Here's what actually went wrong during my sessions.
Screenshots too large. The MCP takes screenshots constantly to understand the page. Some WordPress admin pages produce screenshots so large that Claude errors out with something like "image too large." This didn't happen often, but when it did I had to go back to a previous point in the conversation, before the oversized screenshot. Sometimes that didn't work either and I had to reopen Claude Code entirely.
Missed pop-ups. Claude performs an action, a pop-up or error toast appears for a second, but Claude was running a sleep command or waiting on something else. By the time it checks the page again, the pop-up is gone and Claude has no idea what happened. I had to jump in and say "a pop-up error appeared" or "check the console" to get it back on track.
Forgetting to check Network/Console. Claude doesn't always think to look at the browser's network requests or console logs. It focuses on the visible page. When something failed silently – no visible error, just wrong behavior – I had to nudge it: "check console", "check the network tab", "look at the logs."
It works well. Claude doesn't fill fields faster than you, and it doesn't "see" the page faster either. But it works at a good enough speed, and the real advantage is that every step is integrated with the coding agent. If Claude clicks a button and nothing happens, it tries something else. It can read the console and already check the code to figure out what's going on. While it's doing its thing, you can focus on other work.
When it does get lost – missed a pop-up, forgot to check the console – you redirect it. Think pair programming where you're the navigator, and Claude is driving the browser. You don't need to watch every second, but you check in.
Why I keep using it
The loop. Claude edits code, runs tests, builds a zip, uploads it to production, purges the cache, navigates to the page, checks if it works, and if not, diagnoses why – all in one conversation. "The homepage is showing Portuguese posts" leads to inspecting cookies, clearing them, verifying the fix, updating the code, and redeploying. No Alt-Tab, no forgetting what you were debugging.
Thanks for reading!
We want to work with you. Check out our Services page!

