2026 Practical Guide

MCP & Tool
Design Guide

Giving a model new abilities is an exercise in interface design
A good Tool is not a good API — because the consumer is a model, not a human

3 core primitives
10 Tool design principles
6 anti-patterns to avoid

What is MCP

Model Context Protocol — a standard adapter between a model and the outside world

MCP is "USB-C for AI." There was a time when every tool, data source, or system you connected to a model required its own bespoke wiring. MCP standardizes that connection into one protocol. On one side is a host (Claude, an IDE, an agent); on the other is an MCP server (GitHub, a database, an internal system). They talk only through an agreed message format — so build a server once and it plugs into any host that speaks the protocol.
LensBefore MCPAfter MCP
Integration A bespoke plugin per host (the N×M problem) 1 server → every host can use it (N+M)
Standard Ad-hoc function-calling formats A common spec over JSON-RPC 2.0
Ecosystem Vendor lock-in Broadly adopted by major coding agents in 2026; governance moved under the Linux Foundation
Role "What the model knows" "What the model can do"

* Ecosystem figures and adoption are a June 2026 snapshot. Check modelcontextprotocol.io for the current state.

The 3 Primitives

Everything an MCP server exposes to a host comes down to these three

1

Tools — functions that act

Actions the model calls, with side effects. The "write" seat

Search something, create an issue, run a query — active actions the model decides on and calls. Each has an input schema (JSON Schema) and a description, and most of §4's design principles live here. Because side effects are possible, you flag read-only vs. destructive behavior via annotations.

2

Resources — read-only context

Data the model/host reads in. The "read" seat

File contents, DB records, documents — read-only data identified by a URI. The key difference from a Tool is that it has no side effects and is usually driven by the host (app) to pull in context. A flow like "add this PDF to context" is a Resource.

3

Prompts — reusable workflow templates

Pre-formed tasks the user picks. The "slash command" seat

Templates like "review this code" or "draft release notes" that take arguments and expand. Typically the user explicitly chooses them (slash command, menu). The server offers common tasks in a vetted form so you don't rewrite the prompt every time.

Tell them apart by who drives. The model picks and calls Tools; the app pulls in Resources as context; the user selects and runs Prompts. Conflate them and make everything a Tool, and the model must "decide to call" even for read-only data — bloating the tool list for nothing.

How it works

Message format, transport, auth — the skeleton to know before you build

A

Host · Client · Server

Three separated roles
Host / Client

The host is the app that holds the model (Claude, an IDE). It spins up one client per server connection and talks 1:1.

Server

The side you build. It exposes Tools, Resources, and Prompts — and works without knowing which host is calling.

The server is host-independent — build it once and every host that speaks the protocol reuses it. That's MCP's core payoff.

B

JSON-RPC 2.0 · transports

How messages travel

Every message follows the JSON-RPC 2.0 spec. There are two transports — local processes use stdio (standard I/O), remote servers use Streamable HTTP. Same message format, so write server logic decoupled from transport.

// tools/call request (host → server) { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "search_orders", "arguments": { "status": "shipped" } } }
C

Auth — OAuth 2.1

The permission gate for remote servers

Remote HTTP servers use OAuth 2.1-based auth as the standard. The core rule is least privilege — issue token scopes only as broad as the task needs, and keep secret keys on the server side only. Never let credentials leak into the tool definitions the model sees.

10 Tool Design Principles

The consumer being a model, not a human, changes everything

1

Name it verb+noun, with clear intent

The model infers from the name alone whether to use it

Use names where the action reads instantly — search_orders, create_issue. Vague names like do_action or handle confuse the model. The name is the first signal it uses to pick a tool.

2

The description IS a prompt

Including when to use it — and when not to

The description isn't a human comment; it's an instruction to the model. Write not just "what it does" but "when it's appropriate and when to reach for another tool instead." It's the highest-ROI single line you'll write.

3

