TL;DR: I built a lightweight Chrome MCP. Scroll to the end to learn how to install it. Read the whole post to learn a little bit about the Zen of MCP design.

Claude Code has built in tools to fetch web pages and to search the web – they actually run through Anthropic's servers, if I recall correctly. They do clever things to carefully manage context and to return information in a format that's easy for Claude to digest.

These tools work really well.

Right up to the point where they completely fall apart.

pasted image 20251019 180931

An uncoached testimonial from the only customer who matters.

Last week, I somehow got it into my head that I should update my custom blogging client to use Apple's new Liquid Glass look and feel.

The first issue I ran into was that Claude was absolutely sure that macOS 26 wasn't out yet. (Amusingly, when asked to review a draft of this post, one of the things it flagged was: 'Inconsistent model naming - You refer to "macOS 26" but I believe you mean "macOS 15" (Sequoia). macOS 26 would be way in the future.')

Claude was, however, happy to speculate about what a "Liquid Glass" UI might look like. Once I reminded the model that it had memory issues and Apple had indeed released the new version of their operating system, it was ready to get to work.

I told it to go read Apple's Human Interface Guidelines and make a plan. This is what Claude saw:

<div class="noscript"><h1 class="noscript-title">This page requires JavaScript.</h1><p>Please turn on JavaScript in your browser and refresh the page to view its content.</p></div></noscript><div id="app"></div>

It turns out that Apple no longer offer a downloadable version of the HIG. And the online version requires JavaScript . After a bit of flailing, Claude reached for the industry-standard Playwright MCP from Microsoft.

pasted image 20251019 181028

The Playwright MCP is a collection of 21 tools covering all aspects of driving a browser and debugging webapps, from browser_install to browser_click to browser_navigate_back. Just having the Playwright MCP available costs 13,678 tokens (7% of the whole context window) in every single session, even if you never use it. (Yes, the Google Chrome team has their own Chrome MCP. Its API surface is even bigger)

And once you do start using it, things get worse. Some of its tools return the entire DOM of the webpage you're working with. This means that simple requests fail because they return more tokens than Claude can handle in a response:

> Please use the playwright MCP to fetch the headlines from https://www.bbc.com 

⏺ I'll use the Playwright MCP to navigate to BBC.com and fetch the headlines for you.

⏺ **playwright - Navigate to a URL (MCP)**(url: "https://www.bbc.com")
  ⎿  Error: MCP tool "browser_navigate" response (25837 tokens) exceeds maximum allowed tokens (25000). Please use pagination, filtering, or limit parameters to reduce the response size.

⏺ Let me take a snapshot instead to get a more structured view of the page:
  ⎿  Error: MCP tool "browser_snapshot" response (36196 tokens) exceeds maximum allowed tokens (25000). Please use pagination, filtering, or limit parameters to reduce the response size.

⏺ The page is quite large. Let me try to extract just the headlines using JavaScript evaluation: [...]

It's frustrating to see a coding agent trying over and over to use a tool the way it's supposed to and having that tool just fail to return useful data.

A simple solution that's not an MCP #

After hearing me complain about this a few times, Dan Grigsby commented that he'd had success just asking Claude to teach itself a skill: Using the raw Dev Tools remote control protocol to drive Chrome.

This seemed like a neat trick, so I asked my Claude to take a swing at it.

Claude was only too happy to try to speak raw JSON-RPC to Chrome on port 9292. It...just worked. But it was also very clunky and wasteful feeling. Claude was writing raw JSON-RPC command lines for each and every interaction. It was very verbose and required the LLM to get a whole bunch of details right on every single command invocation.

It was time to make a proper Skill.

After thinking about it for a moment, I asked Claude to write a little zero-dependency command-line tool called chrome-ws that it could run with the Bash tool to control Chrome, as well as a new SKILL.md file explaining how to use that script. chrome-ws encapsulated the complexity and made Chrome easily scriptable from the command line.

The skill sets up the basics of web browsing with its tools and uses progressive disclosure to tell Claude how to get more information, but only when it has a need to know. For example, these examples of how to use the chrome-ws tool.

Claude didn't always reach for the skill, so it wasn't aware of its new command-line tool, but once I pointed it in the right direction, it worked surprisingly well.

This setup was incredibly token efficient – Nothing in the context window at startup other than a skill name and description in the system prompt. What was a little frustrating for me was that any time Claude wanted to do anything with the browser, it had to run a custom Bash command that I had to approve. Every click. Every navigation. Every javascript expression. It got old really, really fast.

