# Lessons from designing MCP tools for AI agents

> Source: <https://blog.spill.coffee/p/trust-the-harbor-pilot>
> Published: 2026-07-10 16:00:06+00:00

# Trust the harbor pilot

### Lessons from designing MCP tools for AI agents

Model Context Protocol (MCP) seems like a simple enough task to achieve. You already have an API for your application that your UI uses, recycle relevant pieces through code re-use and wrap them nicely for AI to digest. An AI digesting MCP implementation should just know to reach for your tool over rolling their own; their user installed it didn’t they?

That was my thought anyway.

Quick context for new readers: [FlurryPORT](https://flurryport.io) captures your incoming webhooks at a stable URL and replays them into your local app, signatures intact. I set out in a design session with AI to come up with the key areas that should be implemented. Discussed security concerns and wrapped them into the plan. I even spent an entire afternoon scrutinizing an upgrade path from my free offering at [FlurryPORT.dev/try](https://flurryport.dev/try). A few coding sessions later and my product had an effective MCP stdio implementation — or so I thought.

I fired up a rival coding AI to what I used as a coding aide and started prompting. I started with non-leading questions to mimic a user who hadn’t thought through their prompts, but loosely knew about how to go about their task. I quickly found that AI was grasping at every tool it could imagine to send/receive webhooks except those provided by my implementation.

I decided to stop my test and just ask directly: how was this MCP implementation, what would make it better? I was surprised how easy it was to gather requirements and I learned a few things. I then handed the next version of design back to the test AI agent to review before building it.

### Design MCP tools for agents

**Make polling rewarding**— Having bland data that isn’t expressive is not appealing to AI. This I found counter intuitive, in that isn’t AI just a computer and shouldn’t it just derive all sorts of usefulness give raw numbers. This I found counterintuitive: isn't AI just a computer, and shouldn't it derive all sorts of usefulness given raw numbers? But the model isn't calculating, it's reaching for whatever most reliably moves the task forward, and a bare number gives it nothing to act on. The difference is easy to see in the payload.

```
{ "captureCount": 12 }
```

vs.

```
{
  "captureCount": 12,
  "capturesRemaining": 88,
  "burst": { "perMinute": 30, "usedThisMinute": 22 },
  "notices": ["8 captures until session cap"],
  "actions": [
    { "label": "Claim this session to keep captures", "cost": "free", "effect": "removes cap", "url": "https://flurryport.io/claim/..." }
  ]
}
```

**Affordances beat prose**— A paragraph informing AI that it should pace requests to the server was ignored. A simple`capture_count`

tool, whose description says what it's for, got called between every batch.*If you find yourself writing guidance prose, ask what tool would make the guidance unnecessary.***Return structured facts**— Receipts, diagnosis codes and an`action[]`

with`label/cost/effect/url`

got relayed to the user verbatim on every run. A canned explanation with well crafted prose was our tool trying to do the model’s job.*Let the model narrate for its user and give it understandable facts.***Stable codes over polished copy**— An agent will key on`at_cap`

or`cap_would_exceed`

, not sentence wording. Having machine readable state made behavior more deterministic where prose set AI off improvising on how to proceed.*Error strings are UI; codes are API.***Refuse before acting —** A partial success creates what the reviewing agent called “explanation debt”. Sending 7 of 10 events forces an agent to narrate to its user. A clean refusal that says “send at most N” produced clean behavior instantly.*Check the whole request against available budget before processing any of it.***Transitions need a map***—*If tooling changed or data was migrated based on a conversion event (like FlurryPORT’s anonymous-to-account flip) provide that change mapping to the AI agent. Without a breadcrumb the AI agent spent 4 minutes exploring tools to rediscover what just changed. When mapping was provided it performed the same task in 21 seconds.*As a follow up to this one, tools that keep their names across a transition should keep compatible schemas too; AI clients cache schemas imperfectly.***One shot notifications get lost**— A`session_claimed`

notice was lost by a mid-batch poll and the agent never informed the user their upgrade was successful.*Anything that an agent must not miss must be a durable field on every response, not on a single event.***Don't rely on agents to run daemons**— An agent spent 1m 46s backgrounding a shell process to stand up a receiver for its user. Replacing it with tooling available within the implementation brought that time down to 3s (eg.`start_echo_server`

).**Tool descriptions are your routing layer**— “CALL THIS FIRST” in a description is a reason four cold runs never reached for ngrok or the Stripe CLI.*Discoverability is written into descriptions not the docs.***Ask the agent**— Asking an agent is the best reviewer of your build. The pre-build design review redesigned schemas, added pacing semantics and cut features. P*ost-hoc feedback finds bugs; pre-build review prevents bad shapes.*

### Receipts

Taking time to optimize your MCP implementation is time well spent.

Rejected sends during an 80-event bulk run — Before optimizing: ~50 (429s), After: 0

Full anonymous funnel, wall clock — Before optimizing: ~20 min, After: ~7 min

Post-signup re-orientation — Before optimizing: ~4 min, After: 21 seconds

Local echo receiver setup — Before optimizing: 1m 46s of shelling a daemon, After: 3 seconds, one tool call

Hand-rolled shell commands — Before optimizing: many, After: zero

*And the reviewing AI’s verdict, verbatim: "I did not reach for ngrok or Stripe CLI first. I captured, watched, replayed, and explained. That's exactly the product motion you want."*

### Try on your agent

Before you point an agent at it, the fine print. The no-signup session is plaintext and expires (about 90 minutes, 100 captures), and the tools tell the agent not to send production or PII data there; claiming the session moves everything into an encrypted project. Test-event signatures are realistic shapes, not real signatures, and the tool says so. And it's stdio only for now, so Claude Code, Codex CLI, and Cursor work; ChatGPT and claude.ai web don't yet. The token your agent gets is read-only and redacts PII by default, and signup links go to your browser, never the agent.

If you would be kind enough to comment on your experience I would love to hear it.

```
claude mcp add flurryport -- npx -y flurryport mcp
codex mcp add flurryport -- npx -y flurryport mcp
[mcp_servers.flurryport]
command = "npx"
args = ["-y", "flurryport", "mcp"]
```

Ask it to debug a webhook handler and watch which tools it picks. Docs at [flurryport.io/docs/cli](https://flurryport.io/docs/cli).
