Ship an MCP
server.
A working server is an afternoon. A server you can put in front of production data is a week, and the difference is entirely in the decisions you make before writing code. Seven steps, in the order that stops you rewriting the auth layer twice.
Decide whose permissions the server acts with.
Most MCP servers get written in the wrong order: tools first, auth bolted on when someone asks about compliance. That sequence forces a rewrite, because the identity model determines your transport, your deployment shape, and which tools can exist at all. Answer this before anything else, and write the answer down.
A specific human with their own permissions, or a background job with no human behind it. If it is a human, their identity has to reach the underlying system, not stop at your server.
If the source system enforces row-level or record-level permissions, your server must pass through the user's authorisation rather than replace it with its own. If it does not enforce anything, you are building the permission layer, so say that out loud.
A shared service-account key is the fast answer and the one that fails an audit. Per-user tokens held by the server, obtained through a proper authorisation flow, is the answer that survives.
Someone will eventually ask who read a record on a given date. If the answer your logs give is the name of a service account, you have no answer.
The shortcut that everyone takes and everyone regrets is a single broad credential shared by all users. It works immediately, it is invisible in a demo, and it turns your server into a confused deputy the moment a low-privilege user asks a high-privilege question. If you are building a read-only server over genuinely public data, take the shortcut knowingly. Everywhere else, do not. Why identity decides the whole design.
Choose tools, not endpoints.
The instinct is to enumerate your REST API and emit one tool per route. Resist it. A model does not want thirty CRUD primitives, it wants the four things people actually ask for. Tools are a product surface aimed at a probabilistic caller, and the design goal is a small set of task-shaped operations that are hard to confuse with one another.
getCustomer, getOrders, getInvoices, getTicketscustomer_snapshotOne call returns what a support agent actually needs, instead of four the model has to sequence correctly.
searchRecords(entity, filters, sort, page)find_orders_for_customerA generic search with a wide parameter space is chosen wrongly and filled in wrongly. Narrow arguments are validated arguments.
executeQuery(sql)revenue_by_monthAn unbounded tool cannot be reviewed, cannot be permissioned, and makes every incident unbounded too.
updateRecord(table, id, payload)issue_refundA named business action can carry a ceiling, a precondition, and an approval gate. A generic write cannot.
A good rule of thumb: if you cannot write a one-sentence description of a tool that makes it obvious when not to use it, the tool is too broad. Aim for somewhere between four and ten tools per server. If you find yourself past twenty, you are probably fronting more than one system and should split the server.
The description is the interface.
A tool definition is a name, a description, and a typed input schema. The model sees only those three things and decides from them alone. Developers routinely spend a day on the implementation and forty seconds on the description, which is precisely backwards: the implementation determines whether the call works, the description determines whether the call happens at all, and with the right arguments.
Say when not to use it
The negative case is what separates two similar tools. Without it the model picks whichever description it saw first, and does so inconsistently.
Name the units and the identifiers
An amount without a currency and an id without a source system are the two most common causes of confidently wrong tool arguments.
Constrain in the schema, not the prose
Enums, required fields and ranges are enforced; a sentence asking nicely for valid input is not. Use strict schema validation so arguments are guaranteed to typecheck before your handler runs.
Describe the return shape
The model plans its next step from what it expects back. Telling it the shape reduces speculative extra calls.
Keep it short and stable
Every word ships on every request. And because descriptions are load-bearing, a reword is a behaviour change: version it like code.
Stdio to build, HTTP to deploy.
- ·Server runs on the same machine as the host
- ·No network, no ports, no auth to configure
- ·Inherits the trust of the logged-in user
- ·Right for developer tooling and for building the thing
- ·Wrong for anything more than one person uses
- ·Server runs as a normal networked service you deploy
- ·Needs real authentication and authorisation
- ·Shared across a team, versioned, monitored, rate limited
- ·Right for every production deployment
- ·Forces the questions stdio let you postpone
The trap is prototyping over stdio, where the server implicitly acts as you, and then deploying the same code over HTTP without revisiting who it acts as now. Ported unchanged, a server that was safe locally becomes a service that performs privileged actions for anyone who can reach the port. When you cross this line, re-read step one.
Connect it from the model side.
With the server running, the host has to be told both that the server exists and that its tools should be exposed to the model. These are separate declarations and a request carrying only one of them is rejected as invalid. Below is the shape using the Claude API from TypeScript.
Once more than a couple of servers are connected, tool definitions start consuming a meaningful share of every request and tool selection gets measurably worse. The mitigation is a tool-search tool plus deferred loading, so definitions are retrieved on demand rather than shipped up front. Two constraints when you enable it: the search tool itself must never be deferred, and at least one tool must remain loaded, or the request is rejected. Keep the tool list byte-stable and deterministically ordered as well, since tools render first and are the most cacheable part of a request.
Open the write path last, and narrowly.
Reads fail as wrong answers. Writes fail as refunds issued, records overwritten and emails sent to customers. Treat them as different products with different review standards, and sequence them: a read-only server in production teaches you how the model actually uses your tools, which is the information you need to design the write ones.
issue_refund with an order id and an amount ceiling, not update_record with a table name and a payload. The narrower the tool, the more accurately it is selected and the more reviewable each call becomes.
Either a human confirms before execution, or the server enforces a precondition it can check independently of what the model claimed. Preferably both above a threshold.
Agents retry. Give every write an idempotency key derived from the request so a duplicate call is a no-op rather than a second refund.
Record the before state, the after state, the arguments, the identity, and the approver. A write you cannot explain afterwards is a write you should not have exposed.
One more rule worth holding: never let a tool description be the only thing standing between a model and a destructive action. Descriptions are instructions to a probabilistic system, not access control. The server enforces; the description merely guides.
Test the selection, not just the handler.
MCP has no compiler between the server and its caller, so the usual test suite misses the failure that actually happens: the model picks the wrong tool, or the right tool with wrong arguments, after someone rewords a description. The fix is a fixed set of realistic prompts with a known-correct tool call for each, run on every change to the server. This is a small eval, and it is the highest-value test you will write.
Keep at least a few cases that should produce no tool call at all, because a model that reaches for a tool when it should have answered directly is a real and common failure. Then track the selection distribution in production: a tool that quietly falls from twelve percent of calls to two percent has usually been broken by an edit rather than abandoned by users. The eval suite guide covers the harness.
The checklist.
Four ways this goes wrong.
The service account that ate the permissions model. One credential, every user, shipped because it was the fastest path to a working demo. It stayed because nothing visibly broke. The bill arrives at the first audit, or the first time someone asks the assistant a question they should not have been able to ask, and the remediation is a rewrite of the server rather than a patch.
The API mirror. Thirty tools generated from thirty endpoints, each named after an internal route. Selection accuracy collapses, context fills with definitions nobody uses, and the fix is not more prompt engineering but a redesign down to six task-shaped tools.
The helpful description edit. Somebody tightens the wording of a tool description on a Friday. No test fails because no test covers selection. Three weeks later support notices the assistant has stopped using that tool, and nobody connects the two events.
The unaudited community server on the write path. A third-party server, holding a production credential, exposing tools whose descriptions the model obeys. It may be perfectly good code. You did not read it, you did not pin it, and it can act on your data. Start third-party servers on the read surface and pin the version.
Identity first, reads before writes, selection in the eval suite.
We have been building on the protocol since it was published, connecting agents to ERP, CRM and internal tools. Every server we ship starts with the identity decision in step one, exposes a task-shaped read surface before any write exists, gates writes narrowly, and carries a tool-selection eval that runs on description changes as well as code changes. The result is a server you can put in front of production data without a rewrite.
→ See the MCP implementation serviceQuestions, answered.
01How do I build an MCP server?+
Work in dependency order rather than starting with code. First decide whose permissions the server acts with, because that determines transport, deployment and which tools are possible. Then design a small set of task-shaped tools rather than mirroring your API endpoints. Write descriptions that state when not to use each tool and enforce constraints in a strict input schema. Build over stdio locally, deploy over streamable HTTP with real per-user authorisation, connect it to the model with both the server declaration and the toolset declaration, add write tools only after the read surface has run in production, and cover tool selection with an eval.
02How many tools should an MCP server expose?+
Between roughly four and ten. Past about twenty tools on a single server you are usually fronting more than one system and should split it, and past twenty to thirty tools across all connected servers the model's selection accuracy degrades noticeably and you should enable deferred loading with a tool-search tool. Tool count matters more than server count because every definition is sent on every request.
03Why is the model not calling my MCP tool?+
Nine times out of ten the description is the problem, not the code. The model only sees the name, description and input schema, so if the description does not clearly state when to use the tool, or overlaps with another tool's description, selection becomes inconsistent. Check for a competing tool with a similar description, add an explicit statement of when not to use it, name the units and identifiers in the schema, and confirm the toolset is actually declared rather than only the server.
04What is the difference between mcp_servers and mcp_toolset?+
They are the two halves of connecting a remote server, and both are required. The `mcp_servers` entry tells the API where the server is, with a type, a url and a name. The `mcp_toolset` entry in `tools` exposes that server's tools to the model, referencing the same name. A request with only one of the two is rejected as a validation error, which is where most first integrations stall for an afternoon.
05Should MCP write tools require human approval?+
Any write whose failure is expensive or irreversible should have either a human approval gate or a hard precondition the server can verify independently of what the model asserted, and ideally both above a value threshold. Beyond the gate, make writes narrow enough to name as a single business action, idempotent through a request-derived key so retries are safe, and reversible with the before state logged. A tool description is guidance to a probabilistic caller and must never be the only control on a destructive action.
06How do I test an MCP server?+
Test the handler with ordinary unit tests, then add the test that actually matters: a tool-selection eval. Keep a fixed set of realistic prompts with a known-correct tool call for each, including several that should produce no tool call at all, and run it on every server change including description edits. In production, track the distribution of which tools get chosen over time, because a sharp drop for one tool usually means an edit broke its selection rather than users losing interest.
07Can I use a third-party MCP server in production?+
Yes, and often you should when the vendor of the underlying system publishes and maintains it, since they will track their own API changes better than you will. Read its auth model before trusting it and confirm it supports per-user authorisation rather than a single shared key. Be considerably more careful with unaudited community servers on the write path: a server you did not read, holding a production credential, exposing tools whose descriptions the model obeys, is a supply-chain decision. Read the source, pin the version, and start it on the read surface.
08How long does it take to build a production MCP server?+
A working server against a single system is an afternoon. A server you can put in front of production data is closer to a week, and almost none of that extra time is protocol work. It goes into the identity and authorisation model, designing task-shaped tools instead of mirroring endpoints, the approval and idempotency machinery on the write path, tracing, and the selection eval. Teams that skip straight to the afternoon version typically pay the week back later with a rewrite.
One server, built in the right order.
Bring the system you want an agent to reach and the rules about who may see what. In one conversation we can usually tell you whether it needs a custom server, and what the identity model has to look like.
Start a conversation →