Your API's newest users are agents...
Your API's Newest Users Are Agents
Introduction
Who called your API last? For us, the honest answer is more and more often an agent. Claude Code debugging a flow. An assistant looking up an order. Someone's custom GPT. Our tools still assume a human pressing Send. This post is about that gap. How we ran into it ourselves, how most teams handle it today, and what we changed.
The Problem
We run ApyHub, a catalog of utility APIs. This year we wanted assistants like Claude, ChatGPT, and Le Chat to call those APIs directly. So we built an MCP server and published it to the official MCP registry. It worked. People used it. Then we noticed something uncomfortable: we now had two descriptions of the same API. One was the requests and tests we already trusted. The other was the MCP server, written by hand, sitting next to them.
Changing an endpoint means updating both. Forgetting one means finding out when an agent breaks. Testing the MCP side was its own chore-connect an inspector, click a tool, read the JSON, close the tab. Next release, repeat from memory. None of this is dramatic. It's just the kind of friction that adds up quietly until you notice you've built a second, worse copy of something you already had.
Three Situations We Kept Hitting
When we looked closer, it was really three different problems. They all involve MCP, which is why they're easy to blur together. The difference is who is calling whom.
1. Your Own Coding Agent, Debugging Blind
Checkout returns a 400. You ask Claude Code for help. Today, you paste the error-maybe a curl command. The agent reasons about what might be wrong. When it wants to test something, it writes its own curl, guesses the headers, and asks where the token lives. You end up being the agent's hands.
What we wanted was simpler. The requests already exist in the project. Let the agent run them. So that's what we built. We use Voiden for this, the open-source API tool we work on, where requests live as plain Markdown files in the repo. One button (or voiden agent in the terminal) and your coding agent can list, run, and inspect those requests. It calls the real endpoint and reads the real response. In our checkout example, it spots the missing header on its own. This is on for the whole project by default. Our reasoning: it's you, your editor, and your own agent. Small trust boundary.
Note: Some teams may have concerns about production credentials in the environment.
2. Someone Else's Agent, Calling One Endpoint
Your support assistant needs to issue refunds. Only refunds. Today, someone writes an MCP server. Picks an SDK. Redefines the inputs as a schema. Wires up auth. Figures out secrets. Hosts it. Now there's a new codebase describing an API you already had. It drifts. Nobody notices until an agent sends a field that was renamed two sprints ago. This was exactly our ApyHub story.
What we do now:
- Take the refund request that's already written and tested.
- Mark it as a tool.
- Choose which values the agent can set (in this case, just the order ID).
- Keep secrets in your environment-the server exposes nothing until marked.
Once other people's agents are involved, opt-in feels like the only honest default. One rule is more opinionated: a tool is only available while its tests pass. A test goes red, the tool goes offline. That's strict. With a flaky test, it will annoy you. We chose it anyway, because an agent losing access felt better than an agent calling something nobody has verified lately.
Is that the right trade? I genuinely go back and forth. Curious what others think.
3. Testing an MCP Server, Yours or a Vendor's
Two versions of this:
- Own MCP server: You run it and want to confirm it still works before a release.
- Vendor's server: You're about to build on someone else's server and want to see what it really returns.
Today we provide an inspector tab. Click, read, close. Gone. Or a one-off script that lives in someone's home folder.
What we wanted was the same thing we already had for REST: save the call, add assertions, run it in CI. So now an MCP call is just another block in the file. Point it at a server, pick a tool, check the response with the same auth and assertions you'd use anywhere else. Before a release, it confirms search_orders still returns the shape you expect. For a vendor's server, like Notion's, the file becomes a working reference the team can rerun.
The Core Insight
All three scenarios live in one file. The request you test with is the one your agent runs-it's also the one you publish as a tool-and it sits right next to your MCP tests. There is one description of the API, instead of three that slowly disagree. Because it's a plain file in the repo, changes go through a pull request. If someone widens what the refund tool accepts, a reviewer sees it in the diff. That's the real point for me: giving an agent access to an API is a permission decision, and permission decisions deserve a review trail.
Why we keep building this Voiden isn't our core business. ApyHub pays for it. We build it because we use it every day, and when something bugs us enough, it ends up in the next release. This one bugged us for months. It shipped in version 2.3, along with a few other things. The changelog has the full list if you're curious.
An open question, since I don't think anyone has this fully figured out yet: how are you deciding what agents can touch in your APIs today? Config file? Gateway? Hand-written MCP server? Gut feeling?
I'd like to hear what's working, and what isn't. If you want to poke at it: voiden.md · GitHub
Top Comment
The duplicate description problem seems unavoidable as long as the MCP layer is hand-written. Did you end up generating the tool schemas from the same source as your requests and tests, or is there still a manual step that can drift?
Comments
No comments yet. Start the discussion.