Your API Is Already an MCP Server

I’ve written before about MCP servers for idea.log and Lexicon, both of which are local binaries that agents talk to over stdio. Emit needed an MCP server too, and I went a different way: there is no server. There’s an endpoint.
Emit’s MCP support lives at https://api.rssemit.com/mcp, inside the same FastAPI application that serves the public API. Nothing to install, nothing to version, nothing to run. I think this is the right default for any web product adding MCP, and it’s barely any code.
The sidecar trap
The instinct when adding MCP to a product is to build A Server: a new package, published somewhere, that users install and configure, which then calls your API over HTTP. That’s how most product MCP servers ship today, and it recreates every problem SDKs have:
- It has its own version, which drifts from the API’s version.
- It has its own auth handling, which means a second place credentials live and a second place auth bugs live.
- It reimplements input validation, or worse, skips it and trusts the API to catch things.
- Users have to install and update it, which for a curl-first audience is a step backward from the API they already have.
The sidecar makes sense when the data is local, like idea.log’s on-device database. When your product is already a hosted API, the sidecar is a middleman with opinions.
MCP as a thin adapter
The modern MCP spec supports streamable HTTP as a transport, which means an MCP server can just be a route. Emit’s implementation is an adapter that accepts JSON-RPC at /mcp and forwards each tool call into the same application, in-process, as if it had arrived at the corresponding REST endpoint.
The payoff is that the word “same” does all the work:
- Same auth. An MCP session presents a regular emit API key. There’s no second credential system to build or breach.
- Same validation. Tool inputs hit the same Pydantic models as API requests. A malformed feed URL is rejected by the same code either way.
- Same schema. The tool definitions are generated from those request models rather than written alongside them, so a new field appears in the tool’s input schema the moment it exists on the endpoint. This is the one I’d insist on hardest, because a hand-written tool schema is a second copy of your API contract, and second copies drift.
- Same rate limits. An agent hammering tools is throttled by the same middleware as a script hammering curl.
- Same deploy. When the API ships a feature, the MCP surface has it that instant. There is no version skew because there is nothing separate to version.
Every one of those bullets is a class of bug that can’t exist because the component that would host it doesn’t exist.
What agents do with it
Concretely, an agent connected to emit’s MCP endpoint can manage feeds, subscribers, broadcasts, and account state. The uses that stuck for me: asking Claude Code to check delivery stats after a post goes out, importing a batch of subscribers with the agent handling the CSV cleanup first, and pausing a feed before republishing an old post so nobody gets emailed a typo fix.
None of that needed new capability. It needed the existing API to be reachable from where the work was already happening, which is an agent session. That’s the honest pitch for MCP on a product like this: it’s not a new feature surface, it’s a new client.
The general recipe
If you run a small API-first product, you’re closer to MCP support than you think:
- Mount a streamable-HTTP MCP adapter at a route in your existing app.
- Map tools onto your existing endpoints, reusing their auth and validation wholesale.
- Publish the URL. Done. Your changelog is your MCP changelog forever after.
The framing I’ve landed on: agents are just another API consumer, and they deserve the same treatment as the rest, no more and no less. Give them the real API through a thin adapter, and decline to build them a special server that will be six weeks stale by October.
Stay in the loop
Get notified when I publish new posts. No spam, unsubscribe anytime.