DEV Community

Cover image for I Renamed All 43 Tools in My MCP Server. Here's Why I Did It Now.
Wes
Wes

Posted on • Originally published at wshoffner.dev

I Renamed All 43 Tools in My MCP Server. Here's Why I Did It Now.

Charlotte has 111 stars. That's not a lot. But it's enough that a breaking change will annoy real people.

I shipped one anyway.

The naming problem

When I started building Charlotte in February, I named every tool with a colon separator: charlotte:navigate, charlotte:observe, charlotte:click. It looked clean. It felt namespaced. Every tool call in every session used it.

The problem: the MCP spec restricts tool names to [A-Za-z0-9_.-]. The colon character isn't in that set. It never was. I either didn't check or didn't care at the time. The MCP SDK was lenient about it until v1.26.0, which started emitting validation warnings on every tool registration.

I had two options. Fix it now with 111 stars and a handful of active users. Or fix it later with more stars, more users, more documentation, more muscle memory, and more pain.

We renamed all 43 tools from charlotte:xxx to charlotte_xxx in a single commit. Breaking change. Documented in the changelog. Migration note in the release.

Here's the thing about MCP: clients discover tools dynamically at connection time. When an agent connects to Charlotte, it asks "what tools do you have?" and Charlotte sends the current list. The agent doesn't care what the tools were called yesterday. So for most users, the upgrade is invisible. The old names simply don't exist anymore and the new names appear automatically.

The people who get hit are the ones with custom prompts or configurations that reference tool names as strings. That's a small group right now. It will be a much larger group in six months.

Breaking changes are cheaper when you're small. That's the whole argument. Ship it early, pay the small cost, avoid the large one later.

What else is in 0.6.0

Batch form filling. This one matters for token economics.

Before 0.6.0, filling a 10-field contact form meant 10 separate tool calls: charlotte_type for each text field, charlotte_select for dropdowns, charlotte_toggle for checkboxes. Each call carries tool definition overhead (the MCP server sends its tool schemas on every API round trip). Ten calls at ~4,000 definition tokens each is 40,000 tokens just in overhead, before any actual content.

charlotte_fill_form takes an array of {element_id, value} pairs and fills everything in one call:

{
  "fields": [
    { "element_id": "inp-a3f1", "value": "Jane Smith" },
    { "element_id": "inp-b7c2", "value": "jane@example.com" },
    { "element_id": "sel-d4e8", "value": "Enterprise" },
    { "element_id": "chk-f9a0", "value": "true" }
  ]
}
Enter fullscreen mode Exit fullscreen mode

One call. One set of definition tokens. The form is filled. It handles text inputs, textareas, selects, checkboxes, radios, toggles, date pickers, and color inputs. Type detection is automatic based on the element's role.

For a testing agent running form validation across 50 pages, this is the difference between 500 tool calls and 50. The token savings compound fast.

Lazy Chromium initialization. Charlotte used to launch a Chromium instance the moment the MCP server started. The problem: MCP clients like Claude Desktop and Cursor spawn all configured servers at startup, whether you're going to use them or not. If Charlotte is in your config but you're just writing code today, you had a headless browser burning RAM for nothing.

Now the browser launches on the first actual tool call. If you never browse, Chromium never starts.

Slow typing. charlotte_type gains slowly and character_delay parameters for character-by-character input. This sounds trivial until your agent tries to test a search-as-you-type field and the site's event handler only fires on individual keystrokes, not pasted text. Autocomplete, live validation, search suggestions. They all need real keystroke events.

Node.js 20 support. I was requiring Node 22 for no reason. No 22-only APIs were in use. Relaxing to >=20 opens Charlotte to the broader LTS user base.

The ASI bug, one last time

In v0.4.1, I found a bug where charlotte:evaluate silently returned null on multi-statement JavaScript. The cause was new Function('return ' + expr) combined with Automatic Semicolon Insertion. I wrote about it at the time.

I fixed it in evaluate.ts. Then found the same pattern in wait-for.ts and fixed it in 0.5.0.

0.6.0 found it a third time in pollUntilCondition, a utility function used by the wait system. Same bug. Same new Function('return ' + expr). Same migration to CDP Runtime.evaluate.

That's three separate files with the same broken pattern, discovered across three releases. Copy-paste bugs are persistent. If you find a pattern-level bug in your codebase, grep for every instance before you close the issue. I should have done that the first time.

7 strangers improved my code

When I started Charlotte in February, it was a solo project. 100% of commits from one person. An external evaluation in early March rated sustainability 2 out of 5 and flagged "97% single-developer commits" as the primary risk.

Six weeks later, seven people I've never met have merged code into Charlotte:

Teoman Yavuzkurt contributed three PRs: fixing the default viewport (800x600 was unrealistically small), solving a stale compositor frame bug in screenshots on SPA transitions, and fixing macOS symlink resolution in tests. Three different areas of the codebase. That's someone who read the code deeply enough to find problems across modules.

clawtom submitted two PRs: an O(1) lookup optimization for the snapshot store (replacing a linear scan with a Map index) and proper error logging for CDP failures in layout extraction. Both unsolicited. Both performance or reliability improvements that I hadn't prioritized.

Sandy McArthur, Jr. joined as a new contributor this cycle. Nuno Curado did the original security hardening back in February. kai-agent-free picked up the "read version from package.json" issue I had tagged as "good first issue." Nestor Fernando De Leon Llanos added the issue templates and community links.

I didn't recruit any of them. They found the project, read the code, and decided it was worth contributing to. The issue templates, the "good first issue" labels, the CONTRIBUTING guide, the test suite that gives contributors confidence their changes don't break things. All of that infrastructure exists to make contributing feel safe and worthwhile. It seems to be working.

The sustainability rating would look different today.

What's next

Charlotte is at 43 tools, 519 tests, and a 1.07:1 test-to-source line ratio. The structural tree view from 0.5.0 gives agents a full page map in under 2,000 characters. Iframe extraction handles embedded content. File output keeps large responses out of the context window. And now batch form fills collapse multi-step interactions into single calls.

The focus for the next cycle is the connect-to-browser feature: attaching Charlotte to an already-running Chrome instance instead of launching its own. This unlocks screen recording of agent sessions, live debugging, and the kind of demo videos that are worth more than any blog post.

Try it

npx @ticktockbent/charlotte@latest
Enter fullscreen mode Exit fullscreen mode

Works with any MCP client: Claude Desktop, Claude Code, Cursor, Windsurf, Cline, VS Code, Amp.

GitHub | npm | Charlotte vs Playwright MCP

Open source, MIT licensed. If you're running browser-heavy agent workflows, I'd like to hear how it holds up.

Top comments (0)