Tag: MCP

  • The Boring Layer That Made the Other Two Work

    Neither of the other two tools I’ve written about recently would exist without this one, and it’s the one I almost didn’t write about, because on its own it doesn’t have a moment. It has a spreadsheet.

    Two separate systems needed to read the same kind of data — spreadsheet-based, hand-maintained, the kind of source of truth that lives in someone’s tabs because it predates any of the automation built around it since. One system needed it for forecasting. The other needed it for real-time list assignments. Both were reading from Google Sheets. Both were going to hit the API rate limits if they each re-read the whole workbook on every check, which is exactly what the first version of each did independently, before I noticed they were solving the same problem twice.

    The unglamorous fix was hash-based change detection — before pulling a worksheet’s full contents, check whether anything actually changed since the last read, and skip the pull entirely if it didn’t. It’s not a clever idea. It’s the obvious idea, the one you reach for once you’re annoyed enough by watching two tools hammer the same sheet for no reason.

    What surprised me wasn’t the fix, it was how much friction disappeared once it existed. Both consumers got faster and more reliable at the same time, for free, because neither of them had to think about rate limits anymore — that problem moved down a layer and got solved once instead of twice.

    The struggle here isn’t technical, it’s motivational. This is the least interesting tool I maintain. It doesn’t forecast anything, it doesn’t catch anything, it just reads a sheet and remembers what it already read. It’s tempting to skip documenting infrastructure like this, because there’s no dramatic bug story attached to it. But the forecasting system and the outlier-detection tool both silently depend on this one being correct, and if it drifts, they both look broken for reasons that have nothing to do with either of them.

    The lesson: the tool nobody notices is often the one everything else is quietly standing on, and it deserves the same rigor as the tools that get the credit.

    Next: extending the same compatibility pattern to a third consumer, now that two have already proven the interface holds.

  • The Bug That Looked Like Slow, and Was Actually Broken

    It was one of those checks that should’ve taken thirty seconds. I ran a search against a real list — a few hundred leads, standard call, nothing exotic — and it just sat there. No error. No result. Just quiet.

    I assumed it was slow. I’d built the thing to hit an internal API and page through records, so “slow” was the obvious story, and I believed it for longer than I should have. I even started looking at whether I needed to add caching.

    Then I ran the same search on a smaller list — thirty records instead of three hundred — and it worked instantly. That’s when I knew it wasn’t slow. Slow doesn’t have a cliff. Broken does.

    The real problem was a single field. My data model marked customer email as an optional, validated email field — which sounds correct, and is correct, right up until the source system’s convention for “no email on file” turns out to be an empty string instead of a null. Pydantic’s email validator doesn’t know what to do with an empty string. It doesn’t skip it. It rejects it. And it rejects it silently enough, deep enough in a batch operation, that the whole search just — stopped. No traceback pointing at the actual cause. Just nothing.

    I’d been debugging the wrong problem for the better part of an hour. I was optimizing for a diagnosis I’d made before I had any real evidence for it, and once I’d said “it’s probably slow” out loud, I kept looking for reasons that were true instead of reasons that were right.

    The fix was small once I found it — one validator that runs before the email check, converting empty strings to null so the real validation logic still applies to anything that’s actually malformed. Seven new tests to make sure it stayed fixed. But the fix isn’t the lesson. The lesson is that “it’s slow” and “it’s broken” produce completely different debugging paths, and picking the wrong one costs you real time before you even notice you’re on it.

    I’ve started treating my own first explanation as a hypothesis to disprove, not a starting point to build on. The five-minute version of that discipline: before you optimize anything, prove it’s actually the bottleneck you think it is.

    Next: going back through every other endpoint in the same tool with the same question — not “is this slow,” but “have I actually confirmed that, or just assumed it.”