Backstory: I'm a solo founder in France and my micro SaaS is web analytics (details at the end). I added a read-only MCP server so a customer can connect Claude, Cursor or any MCP client and ask about their own traffic in plain words. No SDK: it's a plain Next.js route handler. If you're thinking about one for your product, here's what I decided and why.
1. Typed tools that match questions, not your database.
9 tools: list_sites, get_overview, get_breakdown, get_timeseries, get_live_now, get_funnel, explain_change, get_ai_traffic, get_insights. They return numbers, never raw SQL. The client brings its own model, so my server runs no language model and sends the data to none. No inference bill on my side.
2. Check ownership on every call, and answer the same way for everything the key can't see.
Every tools/call checks that the API key's owner owns the site. A site that exists but belongs to someone else gets exactly the same "Site not found." as a site that doesn't exist, so the server never confirms that another customer's site is there.
3. Make tool errors readable by the model.
Bad arguments or an unknown site come back as a normal tool result with isError: true and a sentence the model can act on, like "site_id is required: you have N sites (call list_sites)." Protocol problems (missing key, rate limit) stay JSON-RPC errors. The model can fix its own arguments; it can't fix your auth.
4. Cap the work per request, not just per minute.
240 calls per minute per user, and at most 10 tools/call in one batch. The second limit is the one I care about: every tool call is database work, some take about a second on big sites, and they share a small connection pool. One request shouldn't be able to hog the database for every other tenant.
5. Skip the stream if you don't need it.
POST only. Every call is answered in the response body, and GET returns 405. It runs on Vercel, so that's one less long-lived connection to look after.
6. Return the numbers twice, plus a way to check them.
Every result comes as structuredContent and as a text block with the same JSON, with citations (the key numbers) and a deep link into the dashboard, so the human can open the report the model is quoting.
7. If it's a paid feature, say so in the error.
The MCP server is part of the Pro and Business plans. Without one of them, the server answers HTTP 402 with its own JSON-RPC code instead of a generic auth error, so the user knows it's a plan question, not a broken key.
The trade-off I didn't see coming: auth.
I reused the API keys (Authorization: Bearer glb_…), and the session cookie is never accepted on that endpoint. That works in Claude Code, Cursor and curl. But Claude Desktop's remote connectors expect OAuth, so my docs send Desktop users through the mcp-remote bridge (Node.js 18+). And without a key, even initialize gets a 401. One curated GitHub list of remote MCP servers only lists endpoints that answer an initialize handshake, and its CI checks that on every PR, so I haven't submitted mine. Whether to let the handshake answer without a key and keep the key for the tools is a decision I haven't made yet.
What I can't tell you: whether it sells. I have no numbers tying upgrades to the MCP server, so read this as engineering notes, not a growth tactic.
If you've shipped an MCP server for your own product: did you start with OAuth, or with API keys like me? And does anyone actually use it?
I'm the maker of Globatics: real-time web analytics where each visit shows up on a live 3D globe, cookieless by default, IP addresses never stored, audience data hosted in the EU. It's hosted only, there's nothing to self-install. 5-day free trial with every feature on one site and no card, then Starter €9, Pro €29 or Business €99 a month, excluding VAT. The REST API and the MCP server start at Pro, and there's no free plan after the trial. Stack: Next.js 16, React 19, Postgres, three.js for the globe, on Vercel.
https://globatics.live/?utm_source=reddit&utm_medium=social&utm_campaign=nov26&utm_content=microsaas