# MCP servers: practical patterns for reliable agent tool-use
This post outlines pragmatic patterns for building and operating Model Context Protocol (MCP) servers in production. Use these ideas to keep agent tool-use safe, observable, and reusable across teams.
## 1) Keep tools explicit and composable
- Prefer a small set of well-named tools over generic kitchen-sink handlers.
- Use JSON Schema to validate inputs early and return helpful errors.
- Compose complex outcomes from simple tools (e.g., `search_docs` → `summarize` → `create_ticket`).
## 2) Make long jobs stream
- Emit progress events for multi-step or slow operations (indexing, bulk export).
- Include correlation IDs and timestamps so clients can render progress UIs.
## 3) Control blast radius with scopes
- Grant read-only by default; escalate scope only when needed.
- Use per-tool allow/deny lists; consider confirm dialogs for destructive actions.
## 4) Add observability at the boundary
- Log inputs (with redaction), outputs, durations, and error classes.
- Sample traces and include request IDs to debug cross-service flows.
## 5) Design for retries and idempotence
- Prefer PUT/UPSERT-like semantics where meaningful.
- Return typed error hints: `retryable`, `auth`, `validation`.
## 6) Bound outputs and paginate
- Return at most N items per page; include cursors.
- Allow clients to request only needed fields.
## 7) Standardize naming and schemas
- Keep tool names stable; document breaking changes.
- Version schemas if you must change shapes; offer deprecation windows.
## 8) Harden transports
- Prefer stdio for local/desktop flows; WebSocket for networked servers.
- Terminate TLS at a trusted edge for remote servers and rotate keys.
## 9) Provide test fixtures and sandboxes
- Ship example payloads and a sandbox mode to validate integration quickly.
## 10) Reuse servers across clients
- The same MCP server should serve desktop assistants, IDEs, and server-side orchestrators without code changes.
---
### Minimal example (conceptual TypeScript)
```ts
import { createServer } from "mcp";
const server = createServer({
name: "tickets",
tools: [{
name: "create_ticket",
description: "Create a ticket with title and body",
inputSchema: {
type: "object",
properties: {
title: { type: "string" },
body: { type: "string" }
},
required: ["title", "body"]
},
handler: async ({ title, body }) => ({ id: crypto.randomUUID(), title })
}]
});
server.listen({ transport: "stdio" });
```
---
### Checklist before production
- [ ] Input schemas validated
- [ ] Scopes and confirmations in place
- [ ] Streaming for long operations
- [ ] Metrics and logs wired
- [ ] Retry policy and idempotence tested
- [ ] Pagination for large results
---
In short: make capabilities explicit, scopes tight, and behavior observable. Your agents—and your SREs—will thank you.
MCP servers: practical patterns for reliable agent tool-use
Practical MCP patterns for designing safe, observable, and reusable agent tooling.
