AI
What we learned running a public MCP server
We exposed our own site as a public, unauthenticated MCP server — six tools, stateless streamable HTTP, rate-limited where it costs money or sends email. The design decisions that mattered, and what we would do differently.
Earlier this year we turned this website into an MCP server. Any AI client — Claude, Cursor, ChatGPT, whatever lands next — can connect to https://setkernel.com/mcp, list our tools, and call them: read our studio profile, browse services, look up glossary terms, run a grounded search over our own pages, or send us a project brief on a user’s behalf. No account, no API key, no OAuth dance.
We did it because the direction of travel is obvious: a growing share of “visitors” are agents acting for someone, and a site that only speaks HTML is opting out of that channel. We also did it because the fastest way to learn where this breaks is to run one in public, on a real business, with real abuse incentives.
This is the retrospective. Not a tutorial — the field guide we wrote first covers the what. This one is about the decisions that only became clear after the thing was live.
Decision one: public, unauthenticated, on purpose
The first question everyone asks is “you just left it open?” Yes, and that sentence hides the actual work. “Open” has to mean a bounded set of read tools plus exactly two tools that do anything costly: a search backed by a metered language model, and a brief-sending tool backed by an email API.
Everything else — services, glossary, profile — is static content we already publish. Serving it through MCP changes the format, not the exposure. Once you accept that, the security question stops being “who can connect” and becomes “what can a connection do,” which is the question you should have been asking all along.
The two costly tools are rate-limited per IP at the edge. The limits fail open — if the limiter binding is missing, the request passes — because a marketing site that briefly overspends on inference is a better failure than one whose tools stop working on a configuration hiccup. That is a deliberate trade, and it is written down as one.
Decision two: stateless, and the two protocol eras
MCP grew a transport story faster than it grew a compatibility story. Modern clients speak Streamable HTTP with the current protocol revision; a long tail of 2025-era clients speak an older dialect and expect session semantics they will never actually use against a site like ours.
We serve both from one stateless handler: the current revision natively, and a stateless compatibility lane for the older clients. No sessions, no resumability, no Durable Object holding per-agent state that we would then have to secure, expire, and pay for. None of our tools need a session — each call is a pure function of its arguments.
If you are building a server for a search box or a catalog, ours is a useful template precisely because it is boring. The interesting engineering was deciding how little state we could get away with, not how much we could support.
Decision three: trust boundaries are different with agents
A browser form and an agent tool that does the same thing have completely different threat models, and copying the defenses across gets you the worst of both.
Browser defenses assume the client might be hostile but is definitely a browser. Same-origin checks work because browsers honestly send Origin. A CAPTCHA works because there is a human and a rendered page.
Agents break both assumptions. MCP clients send no Origin header at all — an origin gate would reject every legitimate caller. And there is no page to render a challenge on.
So the boundary moves to what the tool itself can do. Our brief tool accepts at most a few thousand characters, escapes every field before it touches an email body, rate-limits per IP, tags the resulting lead as agent-origin and unverified — because a server cannot confirm that the human whose name appears in the arguments consented to anything. The studio treats those leads accordingly. Meanwhile the browser-only paths that email people keep their origin gate and their challenge, and the two policies never have to pretend they are one policy.
The general rule we landed on: authenticate nothing you can safely serve publicly, bound everything you cannot, and label provenance so a human downstream can judge what an agent sent.
Decision four: discovery is a stack, not a file
The part that surprised us most: connecting to a server is a solved problem, and discovering one is not.
There is no single place an agent looks. So we publish the whole stack — a robots.txt that names our AI-crawler policy, an llms.txt for models that read those, an RFC 9727 API catalog at a well-known URL, a server card describing the MCP endpoint, and markdown negotiation so any page can be fetched as text/markdown by content type instead of scraped. Each layer is a few lines. Together they mean that whatever convention a client follows, it finds the same story: this site is machine-readable, here is the machine-readable description.
The honest observation: we cannot measure most of this. Agent clients do not send referrers you can group into a channel, and most analytics would not count them anyway. We instrumented the tool calls we care about server-side and accepted that the rest is a bet on the direction of traffic, not a tracked campaign.
What we would do differently
Three things, honestly.
First, we would design the tool list for an agent’s attention budget, not a human’s menu. Six tools is close to the ceiling of what a client reasons over well; the ones that earn their place are the narrow ones (“get this service by slug”), not the broad ones.
Second, we underestimated the email path. The moment a tool sends mail to a user-supplied address, you have built a relay for whoever finds it, and the defenses have to be proportionate to that from day one — not added in a later pass, as ours were.
Third, we would publish the server card earlier. It took one afternoon and it is the piece that makes the server discoverable by tools we have never heard of. Everything else we built assumed the client already knew we existed.
The takeaway
A public MCP server is cheap to build, uncomfortable to harden, and easy to over-engineer. The version that survives contact with the real internet is the one where every tool is bounded, every stateful feature is argued down to zero, and the trust decisions are written next to the code that makes them.
If you want to see the result, point an MCP client at this site — or read how we build these for clients, or run our free agent-readiness scan on your own site and see what the machines see.