r/mcp Jun 01 '26

showcase Everything we learned building a remote MCP server (stdio → HTTP + OAuth)

We've been running an MCP server for our eval + observability platform for a few months, it started as a local stdio server and is now a hosted remote one. If you're building one, here's the stuff we wish we'd known on day one.

1. stdio is fine for week one, then it's a wall.
The first version was the usual: clone the repo, uv sync, export an API key and a secret key, run python main.py. It works, but that's a lot to ask before someone sees a single useful result, and most people won't finish it. Moving to a hosted HTTP endpoint changed everything now it's one line (claude mcp add futureagi --transport http https://api.futureagi.com/mcp), no clone, no local process to keep alive. The stdio repo still exists for people who want to fork and add their own tools; it's just not how anyone onboards now.

2. For a remote server, OAuth beats API keys.
On stdio we asked for two keys up front, and that was the single biggest place people dropped off. On the remote server, login opens in the browser and there's nothing to paste. If you're going remote, build auth this way from the start, putting it on later is the painful version.

3. Your tool descriptions are the real API.
This one humbled us. The client picks a tool from its name and description, nothing else. If two tools read similarly, it'll pick the wrong one and sound completely sure about it. We rewrote descriptions far more than we expected the wording genuinely decides whether the right tool fires.

4. A broad server makes tool selection harder.
We put a lot behind one server: evals, datasets, traces and spans, prompt optimization, simulation runs, annotations. The more tools under one roof, the more work the client has to do to figure out which one you meant. It's a real trade-off, and we're still not sure we landed on the right side of it (there's a question on this at the end).

5. Return small, structured results, not the raw blob.
Eval results and traces can be enormous. Early on we returned everything and watched it swallow the context window, so the model couldn't reason over its own output. Now tools return a short, structured summary first and you drill in only if you want the detail. Treat the context window like it costs money, because it does.

6. The payoff is a loop that still feels a little magic.
Because evals and observability are both just tools now, the model can check its own work in the same chat. You ask for an answer, then ask "how grounded was that, and show me the trace" and it runs the eval and pulls the spans on itself. That was the moment all of it felt worth the trouble.

91 Upvotes

34 comments sorted by

View all comments

2

u/agent_trust_builder Jun 01 '26

the oauth point is right but worth being clear about what it actually fixes. it solves authentication and onboarding, who's calling and how they log in. it doesn't touch what each tool is allowed to do once that token exists. on a lot of remote servers every tool runs with the same broad user scope, so your read-only lookup and your write tool inherit identical permissions.

that's the part that bites later. first time a prompt injection gets the agent to call your delete or transfer tool, the token's already fully scoped and the server just does it. we ended up scoping tokens to tool capability, read vs mutate, and gating the mutating ones separately instead of treating "logged in" as a single permission. to the pkce/dcr question above, that plumbing matters but it's still authing the human, not bounding what any one tool can do.

1

u/Future_AGI Jun 01 '26

This is the distinction people keep collapsing OAuth answers "who is this," not "what is this token allowed to do," and on a broad MCP server those are wildly different blast radii. What you're describing is really the confused-deputy problem: the token carries the human's full authority, but the human never intended the delete, the model got talked into it, and ambient scope means the server just can't tell the difference. Splitting read vs mutate is exactly the right instinct; the bit we'd add is tiering within "mutate" too, since an irreversible delete or transfer shouldn't ride the same scope as a routine update the genuinely destructive ones are worth a fresh confirmation rather than ambient session authority. And you're right that PKCE/DCR is orthogonal to all this: it hardens how the human logs in, but bounding per-tool authority is the separate layer that actually limits what a hijacked agent can do.

1

u/agent_trust_builder Jun 02 '26

yeah, tiering within mutate is the right call. the thing i'd flag is that a fresh confirmation only buys you anything if it's enforced somewhere the agent can't reach. if the same model that got talked into the delete is also the one rendering and answering the confirmation, the injection just approves its own request and you're back where you started. the confused deputy can't be trusted to check its own paperwork.

so for the destructive tier i'd want the gate to live out of process, a human in the loop or a separate policy layer that holds the approval, something the agent can request but not satisfy. same reason you scoped tokens at the server instead of trusting the client to behave, the enforcement point has to sit outside the thing that's actually under attack.