DEV Community

Alex @Bickov
Alex @Bickov

Posted on

I built an MCP connector for my own app. It found two bugs that had been sitting in production

SlimSnap is a screenshot tool for Mac and Windows. Scroll capture, annotation, and it turns a capture into structured JSON instead of a flat image. Last week I shipped an MCP connector for it, a small local server so Claude Code, Claude Desktop, and Cursor can read a capture directly instead of you pasting an image into the chat.

Building the connector meant actually measuring SlimSnap's own output for the first time in months. That's when I found the two bugs below. Neither came from a bug report. Both were sitting in production since launch.

Bug one: measuring distance to the wrong point

SlimSnap links an annotation arrow to the element it's pointing at, so an agent gets "this arrow points at the Continue button" instead of just a pair of coordinates. The first real capture I ran through the connector had an arrow resting on a "Continue with phone" button. It linked to a heading above the button instead.

The code measured distance from the arrow to each element's center. A short heading close to the arrow can have a center nearer than a wide button's center, even when the arrow tip is sitting right on the button's edge. On a real 1030x1348 capture: center distance gave the heading 192px and the button 251px, heading wins. Edge distance gave the button 74px and the heading 141px, button wins, correctly.

Fix was switching from center distance to distance-to-rectangle. Three lines of logic, months in production before the connector surfaced it because nobody had looked this closely at the linking before.

Bug two: 19 characters to say 5

Coordinates were exported as 64-bit floats when 32-bit was always enough. So a value that should read 0.048 got written as 0.04800000041723251. Same number, one a human would never type and a model has to parse anyway.

It's not just noise. Every element in every capture carries 3-4 coordinates. On a busy page with 100+ elements that's hundreds of these strings, each one about 4x longer than it needs to be, in every JSON export since the format shipped. Fixed in 0.5.1 by exporting at the precision the data actually needs.

Neither bug was found by a user complaining. Both were found by pointing my own tool at my own output and actually reading what came back.

What the connector is for, and what it isn't

The reason JSON instead of an image matters comes down to how vision models handle a screenshot. They cap the long edge at 1568px. A 2540x18410 scrolling capture arrives at the model as 216x1568, about 8.5% of its original size, and 16px body text renders under 2px tall. Unreadable, not just compressed.

For a single screen that isn't hitting that cap, JSON is a real win. One capture of an X login screen came out to 377 tokens as JSON versus 1,813 tokens as an image of the same screen, already under the 1568px limit and not even downscaled. About 4.8x smaller, and the text in it is exact, not OCR guesswork off a compressed image.

For a tall page it's more honest than that. A full 18,410px scroll capture came out to 11,372 tokens as JSON versus 448 tokens for the same page as a downscaled image. The image is cheaper. It's also close to unreadable at that size. So the real trade is token cost against whether the model can actually read what you sent it, not JSON being universally cheaper, because on a tall page it isn't.

The connector has four tools: get the latest capture, list recent captures, get a specific one, and an opt-in image tool that returns the same 1568px-capped PNG if you want it anyway. Works in Claude Code, Claude Desktop, and Cursor. Doesn't work in Codex right now, its tool result cap is 10KB and it drops images entirely once a response has structured output alongside them, that's an open bug upstream, not something I can fix from SlimSnap's side.

Install:

claude mcp add slimsnap -- npx -y @slimsnap/mcp
Enter fullscreen mode Exit fullscreen mode

Or grab the desktop bundle from the GitHub release if you're on Claude Desktop or Cursor. One thing worth knowing before you install: Claude Code gates MCP tools per tool and per directory, so the first few captures trigger a permission prompt each. There's a user-wide allow list in the README if that gets old fast.

Repo and docs: slimsnap.ai/connector

Top comments (2)

Collapse
 
mads_hansen_27b33ebfee4c9 profile image
Mads Hansen

This is a great example of an integration becoming an executable specification for the product it exposes. I’d turn both discoveries into contract/property tests so the connector keeps doing that job after the immediate fixes.

For arrow linking, useful invariants are stronger than example fixtures: a tip inside a rectangle has distance zero; translation and uniform scaling do not change the winner; widening an element around the same tip cannot make it less eligible. Then fuzz aspect ratios, overlaps, nested elements, and ties rather than only replaying the 1030×1348 capture.

For coordinates, define the serialization contract explicitly: accepted absolute error, canonical decimal form, maximum characters per number, and round-trip tolerance. A golden corpus can track both semantic correctness and token size per capture, so a change that preserves values but doubles payload cost still fails visibly.

I’d also add response-budget tests across MCP clients: full capture, paginated regions, text-only fallback, and oversized structured results. The Codex limit you hit is exactly the kind of consumer constraint that belongs in the connector’s compatibility matrix, not in tribal knowledge.

Collapse
 
bickov profile image
Alex @Bickov

The distance-to-edge invariant would've caught the bug in seconds instead of me squinting at screenshots. Stealing that for a test.

Don't have response-budget tests across clients yet. Only know Codex breaks over 10KB because a user hit it, not because I tested for it.

Right now I diff JSON by eye. That's basically how the precision bug sat there, 0.048 stored as 0.04800000041723251, nobody caught it until it got big enough to see.