Boolean & Beyond  —  Field Notes
Nº 07 · Implementation Guide · 2026
Field Notes on MCP

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.

Implementation Guide/Bangalore · Coimbatore/read time: about 13 minutes
7
steps, in dependency order
0
write tools until step 06
1
identity decision, made first
2
halves to the connector
§01
Step 01

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.

1
Who
is the caller

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.

2
What
can they see

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.

3
Where
does the credential live

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.

4
How
will you prove it later

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.

§02
Step 02

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.

endpoint-shaped
task-shaped
getCustomer, getOrders, getInvoices, getTicketscustomer_snapshot

One 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_customer

A generic search with a wide parameter space is chosen wrongly and filled in wrongly. Narrow arguments are validated arguments.

executeQuery(sql)revenue_by_month

An unbounded tool cannot be reviewed, cannot be permissioned, and makes every incident unbounded too.

updateRecord(table, id, payload)issue_refund

A 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.

§03
Step 03

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.

// what the model actually sees
name: find_orders_for_customer
description: Return the 20 most recent orders for one customer,
newest first. Use when the user asks about a specific
customer's order history. Do not use for order search
across customers, or for orders older than 24 months.
input: customer_id string, required, the internal CRM id
status enum, optional: open | shipped | cancelled
returns: order id, date, status, total, currency
state what it does, when to use it, and when not to
01

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.

02

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.

03

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.

04

Describe the return shape

The model plans its next step from what it expects back. Telling it the shape reduces speculative extra calls.

05

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.

§04
Step 04

Stdio to build, HTTP to deploy.

stdio
local subprocess
  • ·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
streamable http
remote service
  • ·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.

§05
Step 05

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.

$ tsx connect-mcp.ts
const response = await client.beta.messages.create({
model: "claude-opus-5",
max_tokens: 16000,
betas: ["mcp-client-2025-11-20"],
 
// half one: where the server is
mcp_servers: [
{ type: "url", url: "https://mcp.internal/crm", name: "crm" },
],
 
// half two: expose its tools to the model
tools: [{ type: "mcp_toolset", mcp_server_name: "crm" }],
 
messages: [{ role: "user", content: userMessage }],
});
the name in mcp_servers and mcp_toolset must match exactly

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.

§06
Step 06

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.

1
Narrow
one business action per tool

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.

2
Gate
approval or hard precondition

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.

3
Idempotent
safe to retry

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.

4
Reversible
and logged as a diff

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.

§07
Step 07

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.

$ mcp-eval run --server=crm --cases=40
correct tool selected38/40
arguments valid on selection37/38
unnecessary extra calls3
refused when out of scope6/6
run this on every description change, not just every code change

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.

§08
Before you ship

The checklist.

Two users with different permissions get different answers to the same question01
No shared service-account credential on any path that touches user-scoped data02
The end user's own token is not forwarded to any downstream system03
Every tool has a description that says when not to use it04
Input schemas are strict, with enums and required fields enforced05
Fewer than roughly twenty tools, or deferred loading is enabled06
The tool list is byte-stable and deterministically ordered for caching07
Read tools shipped and observed before any write tool exists08
Every write is narrow, gated, idempotent and reversible09
Every call is traced to a person, with arguments and result recorded10
A tool-selection eval runs on every server change, including description edits11
The server version the host connects to is pinned, not floating12
§09
Learn from ours

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.

How we build these

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 service
§10
Asked first

Questions, 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.

Colophon

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 →
How to Build an MCP Server: Step-by-Step Guide | Boolean & Beyond