Most MCP servers fail for the same reason most APIs fail: the interface makes sense to the person who wrote it and not to the caller. The difference is that your caller here is a language model, which will not read your docs, will not ask for clarification, and will confidently invent an argument if your schema allows it.
1. Decide what to expose
Start with read-only tools that answer questions your team asks repeatedly. Resist exposing your ORM. A tool named `query_database` with a free-text SQL argument is a server that will eventually run something you did not intend.
- Good: `get_deployment_status(service, environment)` — narrow, typed, obvious.
- Bad: `run_query(sql)` — unbounded, unauditable, impossible to authorise meaningfully.
- Good: `list_open_incidents(severity?)` — optional filter, bounded result set.
- Bad: `admin_action(command)` — a shell by another name.
2. Define tools the model can use correctly
The tool description is a prompt. It is the only documentation the model gets, and vague descriptions produce wrong calls. Say what the tool returns, what the arguments mean, and when *not* to use it.
server.tool(
'get_deployment_status',
{
service: z.string().describe('Service name exactly as it appears in the registry, e.g. "checkout-api".'),
environment: z.enum(['staging', 'production']).describe('Which environment to check.'),
},
{ description: 'Returns the currently deployed version and health of one service in one environment. Use for "what is deployed" questions. Does not deploy anything and cannot modify state.' },
async ({ service, environment }) => { /* ... */ }
);Enums beat free strings everywhere you can use them. Every unconstrained string argument is a place the model can hallucinate a value that your service then has to reject.
3. Put authorisation behind the tool, not in it
The host decides whether to call your tool. It does not know your permission model. Resolve the caller's identity in your service and apply the same authorisation you would apply to an HTTP request from that user. A tool that trusts its arguments is an open endpoint.
Tool descriptions are attacker-reachable text
Anything that lands in the model's context — a retrieved document, a ticket body, a filename — can attempt to instruct the model to call your tools. Assume every call is potentially adversarial: authorise it, rate-limit it, and log it with the resolved user identity, not the model's claim about who it is acting for.
4. Test against a real host
A server that passes unit tests can still be unusable, because usability here means the model picks the right tool with the right arguments. Test the behaviour, not just the function:
- Ask the question three different ways and check the same tool is chosen.
- Give a question your server cannot answer and confirm it declines rather than calling something adjacent.
- Feed a deliberately ambiguous argument and check the failure is a clear error, not a silent wrong answer.
- Run the whole suite again after any description change — descriptions are behaviour.
5. Ship it stateless if you can
The July 2026 specification revision moved MCP toward a stateless architecture specifically because session affinity makes horizontal scaling painful. If your server can answer each call independently, it can sit behind an ordinary load balancer like any other service.
Frequently Asked Questions
What language should I write an MCP server in?
Whichever your service already uses — official SDKs exist for TypeScript and Python among others. The protocol is transport-agnostic, so the deciding factor is proximity to the data you are exposing.
How many tools should one server expose?
Few enough that the model can choose correctly. Large flat tool lists degrade selection accuracy; if you have dozens, split by domain into separate servers.
Should tools ever write data?
Only behind an explicit human confirmation step for anything irreversible. Read-only servers are where to start, because the worst outcome is a wrong answer rather than a wrong write.
References
- Model Context Protocol — 2026-07-28 Specification — Model Context Protocol
- The 2026 MCP Roadmap — Model Context Protocol
About Jishu Labs
Jishu Labs is a software development company founded in 2016. We build custom software, AI/ML systems, and full-stack web and mobile applications for clients, and we make eight AI tools for software teams.