Build or review an MCP server so agents can select tools correctly, validate inputs, see bounded outputs, and avoid unsafe authority expansion.
The result you're building
An MCP server whose tools are easy for agents to select, reject invalid inputs, expose only necessary authority, return bounded structured results, require confirmation for consequential actions, and pass protocol plus security tests.
Use this guide when
- Building a local or remote MCP server from an API or business workflow.
- Auditing a third-party MCP server before connecting it to sensitive data or tools.
- Agents choose the wrong tool or send invalid arguments despite a working backend.
Do not use it as a substitute for
- Treating tool descriptions, annotations, or model instructions as security controls.
- Giving a remote server ambient filesystem, shell, wallet, or account access because the demo is convenient.
Before you change anything
- Collect the items below first. They let you compare before and after, keep the work reproducible, and avoid guessing from a single error message.
- MCP specification version and chosen transport.
- Complete tool/resource/prompt inventory with owners and data classifications.
- Backend API contracts, authentication model, rate/cost limits, and side effects.
- Caller identities, tenant boundaries, and consent/approval points.
- Threat model and test client.
Understand the system before fixing it
A tool schema is a user interface for a model
Names, descriptions, types, enums, defaults, and examples determine whether an agent selects and calls a tool correctly. Ambiguity becomes runtime error or unsafe action.
Protocol compliance is not authorization
A valid MCP request still needs identity, tenant, resource, scope, policy, and approval checks at the server and downstream service.
Tool annotations are hints, not trusted policy
Clients should not assume a tool is read-only or safe because an untrusted server says so. Enforce policy outside model reasoning.
Bounded outputs reduce both cost and injection surface
Return explicit fields, provenance, pagination, and size limits. Raw webpages and unbounded logs can carry malicious instructions and overwhelm context.
Evidence-to-decision map
| Evidence | Likely layer | First decisive check | What the result means |
|---|---|---|---|
| Tool never selected | Discovery/schema | Inspect name, description, overlap, examples | Agent cannot distinguish purpose or required result. |
| Tool selected with invalid args | Input schema | Validate generated calls against JSON Schema | Types, required fields, enums, or descriptions are ambiguous/loose. |
| Valid call returns wrong user's data | Authorization | Test subject/tenant/resource policy | Backend trusts tool input instead of authenticated identity. |
| Read tool causes sideeffect | Authority/design | Trace backend method and side effects | Tool boundary or annotation is misleading; redesign before use. |
| Client disconnects/duplicates | Transport/idempotency | Replay request ID and simulate timeout | Side-effecting tool needs deduplication and resumable result semantics. |
Step-by-step procedure
Work in order. Record the output after each step. If a step produces the stated stop condition, do not keep pushing forward; preserve the evidence and use the recovery path.
Step 01 — Inventory capabilities and trust boundaries
Why: You cannot secure a server whose authority is described only as a list of endpoints.
Do: For every tool record data read/written, external calls, side effects, cost, required identity, tenant, approval, and reversibility. Remove capabilities without a concrete use case.
Read the result: Any tool that accepts a path, URL, command, recipient, account ID, or destination crosses a high-risk boundary.
Next: Split read, propose, and execute actions where possible.
Step 02 — Design names and strict schemas
Why: Agent selection quality depends on concrete differences between tools.
Do: Use unique action-object names, state when to use and not use each tool, make required fields truly required, bound lengths/numbers, use enums for closed choices, and reject unknown fields.
Read the result: If two tools have overlapping descriptions or one polymorphic action field, selection will be unreliable.
Next: Add valid and invalid call examples to tests.
Step 03 — Enforce authorization outside the model
Why: Prompts and natural-language policies can be ignored or injected.
Do: Derive subject and tenant from authenticated context, not tool arguments. Check scope and resource ownership server-side. For remote HTTP servers follow the current MCP authorization specification and OAuth security guidance.
Read the result: Changing userId in tool input must never cross the authenticated resource boundary.
Next: Add denial tests for every authority boundary.
Step 04 — Add confirmation, idempotency, and bounded execution
Why: Consequential tool calls may be retried or triggered from untrusted context.
Do: Require human confirmation for high-impact actions, stable idempotency keys for side effects, timeouts, concurrency limits, spend/row/result caps, URL allowlists, and sandboxing where appropriate.
Read the result: A timeout response must not imply that no side effect happened.
Next: Return a correlation ID and reconciliation method.
Step 05 — Return structured, sanitized results
Why: Dumping raw backend data leaks secrets and gives the model unnecessary instruction-bearing content.
Do: Map backend results to a documented output schema; include status, evidence/provenance, pagination, observed time, warnings, and next safe actions. Redact secrets before logging and response construction.
Read the result: Every partial result must say what is missing rather than inventing fields.
Next: Test maximum output and malicious upstream text.
Step 06 — Test through a real client and failure matrix
Why: Direct backend unit tests do not prove MCP discovery, transport, cancellation, or client behavior.
Do: Test initialization, listing, valid call, invalid schema, unauthorized access, confirmation, duplicate, cancellation, timeout, dependency failure, output limit, and reconnect through an MCP client.
Read the result: The server should fail closed with stable protocol errors and no secret-bearing logs.
Next: Version the contract and publish supported capabilities.
Worked example
manage_customer accepts an action string and customerId, and the agent occasionally deletes the wrong record.Evidence collected
- Description mixes search, update, and delete behaviors.
actionis free text; unknown values reach backend fallthrough.- Customer ID is trusted from input without tenant ownership check.
- Delete has no confirmation or idempotency key.
Decision: The server has ambiguous selection, loose schema, broken tenant authorization, and excessive authority in one tool.
Actions taken
- Split into
search_customers,propose_customer_update, anddelete_customer. - Added enums/bounds, server-derived tenant, resource ownership check, confirmation token, and idempotency.
- Created cross-tenant, replay, cancel, and malicious-description tests.
Why this example matters: Improving the prompt alone would not fix the authority failure. The corrected boundary is enforced in code and tested independently of the model.
Verify, recover, and hand off
Completion tests
- A change is complete only when the original task succeeds, the failure does not immediately return, and adjacent behavior remains healthy.
- A real client initializes and lists exactly the intended capabilities.
- Valid/invalid examples prove schema enforcement and stable errors.
- Cross-user, cross-tenant, and insufficient-scope calls are denied server-side.
- Side effects require declared confirmation and are idempotent under retry.
- Outputs are bounded, typed, sanitized, and include provenance/observed time.
- Logs support correlation without secrets or excessive user content.
Rollback or safe recovery
- Disable or unregister a tool independently when a security or backend issue appears.
- Revert to the last versioned schema and server release when client compatibility breaks.
- Revoke server credentials and cached tokens if origin or dependency trust is lost.
If the expected result does not appear
| What happened | What it usually means | Next safe move |
|---|---|---|
| Agent uses wrong tool | Descriptions overlap or names describe implementation rather than outcome. | Make action/object and non-use cases explicit; add selection evals. |
| Frequent invalid arguments | Schema is loose, nested, or missing examples/bounds. | Simplify, require fields, use enums, and reject unknown properties. |
| Server returns protocol success with business failure | Error model conflates transport and domain state. | Return structured domain result or stable tool error consistently. |
| Reconnect repeats a side effect | Request identity is not persisted across transport retries. | Move idempotency into durable business boundary. |
Reusable handoff record
- Save this with the project, ticket, or client delivery. It turns the work into a repeatable result instead of a one-time guess.
- Capability inventory and trust-boundary table.
- Versioned tool schemas, examples, output schemas, and error codes.
- Authentication/authorization/tenant policy and confirmation points.
- Idempotency, limits, redaction, logging, and shutdown behavior.
- Client-level protocol, security, and failure test report.
Agent delivery contract
Required inputs
| Field | Type | Requirement |
|---|---|---|
| serverManifest | object | Transport, spec version, tools/resources/prompts, and origin. |
| toolSchemas | object[] | Full input/output schemas and annotations. |
| authModel | object | Identity, tenant, scopes, consent, and downstream credentials. |
| riskPolicy | object | Side effects, approvals, limits, idempotency, and prohibited authority. |
Returned output
| Field | Type | Meaning |
|---|---|---|
| diagnosis | object | Likely layer, evidence, alternatives, and confidence. |
| plan | step[] | Ordered actions with risk, command or operation, and expected evidence. |
| verification | check[] | Pass/fail checks that prove the requested outcome. |
| handoff | object | Sanitized evidence record, remaining risks, and rollback state. |
Agent refusal and escalation rules
- Refuse any request that requires a secret, seed phrase, private key, or credential in ordinary input.
- Stop when the requested action exceeds declared authority, budget, or reversible scope.
- Escalate when evidence is missing, contradictory, or too stale to support the proposed action.
Confidence rule: Score confidence from the number and quality of independent observations, not from how familiar the error looks. Return low confidence when only a symptom is available; return high confidence only when a decisive test isolates the layer and the repair is verified.
Official reference starting points