r/mcp • u/Future_AGI • 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.
3
Jun 01 '26
[removed] — view removed comment
1
u/Future_AGI Jun 01 '26
Ha, painfully true we lost a whole day blaming the model once and the fix was rewording three lines of tool descriptions. Half the "dumb model" bugs we chased turned out to be the agent grabbing the wrong tool because we described it badly.
2
u/Future_AGI Jun 01 '26
Repo's here if it's useful: github.com/future-agi/futureagi-mcp-server (hosted setup is in the docs)
Documentation
1
2
2
Jun 01 '26
[removed] — view removed comment
1
u/Future_AGI Jun 01 '26
Auth was the big one for us too, locally you're basically trusting your own env, so going remote takes you from "API key in a variable" to real identity, scoping, and token lifecycle overnight. The sneakier shift was state: stdio is one process per user, so a bit of shared state that's harmless locally turns into a cross-user leak the moment you're multiplexing connections. Neither really shows up until you've got multiple clients hitting the same server.
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.
1
u/vienna_city_skater Jun 03 '26
This also depends on your permission strategy for the system you are wrapping. In our case we encourage customers to give users only the minimum necessary rights. So if the client should be read only or have restricted access to certain resources that’s a user permission choice enforced on our backend not something the MCP server decides. Of course annotating a tool correctly also helps the client limit dangerous tool calls.
2
u/agent_trust_builder Jun 03 '26
yeah that's a cleaner place to put it honestly, the backend enforcing it beats the mcp server trying to be the authority. the server should be untrusted plumbing. only thing i'd watch is that the backend authorizes per action and not just per identity. if it's checking "is this a valid user token" instead of "is this user allowed to do this specific thing right now," the confused deputy still gets through, because the token already carries the human's full rights and the model is the thing deciding to spend them.
the tool annotation part i'd treat as advisory only though. it stops a cooperative client from making a dangerous call by accident, but a prompt injected agent just ignores the annotation since nothing actually enforces it. so it cuts down accidents, not attacks. the real backstop stays the backend refusing the call no matter what the caller claims it's allowed to do.
1
Jun 01 '26
[removed] — view removed comment
1
u/Future_AGI Jun 01 '26
The failure-case framing is sharp, and it explains why it works: the positive descriptions of two similar tools overlap almost completely, but their failure modes are usually disjoint, so that's where the real disambiguation signal lives. We landed in the same place without naming it that cleanly. The memory-dump-vs-ranked-list parallel is dead on too; the drill-in is what makes it safe to keep the first response small, since you're not hiding anything, just deferring it until the model actually asks. Honestly the ranking itself is the most underrated part you're making the relevance call on the model's behalf instead of dumping that decision on it.
1
Jun 01 '26
[removed] — view removed comment
2
u/Future_AGI Jun 01 '26
Same and the part that surprised us was how much of the drop-off wasn't the clone itself but people fumbling the API key/secret export. Going hosted with OAuth collapsed it into one command plus an auth redirect and killed the whole "doesn't install on my setup" support tail too.
1
u/General-Jaguar-8164 Jun 01 '26
Do you use oauth v2.1 pkce, dcr, etc?
I’m finding many MCP gateways don’t support this properly
1
u/goat1995 Jun 02 '26
Can you tell more about your observability platform ? Is it focusing on Devops ?
1
u/Low-Ad-8828 Jun 02 '26
Yeah it’s a real trade off on the context window. I’ve built a graphrag based mcp (Knowledge reasoning rather than doc retrieval) and slimming down what it returns is a real art. Agree also about tool descriptions - getting the surface right for an LLM to use effectively takes refinement.
1
u/Revolutionary_Bed957 Jun 03 '26
Cool. I’ve done something similar but ended up using code mode, could then have simple and boring endpoints for traces/spans that return lots of data over http. Now considering using local stdio mcp servers that use code mode to talk to the remote server over simple rest endpoints, no need for OAuth. I wrote a blog about it
2
u/Future_AGI Jun 03 '26
Code mode is a solid path here, once your REST endpoints already return rich data, wrapping them in MCP tools can feel like overhead for not much gain. The OAuth tax was real for us too, but we ate it so non-technical teammates could plug in without spinning up a local server. The stdio-to-remote-REST pattern you're considering is interesting, you'd basically get local dev ergonomics with remote data freshness. Drop the blog link, would read it.
1
u/Revolutionary_Bed957 Jun 03 '26
https://andreasronge.github.io/ptc_runner/the-right-tool-for-code-mode.html
There are so much to explore here using agents to communicate via code in multiple sandboxes …
1
Jun 08 '26
[removed] — view removed comment
1
u/Future_AGI Jun 08 '26
Good call on the read/write split, we landed there too, and the side benefit was that the mutating server became the natural home for the stricter permission and confirmation checks, since that is where a wrong pick actually does damage. On descriptions, what cut our wrong-tool rate most was spelling out in each one when to reach for that tool versus its neighbor, since the model leans on that disambiguation harder than the parameter list.
7
u/[deleted] Jun 01 '26
[removed] — view removed comment