Reading MCP's specification tells you what's possible; seeing a handful of concrete server implementations across different use cases is usually what actually makes the patterns click — this walks through several representative examples covering the common shapes an MCP server takes in practice.
MCP server examples across common use cases — database access, external API wrapping, file operations, and workflow automation — illustrate recurring implementation patterns worth internalizing: careful tool scoping, clear descriptions, and appropriate use of tools versus resources for each specific kind of data or action being exposed.
Why Looking at Real Examples Matters (and When the Spec Alone Suffices)
Concrete examples reveal practical decisions the abstract specification doesn't fully convey — how granular to make tools, when to split one broad capability into several narrow ones, how to structure error responses usefully — the kind of judgment calls that become clearer from seeing worked examples than from reading protocol documentation alone.
The specification alone suffices once you've internalized these patterns from enough real examples — at that point, working from the spec directly for a genuinely novel use case is more efficient than searching for an example that happens to match closely.
Example 1: A Scoped Database Query Tool
Rather than exposing raw SQL execution, scope the tool to specific, safe query patterns:
server.tool(
"search_orders",
{ customerId: z.string(), status: z.enum(["pending", "shipped", "delivered"]).optional() },
async ({ customerId, status }) => {
const orders = await db.orders.findMany({
where: { customerId, ...(status && { status }) },
});
return { content: [{ type: "text", text: JSON.stringify(orders) }] };
}
);
This pattern — specific, parameterized queries rather than raw SQL access — is the difference between a reasonably safe tool and one with a dangerously broad blast radius.
Example 2: Wrapping a Third-Party API With Rate Limit Awareness
server.tool(
"get_weather",
{ city: z.string() },
async ({ city }) => {
const cached = await cache.get(`weather:${city}`);
if (cached) return { content: [{ type: "text", text: cached }] };
const response = await rateLimitedFetch(`https://api.weather.example.com/${city}`);
const data = await response.text();
await cache.set(`weather:${city}`, data, { ttl: 300 });
return { content: [{ type: "text", text: data }] };
}
);
Wrapping third-party APIs benefits from caching and rate limit handling at the MCP server layer, protecting both your API quota and response latency.
Example 3: A File Resource With Directory Scoping
const DOCS_DIR = path.resolve("./docs");
server.resource(
"documentation",
new ResourceTemplate("docs:///{filename}", { list: async () => {
const files = await fs.readdir(DOCS_DIR);
return { resources: files.map((f) => ({ uri: `docs:///${f}`, name: f })) };
}}),
async (uri, { filename }) => {
const safePath = path.join(DOCS_DIR, path.basename(filename));
const content = await fs.readFile(safePath, "utf-8");
return { contents: [{ uri: uri.href, text: content }] };
}
);
Note the path.basename() call — scoping resource access to a specific directory and sanitizing the filename prevents path traversal outside the intended docs directory.
Example 4: A Multi-Step Workflow Tool With Confirmation
server.tool(
"deploy_service",
{ serviceName: z.string(), environment: z.enum(["staging", "production"]) },
async ({ serviceName, environment }, extra) => {
if (environment === "production" && !extra.userConfirmed) {
return {
content: [{ type: "text", text: `Confirm production deploy of ${serviceName}?` }],
requiresConfirmation: true,
};
}
const result = await deploymentService.deploy(serviceName, environment);
return { content: [{ type: "text", text: `Deployed: ${result.deploymentId}` }] };
}
);
Consequential actions like production deployments should require explicit confirmation, following the same principle covered in MCP security guidance — high blast-radius actions need a human checkpoint.
Common Patterns Worth Internalizing From These Examples
Scope tools narrowly to specific, safe operations rather than exposing raw, general-purpose access — a scoped search_orders tool is safer and often more usable than raw SQL access, since the model doesn't need to construct correct SQL and can't accidentally (or maliciously, via injection) run an unintended query.
Cache and rate-limit third-party API calls at the MCP server layer, protecting both your API quota and the responsiveness of tool calls, rather than making a fresh external call on every invocation regardless of recency.
Sanitize any path or identifier used to construct a file/resource lookup, preventing path traversal or unintended access outside the scoped directory or dataset the tool is meant to expose.
Require explicit confirmation for consequential, hard-to-reverse actions, building the checkpoint directly into the tool's logic rather than assuming external process will always catch it.
When Should You Build a Narrow, Single-Purpose Tool Instead of a Flexible, General One?
Build narrow tools when the operation has real consequences (data modification, external side effects) where a general-purpose interface (raw SQL, arbitrary shell commands) would create too broad a blast radius. Consider a more flexible tool only for genuinely low-consequence, read-only operations where the flexibility provides real value and the security tradeoff is minimal.
Applying These Patterns in Production
Treat every tool's scope as a deliberate security decision, not just an implementation convenience — narrow, specific tools are safer and often easier for a model to use correctly than broad, general-purpose ones. Apply caching, confirmation checkpoints, and input sanitization consistently across your tools, following the patterns shown here rather than reinventing safety practices ad hoc per tool.
If you're building your first MCP server, starting from a pattern like the scoped database query or confirmation-gated action example is a safer foundation than starting from a broad, general-purpose interface and trying to narrow it later.