How to Build an MCP Server for Your SaaS
Oct 21, 2026 · 7 min read · by Jordan Kwan
TL;DR: An MCP server is a projection of your product, not a second product. Build it on Streamable HTTP in an official SDK, design every tool name, description, and schema for a model reader, set the annotations honestly, make writes safe to retry, do OAuth the way the spec profiles it, and test with real agents. The 10 steps below are the ones that matter, each with its failure mode.
Who is this for?
You ship a SaaS product and you have decided, or been told, that agents are now a user segment. You already have an API. This playbook assumes that API works; your problem is the layer in front of it. If you are still deciding whether the surface is worth building, start with the case for it. The companion analysis of what MCP genuinely changes versus what it rebrands is MCP vs API.
What are the steps?
1. Project your product, do not mirror your API. Start from the jobs your users hire the product for; make each job one tool. Your REST endpoints were shaped by screens and CRUD. An agent needs outcomes. Failure mode: one tool per endpoint, so the model faces 200 near-identical tools and picks wrong, confidently. Reachium's server exposes 94 tools for a full LinkedIn automation product, not one per route. The tool list is a product decision, not a schema dump.
2. Pick the transport by who connects, and know which era you are in. For a remote SaaS the answer is Streamable HTTP: one endpoint, requests as HTTP POSTs, responses optionally streamed. stdio is for local tools a client launches as a subprocess, not a hosted product. The older HTTP with SSE transport was deprecated in the 2025-03-26 revision, so do not build on it. Versioning is the subtle part: most clients in the wild still open with the initialize handshake from the 2025-11-25 lifecycle, while the current 2026-07-28 revision is stateless and declares its version on every request. The official SDKs absorb most of that split. Failure mode: testing only against the newest client and discovering in production that half your users run the handshake era.
3. Build on an official SDK, in a Tier 1 language if you can. The project lists 10 official SDKs. TypeScript, Python, C#, and Go are Tier 1, the fullest protocol support and strongest maintenance commitment. The SDK owns the handshake, capability negotiation, pagination, and the error envelope, the parts everyone gets subtly wrong by hand. Failure mode: a hand-rolled JSON-RPC layer that passes your tests and fails inside a real host over a notification or cancellation path you never implemented.
4. Write names and descriptions for the model, because the model is the reader. A tool has a name, a description, and a JSON Schema for its inputs, and the description is the only part the model sees when deciding whether to call it at all. Name tools verb-first and unambiguously (search_leads, not query). Write descriptions as routing instructions: what it does, when to reach for it, what it returns. Failure mode: descriptions copied from developer docs written for a human who can see the UI. The model cannot see your UI. The description is the UI.
5. Treat the input schema as a contract, and validate like it is hostile. Use tight types, enums where the set of values is closed, defaults where a sane default exists, and keep the required list short. The schema is the contract, but the model will still send plausible values of the wrong shape, so validate on the server and answer with a correction the model can act on. Failure mode: one free-form string parameter that makes the model compose a mini language inside it. Every such parameter is a bug farm.
6. Set the annotations, and set them honestly. Every tool can carry annotations: four boolean hints covering whether the tool is read-only, destructive, idempotent, and open-world. The defaults assume the worst, so an unannotated tool is treated as a destructive, non-idempotent, open-world write. Clients use them to gate confirmations and retries. Failure mode: marking everything read-only to skip confirmation dialogs. The specification is explicit that annotations are hints which clients must treat as untrusted, and lying in them buys you a reputation, not a shortcut. Enforcement belongs in your authorization layer, never in the hints.
7. Make writes safe to retry and errors safe to read. Agents retry, and networks time out after the write landed. Give create-style tools idempotency keys, or natural unique keys, so a retry returns the original result instead of a duplicate. Then split your errors the way the protocol does: protocol failures belong in JSON-RPC errors, but a tool that ran and failed should return a normal result with isError: true and content written for the model: which input was rejected, what a valid one looks like. Add an output schema and return structuredContent so the model parses data instead of scraping prose. Failure mode: surfacing a stack trace or a bare protocol error for "no such lead", which reads to the model as a dead end instead of an instruction.
8. Do authorization as a resource server, because that is what the spec says you are. For remote servers the authorization specification profiles OAuth 2.1: your server publishes Protected Resource Metadata (RFC 9728) so clients can discover your authorization server, tokens must be issued for your audience using resource indicators (RFC 8707), and you must only accept tokens meant for you. Accepting or passing through a token issued for another service is explicitly off limits; that is how confused-deputy attacks happen. Newer clients register with Client ID Metadata Documents; Dynamic Client Registration (RFC 7591) is deprecated in the current revision. Before launch, read what is inside the MCP server you installed from the client side; it is the threat model your users' security teams will apply to you. Failure mode: checking that a token is valid and skipping the audience check, which turns every other app's token into a key to your product.
9. Test with real agents, not just unit tests. Start with the official MCP Inspector to exercise every tool and inspect the raw traffic. Then run a fixed battery of real tasks in the hosts your customers use, and score tool choice, schema accuracy, and error recovery. Test at your real tool count. Failure mode: validating with 3 tools in the Inspector and shipping 94, then learning that tool selection, not tool correctness, was the hard part. Tool lists are paginated, so a broken cursor silently hides tools from every client.
10. Ship and version the surface like a public API. The protocol gives individual tools no version field, and clients cache tool lists, so renaming a tool or an argument breaks things silently. Add new tools alongside old ones, keep deprecated names working, and treat an SDK upgrade that jumps a spec revision as a protocol migration, because it is one. 5 revisions shipped in 20 months, and the latest removed the connection handshake. Failure mode: a tidy rename in a cleanup release that silently breaks every saved workflow your best customers built.
What goes wrong most often?
Three patterns repeat. First, tool sprawl: the surface mirrors the internal API, descriptions overlap, and the model routes badly. Second, annotations left at their defaults, so every call triggers a confirmation, users learn to approve reflexively, and the safety mechanism trains the exact behavior it exists to prevent. Third, errors written for logs instead of for the caller: the model cannot see your logs, so an unactionable error is an abandoned task.
How do you know it is working?
Two numbers, both from your own logs. The first is task completion: run the same battery of representative tasks through two or three real hosts weekly and track the share completed without human help. That percentage is your product metric. The second is the tools/call error rate split by cause, model error versus product error. Model errors mean your descriptions and schemas need work. Product errors mean your API does. When we consolidated Rio, our AI reply agent, and the MCP onto one shared tool surface on October 19, 2026, every fix to a description, a schema, or an error message improved both our agent and every external agent at once. That compounding is the real return on building the surface properly.
Written by Jordan Kwan, founder of Reachium.
I build Reachium, the LinkedIn outreach platform behind the tactics you just read. Same brain, live product.
See what Reachium does ↗Prefer the long version first? What Reachium is and how it works