Fewer params, stricter types

Lean on JSON Schema · enum · required

Free-text params are easy to get wrong. When the valid values are known, pin them with enum; mark mandatory fields required. The more params, the higher the odds of filling one wrong — keep only what's essential.

4

Make output easy for the model to read

And token-thrifty

The model parses your response again. Return lean, structured results and strip human-facing decoration (HTML, excessive metadata). Stay aware that every response is context tokens.

5

Make errors recoverable by the model

Reason for failure + hint for the next move

Don't throw a raw stack trace. Say "order ID not found — the format is ORD-XXXX," giving why it failed and how to fix it in natural language. That's what lets the model self-recover with a retry.

6

Approval gate for destructive actions

Human-in-the-loop isn't optional

Hard-to-undo actions — delete, charge, external send — should require human approval before they run. Flag them with annotations.destructiveHint so the host can surface a confirmation step. Safety before the convenience of automation.

7

Control the number of tools

More tools = worse selection accuracy

Throw 50 tools at once and the model picks the wrong one (context rot). Merge similar capabilities and route which tools to expose by context. The rule isn't "expose every feature" but "only what's needed now."

8

Declare side effects and idempotency

readOnlyHint · idempotentHint

State whether a tool is read-only (readOnlyHint) and whether repeating the same call is safe (idempotentHint). This is how the host and model safely decide on retries and parallel calls.

9

Paginate and cap large responses

Huge results devour context

Return 100K rows and you blow up context and cost. Set a default limit and cursor-based pagination, and let the model ask for more when it needs it.

10

Least privilege, secrets on the server

OAuth 2.1 · minimal scopes

A tool's privileges are your blast radius when something goes wrong. Grant only the minimal scope the task needs, and keep API keys and tokens inside the server. Never expose credentials in the interface the model sees.

6 Common Mistakes

Most MCP servers trip over the same spots

!

Rebuilding a server that already exists

The most common waste. Major systems — GitHub, Slack, databases — usually already have an official or community server.

→ Search the registry before you build. Build only when nothing exists.
!

Exposing REST endpoints 1:1

Turning 50 existing API endpoints into 50 tools. The model uses tools differently than a human — they end up too granular and too many.

→ Group by the model's "intent." get_user+get_ordersget_customer_summary.
!

Empty or human-only descriptions

Settling for "Order lookup API." The model gets no cue about when to reach for this tool.

→ Write when to use it and when not, with input examples, from the model's point of view.
!

Returning a giant response verbatim

Handing back a whole table or full log. Context blows up and token cost spikes.

→ Default cap + pagination + a summary of only the needed fields.
!

No approval gate on destructive actions

Letting the model delete, transfer money, or send mail directly. One misjudgment becomes irreversible.

→ destructiveHint + a human confirmation step before execution.
!

Dumping 50 tools and hoping it picks right

"More tools = smarter" is backwards. More choices means more wrong picks and higher cost.

→ Narrow exposed tools with context routing, and consolidate similar ones.

Get started — buy or build?

A 3-step decision before you stand up a new server

Step 1

Does it already exist?

Search the official and community MCP server registries first. GitHub, Slack, Google Drive, Postgres and the like mostly already exist. If it does, use it — a custom build is maintenance debt.

Step 2

Is it internal-only?

Build your own only when no public server can exist — internal systems, your own DB, domain logic. Start from an official SDK (Python, TypeScript, etc.) and use §4's principles as your checklist.

Step 3

Read first; writes come with a gate

Ship read-only tools first and watch whether the model uses them well. Open up writes and destructive actions gradually, behind an approval gate. Don't hand over full power on day one.

Designing an MCP server is an extension of API design. The one difference is that the consumer is a model, not a human. Good names, a clear contract, strict types, safe permissions — the instincts that made you good at APIs carry straight over. What changes is that "the description is read by a model, not a person," and that "get it wrong and the model wanders off on its own."