
TL;DR
Build MCP servers that connect Claude to your databases, APIs, and tools. Architecture, TypeScript SDK code, debugging, and the production gaps the spec doesn't cover.
The Model Context Protocol is the bit of plumbing that turns Claude from "an LLM with tools you wrote inline" into "an LLM that can talk to any system on your network through a standardized contract." That sentence sounds like it could be a press release, so here is the engineering version: MCP is a JSON-RPC-over-stdio (or SSE, or HTTP) protocol that lets a client — Claude Desktop, Claude Code, the Agent SDK, or your own — connect to a server that exposes resources (data Claude can read) and tools (actions Claude can take), with capability negotiation, structured errors, and persistence.
The reason it matters: you can write a tool layer once and reuse it across every Claude surface and, increasingly, across other agents. The reason most teams misuse it: they build an MCP server when they should have shipped inline tool use, or vice versa.
We did the MCP Deep Dive: Building Extensible Agents video on the channel walking through a live MCP build. This is the production-grade companion. We build a real server, debug it, talk about transports, and cover the spec gaps you will hit the first week in production.
A short heuristic before we go deeper, because this is the question I get most often:
Building an MCP server for a three-tool weather agent is overkill. Building inline tool use to expose your entire production database to Claude Code is going to be unmaintainable. Pick the right layer.
For a deeper look at when each pays off, the MCP vs function calling post covers the trade-offs and the DD MCP Lens debugger shows the kind of tooling you will eventually want when you are running real MCP servers.
The protocol has three pieces:
stdio for local processes, streamable HTTP (formerly SSE) for remote servers.Capability negotiation happens during the initialize handshake. The client says what it supports; the server says what it offers. Both sides know what is on the table before any real work starts. This is what lets the protocol evolve without breaking older clients.
Two distinctions worth burning in:
postgres://customers/123). Tools are actions (run_migration, send_email). Use resources for "let Claude read this," use tools for "let Claude do this." Mixing them up is the most common architectural mistake in early MCP servers.The official @modelcontextprotocol/sdk is the path of least resistance for a real server. Here is a minimal Postgres-backed server exposing customer rows as resources and a query_revenue tool. It is short, but every piece is the production shape.
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListResourcesRequestSchema,
ListToolsRequestSchema,
ReadResourceRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { Pool } from "pg";
import { z } from "zod";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const server = new Server(
{ name: "acme-customers", version: "0.1.0" },
{ capabilities: { resources: {}, tools: {} } }
);
server.setRequestHandler(ListResourcesRequestSchema, async () => {
const { rows } = await pool.query(
"SELECT id, name FROM customers ORDER BY name LIMIT 100"
);
return {
resources: rows.map((r) => ({
uri: `postgres://customers/${r.id}`,
name: r.name,
mimeType: "application/json",
})),
};
});
server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
const id = req.params.uri.replace("postgres://customers/", "");
const { rows } = await pool.query("SELECT * FROM customers WHERE id = $1", [id]);
if (!rows.length) throw new Error(`Customer ${id} not found`);
return {
contents: [
{
uri: req.params.uri,
mimeType: "application/json",
text: JSON.stringify(rows[0], null, 2),
},
],
};
});
const QueryRevenueInput = z.object({
customer_id: z.string(),
start_date: z.string(),
end_date: z.string(),
});
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "query_revenue",
description:
"Sum revenue for a customer between two ISO dates (inclusive). Returns USD cents.",
inputSchema: {
type: "object",
properties: {
customer_id: { type: "string" },
start_date: { type: "string", description: "YYYY-MM-DD" },
end_date: { type: "string", description: "YYYY-MM-DD" },
},
required: ["customer_id", "start_date", "end_date"],
},
},
],
}));
server.setRequestHandler(CallToolRequestSchema, async (req) => {
if (req.params.name !== "query_revenue") {
throw new Error(`Unknown tool: ${req.params.name}`);
}
const input = QueryRevenueInput.parse(req.params.arguments);
const { rows } = await pool.query(
`SELECT COALESCE(SUM(amount_cents), 0) AS total
FROM invoices
WHERE customer_id = $1 AND invoice_date BETWEEN $2 AND $3`,
[input.customer_id, input.start_date, input.end_date]
);
return {
content: [{ type: "text", text: JSON.stringify({ total_cents: rows[0].total }) }],
};
});
const transport = new StdioServerTransport();
await server.connect(transport);
Wire this up in claude_desktop_config.json:
{
"mcpServers": {
"acme-customers": {
"command": "node",
"args": ["/abs/path/to/dist/server.js"],
"env": { "DATABASE_URL": "postgres://..." }
}
}
}
Restart Claude Desktop, and the customers list shows up as resources, the tool shows up in the tool palette. That is the basic shape. Everything from here is making it production-ready.
Get the weekly deep dive
Tutorials on Claude Code, AI agents, and dev tools - delivered free every week.
Three patterns we have shipped variations of:
Private data access. The example above. Database rows, internal API responses, file system contents — anything the model should be able to read without you exporting it. The key design choice: expose row-level resources, not schemas. Let Claude pull only what it needs, not the whole table.
Multi-service orchestration. A single MCP server that fronts five internal microservices. Tools become the public API of your platform, not the public API of each service. This is where MCP shines for internal devtools — you ship one server, expose curated actions, and every Claude surface in the company can use it.
Long-running processes. Tools that kick off background jobs and return a job ID, paired with a resource that exposes the job status. This is where MCP beats inline tool use cleanly: the model can poll the resource until the job finishes, and the server holds the state.
Things to handle in production:
version field and add new tools alongside old ones. Do not silently change tool semantics — clients cache schemas.The MCP Inspector is the first tool to reach for. Run it against your server before you ever connect Claude:
npx @modelcontextprotocol/inspector node dist/server.js
It opens a UI showing the protocol handshake, lists your resources and tools, and lets you invoke them with arbitrary inputs. Half the bugs we hit in early MCP servers are visible in the inspector before they are visible in Claude.
Beyond the inspector, three production must-haves:
tool_name, duration_ms, ok, error. Stdio servers should log to stderr — Claude Desktop captures it.GET /health returning 200. Cheap insurance.Common issues we see, in rough frequency order:
tools/list aggressively. Restart the client after schema changes during dev.Once you have more than one MCP server, things to watch:
Tool name collisions. Two servers exposing search will confuse the client. Namespace by server (acme-customers__query_revenue) — most clients do this automatically, but some do not. Check your client's behavior.
Load balancing. For HTTP servers, run multiple instances behind a load balancer. JSON-RPC requests are mostly stateless. Where you have state (subscriptions, long-running jobs), pin sessions or move state to a shared store.
Auth. stdio inherits the user's local trust. HTTP servers need real auth. The current dominant pattern is OAuth 2.1 with the client doing a browser-based flow on first connect. The MCP spec has a section on this; follow it, do not roll your own.
Rate limiting. Per-tool, per-client rate limits. A runaway agent should not be able to DOS your server. Hard caps in the transport, not in each handler.
Ecosystem reality check. As of mid-2026 there are dozens of open-source MCP servers worth using off the shelf — Postgres, GitHub, filesystem, Slack — and many more that are stale or half-built. Check the last commit date and the issue tracker before you depend on one. We maintain a curated list of the top MCP servers that matter for exactly this reason.
MCP is the right tool when your integration is something a system owns and should be reusable across agents. It is the wrong tool when you have three handlers tightly coupled to one prompt. Get the boundary right, and the rest of the spec is straightforward engineering.
For more on the broader Claude developer stack, see our guides on tool use patterns and prompt caching.
Technical content at the intersection of AI and development. Building with AI agents, Claude Code, and modern dev tools - then showing you exactly how it works.
Visual testing tool for Model Context Protocol servers. Like Postman for MCP - call tools, browse resources, and view...
View ToolLightweight CLI for discovering and calling MCP servers. Dynamic tool discovery reduces token consumption from 47K to 40...
View ToolCentralized manager for MCP servers. Connect once to localhost:37373 and access all your servers through a single endpoi...
View ToolRegistry and hosting platform for MCP servers. 6,000+ servers indexed. One-command install and configuration via CLI. Su...
View ToolConfigure Claude Code for maximum productivity -- CLAUDE.md, sub-agents, MCP servers, and autonomous workflows.
AI AgentsConfigure model, effort, tools, MCP servers, and invocation scope.
Claude CodeStep-by-step guide to building an MCP server in TypeScript - from project setup to tool definitions, resource handling, testing, and deployment.
AI Agents
MCP is the USB-C of AI agents. What the Model Context Protocol is, why Anthropic built it, and how to install your first...

MCP lets AI agents connect to databases, APIs, and tools. Here is what it is and how to use it in your TypeScript projec...

Master tool use in the Claude API. Schema design, retry logic, multi-step loops, and the failure modes that only show up...

New tutorials, open-source projects, and deep dives on coding agents - delivered weekly.