Designing MCP servers and tools that agents can use safely
MCP gives agents a standard way to call tools, but a badly designed tool is still a badly designed tool. These are the rules we use for tool contracts, permissions, error handling and logging when we build Model Context Protocol servers for company systems.
What an MCP server is, in one paragraph
The Model Context Protocol (MCP) is an open standard that lets an AI application discover and call tools, read resources and use prompts exposed by a server, over a common wire format. An MCP server wraps something you already have, an ERP, a helpdesk, a document store, an internal API, and presents it to agents as a list of named tools with JSON schemas. The agent side (Claude, an orchestrator you built, an IDE) is the client. The protocol solves the plumbing; it does not decide what the tools should be. That is the design work.
Rule 1: model the business operation, not the API
The most common mistake is to auto-generate a tool per REST endpoint. Agents then get GET /invoices, PATCH /invoices/{id} and forty siblings, and have to reconstruct business meaning from HTTP verbs. Instead, expose the operations a competent employee would name:
# reads: no side effect
erp.match_po(invoice_ref, vendor, total) → {status, po_number, variance}
helpdesk.fetch_new(since) → [ticket]
# writes: reversible, not visible to the customer
helpdesk.draft_reply(ticket_id, body) → {draft_id}
# writes: irreversible or customer-visible, need approval
erp.post_invoice(invoice_id, po_number) → {posted_id}
helpdesk.send_reply(draft_id) → {sent_at}
Fewer, meaningful tools produce better plans, fewer wrong calls and a permission model humans can read.
Rule 2: separate reads, drafts and sends
Split every side-effecting operation into stages that map to risk:
- Read: no side effect. Agents may call freely within a budget.
- Draft / stage: creates something reversible and invisible to the outside world (a draft reply, a pending journal entry).
- Commit / send: irreversible or externally visible. Gated by policy and, above a threshold, by a human.
This lets the orchestrator do all the work up to the last step, show a human exactly what will happen, and then commit with one call. It also makes evaluation cheap: you can run the whole pipeline in tests without ever hitting a commit tool.
Rule 3: schemas are the contract, so make them strict
- Use enums for anything that has a fixed set of values (status, currency, category). Free text invites hallucinated values.
- Require identifiers, not names, for anything that is looked up (
vendor_id, notvendor_name). Provide a separate search tool that returns identifiers. - Bound every list and string (
maxItems,maxLength); bound every numeric field the business bounds. - Return structured results with a stable shape. Never return a blob of prose for the model to parse.
- Write the tool description for the model: what it does, when to use it, when not to, and what it costs. This text is part of the prompt.
Rule 4: permissions belong to the server and the credential
An MCP server should run with the narrowest credential that its tools need, and one server should not mix read-only and commit tools unless they share a risk level. In practice we deploy separate servers (or separate credentials on one server) for read and commit, and the orchestrator is the only client that holds the commit connection. Specialist agents get the read server. Whatever a prompt injection inside a document manages to convince a specialist agent to do, it cannot post an invoice.
Rule 5: treat every tool result as untrusted input
Tool results carry data from outside: PDF contents, email bodies, ticket text, web pages. That data can contain instructions aimed at the model. Defences that work in practice:
- Wrap external content in clearly delimited fields and tell the model it is data, not instruction.
- Keep commit tools out of reach of any agent that reads external content.
- Have the orchestrator verify results against the plan rather than accept them.
- Log the raw input of every tool call so an incident can be traced to its source document.
Rule 6: errors are part of the contract
Return typed errors the model can act on: not_found, ambiguous(candidates=[...]), permission_denied, rate_limited(retry_after), validation_failed(field, reason). A generic 500 with a stack trace teaches the model nothing and usually triggers a retry loop. Decide per error whether the correct behaviour is retry, ask the user, or stop.
Rule 7: log for the auditor, not the developer
Each call record should include: run id, calling agent, tool name, input (redacted where required), output summary, duration, cost, and the approval reference for commit tools. From that log you should be able to answer "who changed this record and why" without opening a model transcript.
A small, runnable reference implementation of these rules on an invoice-intake process is open source: github.com/justdukkan/invoice-intake-mcp.
A checklist before an MCP server goes to production
- Every tool has a one-sentence purpose a business owner would recognise.
- Reads, drafts and commits are distinct tools with distinct credentials.
- All inputs are schema-validated server-side; enums and identifiers used wherever possible.
- Descriptions say when not to use the tool.
- Typed errors; no retries on non-retryable errors.
- Rate limits and per-run budgets enforced by the server, not the prompt.
- Structured audit log with approval references.
- A replayable test set of calls with expected outputs, run on every change.
Frequently asked questions
Can I expose my whole API through MCP automatically?
You can, and it is a fast way to prototype, but for production we model business operations rather than endpoints. Fewer, meaningful, schema-strict tools give agents better plans and give you a permission model people can review.
How many tools should one MCP server expose?
As few as the domain needs; a dozen is common, more than thirty is a signal the server covers too many risk levels or too many domains and should be split.
Does MCP handle authentication and permissions?
MCP standardises how tools are described and called. Which credential a server uses, and which client may reach which server, is your architecture. We scope credentials per server and keep commit tools reachable only by the orchestrator.
We look at the process, propose an architecture, and decide together whether it is worth building. No packages, no price list.
Get in touch → Book a call