Notes
Tools, not endpoints
by Omar Crosby · Updated July 14, 2026
What building three MCP servers taught us about designing for a probabilistic caller.
The obvious way to build an MCP server is to open your REST client, wrap each endpoint in a tool, and ship. Every route becomes a function, every parameter becomes a JSON Schema field, every response becomes whatever the upstream API happened to return. Two afternoons and you’re done.
Don’t.
We’ve now built three of these — one for ECNL youth soccer, one for the NWSL, one for the NFL — and the servers that survive contact with real agents look almost nothing like a mirror of their upstream APIs. The caller is not a script written by someone who read your docs. The caller is a model that will pattern-match your tool signatures and confabulate the rest. Every shape you get wrong is a hallucination generator.
The caller changes the shape
A human reading OpenAPI docs will forgive an ugly endpoint. They’ll figure out that GET /events?orgId=9,12,13,16 is a discovery walk, or that the “match detail” JSON has three different shapes depending on which sport it is, or that the ID you need for one call is buried in the response of another.
A model won’t. It will call the tool with the signature you handed it, get a shape it can’t reason about, and then make something up. Or it will invent an ID that looked plausible from a similar tool. Or it will call the same endpoint six times because your description didn’t tell it what the tool is for.
That makes the tool signature the specification. Not the reference documentation, not the ADR, not the source code. The signature. Which means the interesting work is in the shape.
Three principles from three servers
Curate the aggregates. Don’t proxy the endpoints. The single most valuable tools our MCP servers expose are the ones that don’t correspond to any upstream endpoint at all. get_rpi runs the NCAA-standard RPI computation over match results. get_strength_of_schedule derives from standings and schedules. get_adjusted_points_per_game computes a tier-weighted PPG. These are the analyses an analyst would actually want, computed inside the server as pure functions from the raw tools’ output. The upstream API doesn’t have them and never will. If the server only mirrored the endpoints, the agent would have to reconstruct the analytics itself — which it would, badly, in-band, on every conversation.
Give the agent a bridge from natural language to identifiers. The ECNL server’s find_events("ECNL Girls Southeast 2025-26") returns a numeric eventID. That’s it. That’s the whole tool. It exists because the alternative — asking the model to know or invent the right event ID — fails immediately. Every subsequent tool takes IDs as parameters, and IDs are the one thing a model cannot legitimately guess. A dedicated identifier-resolution tool as the first hop in every conversation is not a nicety; it is what makes the rest of the server usable.
Keep the surface small enough to fit. The ECNL server ships 12 tools. The NWSL server ships 16, grouped into four semantic buckets (core data, matches, analytics, editorial). The upstream APIs have hundreds of endpoints between them. The point is that an agent’s usable working memory for tools is small; a well-named twelve is worth more than an exhaustive fifty. Every additional tool competes for the model’s attention on every turn. Adding a tool is not free — the cost is paid by every other tool you already have.
Metadata is not decoration
Every tool in these servers is marked read-only and idempotent as part of its signature — not in a comment, not in the description string, but as structured annotations the MCP runtime can act on. readOnlyHint and its siblings are how you tell the harness that a tool cannot mutate anything, that calling it twice is the same as calling it once, that a retry after a network blip is safe. The runtime uses this to decide whether to require confirmation, whether to cache, whether to fan out.
If you don’t set these, the harness has to assume the worst about every tool you ship, and the friction lands on your users as per-call confirmation prompts. Nine tools with correct annotations beat forty tools that all look potentially dangerous.
What this leaves for next time
None of the above tells you how to know the tools stay correct when the caller is a coin flip — an LLM will find phrasings and parameter combinations no test suite anticipated. Eval infrastructure, replay against real production traffic, LLM-as-judge scoring — that’s a separate problem worth its own note. We’ve built that too, and it changed how we felt about deploying an MCP server. But it starts here, with the tool contract.
The endpoint you’d write for a script is not the tool you should write for an agent. If your MCP server looks like your REST API, you’ve built the wrong thing.