There's no real way to fix that without creating a custom MCP.

Giving up and making an MCP #

But that would put us right back where we were with the official Playwright MCP, right? Nearly two dozen tools and 13k tokens spilled on the floor every time we started a session. Even trimming things down to only the dozen most important commands is still a bunch of tools, most of which Claude won't use in a given session.

If you've ever done API design, you probably know how important it is to name your methods well. You know that every method should do one thing and only one thing. You know that you really need to type (and validate) all your parameters to make sure your callers can tell what they're supposed to be passing in and to make bad method calls fail as soon as possible.

It would be absolutely unhinged to have a method called use_browser that took a parameter called action that was itself a method dispatcher, a parameter called selector, and a parameter called payload.

You'd have to be crazy to think that it's acceptable API design to have the optional, untyped payload field just have a description like ("Action-specific data: navigate=URL | type=text (append \\n to submit) | extract=format (text|html|markdown) | screenshot=filename | eval=JavaScript | select=option value | attr=attribute name | await_text=text to wait for

And yet. That is exactly how I designed it.

And it's just great.

The high-level tool description reads:

Control persistent Chrome browser via DevTools Protocol. Use the superpowers-chrome:browsing skill for detailed guidance.

CRITICAL: Selectors support CSS or XPath (XPath must start with / or //). Append \\n to payload in 'type' action to submit forms. State persists across calls.
  
Examples: {action:"click", selector:"//button[@type='submit']"} | {action:"extract", payload:"text", selector:"//h2"}

Workflows: navigate→await_element→extract | navigate→type(payload:"text\\n")→await_text

At session startup, the whole MCP config weighs in at just 947 tokens. I'm pretty sure I can shave at least 30-40 more.

It's optimized to make Claude's life as easy as possble. Rather than having a method to start the browser, the MCP...just does it when it needs to. Same with opening a new tab if there wasn't one waiting.

The tool description tells Claude what to do and where to read up when it needs more help. At least so far, it works just great for me.

One of the mistakes I made while developing the MCP was to instruct Claude to cut down the API surface by only accepting CSS selectors, rather than accepting CSS or XPath. It seemed natural to me that a smaller, simpler API would be easier for Claude to work with and reason about. Right up until I saw the MCP tool description containing multiple admonitions like CRITICAL: CSS selectors only (NOT XPath). The whole thing just...worked better when I let the selector fields accept either CSS or XPath.

Another thing that Claude got not-quite-right when it first implemented the MCP was that it included detailed human-readable description text for all the use_browser method parameters. Because LLMs that are using MCPs can see both the description and the actual JSON schema, you don't need to repeat things like lists of values for an enum or type validations. One trick you can use is to ask your agent to tell you exactly what it can see about how to use an API.

One of the weirdest realizations I had while building superpowers-chrome is this: I have no doubt that there are a dozen similar tools out there, but it was literally faster and easier to build the tool that I thought should exist than to test out a dozen tools to see if any of them work the way I think they should.

I have feels about designing for LLMs #

Over the last couple of decades, the common wisdom has become that Postel's Law (aka the robustness principle) is dated and wrong and that APIs should be rigid and rigorous. That's the wrong choice when you're designing for use by LLMs.

This might be a hard lesson to hear, but tools you build for LLMs are going to work much, much better if you think of your end-user as a "person" rather than a computer. Build your tools like they're a set of scripts you're handing to that undertrained kid who just got hired in the NOC. They are going to page you at 2AM when they can't figure out what's going on or when they misuse the tools in a way they can't unwind.

Names and method descriptions matter far more than they ever have before.

Automatic recovery is hugely important. Designing for error recovery rather than failing fast will make the whole system more reliable and less expensive to operate.

When errors are unaviodable, your error messages should tell the user how to fix or work around the problem in plain English.

If you can't give the user exactly what they asked for, but you can give them a partial answer or related information, do that.

Claude absolutely does not care about the architectural purity of your API. It just wants to help you get work done with the limited resources at its disposal.

Actually using it #

This new MCP and skill for Claude Code, is called superpowers-chrome.

You can install it like this:

/plugin marketplace add obra/superpowers-marketplace
/plugin install superpowers-chrome@superpowers-marketplace

If you're already using Superpowers, you can just type /plugin, navigate to 'Install plugins', pick 'superpowers-marketplace' and then you should see superpowers-chrome.

I'd love to hear from you if you find it helpful. I'd also love patches and pull requests.