Claude Code can refactor your entire codebase, write migration scripts, and reason about complex architecture decisions. But ask it to look up a customer record in your internal API, query your company's Jira board, or check the latest deployment status from your CI pipeline—and it draws a blank.
That's not a limitation of the model. It's a limitation of what the model can reach. Out of the box, Claude Code has access to your filesystem, your terminal, and your git history. That covers a lot. But your real development workflow lives across dozens of systems: internal APIs, databases, monitoring dashboards, documentation wikis, deployment pipelines, and domain-specific tools your team built in-house.
This is where MCP servers come in. The Model Context Protocol is the standard that lets you hand Claude Code new capabilities—your capabilities—without modifying Claude itself. You build a server. Claude connects to it. Now Claude can do things it couldn't do before: query your database, hit your internal REST API, pull context from your wiki, or trigger your deployment pipeline.
If the previous articles in this series covered how to configure Claude Code (CLAUDE.md) and constrain it (hooks), this one is about giving it new powers.
MCP stands for Model Context Protocol. It's an open standard introduced by Anthropic in late 2024 and now governed by the Agentic AI Foundation under the Linux Foundation. The protocol has been adopted across the industry—Claude, ChatGPT, GitHub Copilot, and others support it.
The mental model is straightforward: MCP is a contract between an AI tool (the client) and an external system (the server). The client says "what can you do?" The server responds with a list of tools, resources, and prompts. The client then calls those tools as needed during a conversation.
Think of it like a USB-C port for AI. Before USB-C, every device had its own charger. Before MCP, every AI integration was a custom connector. MCP standardizes the interface so you build your integration once and it works across any MCP-compatible client.
Three primitives make up the protocol. Tools are functions Claude can call—they perform actions and return results. Think API calls, database queries, file operations with side effects. Resources are read-only data sources Claude can pull in for context—database schemas, documentation, configuration files. Prompts are reusable templates that guide how Claude interacts with your tools—standardized ways to invoke complex workflows.
MCP is not a plugin marketplace (though servers are shareable). It's not an API gateway (though it sits in front of your APIs). It's a protocol—a set of rules for how AI agents and external systems talk to each other.
There are 500+ pre-built MCP servers available. PostgreSQL, GitHub, Slack, Stripe, AWS—if it's a mainstream service, someone's probably built an MCP server for it. You can install these in minutes.
But here's when you need to build your own:
Your company has an internal REST API for managing customer accounts. No public MCP server exists for it. You need Claude to pull customer context during debugging sessions—"show me the subscription tier and last 5 support tickets for customer X." Your team built a custom deployment system on top of Kubernetes. You want Claude to check pod status, read deployment logs, and trigger rollbacks—all through natural language. Your documentation lives in a proprietary wiki. Claude needs to search it when answering architecture questions about your system.
In each case, the domain knowledge is yours. The API is yours. The data model is yours. No generic server will understand your company's customer schema or your team's deployment naming conventions. You build it because you own the domain.
Let's build a concrete example. Say your team has an internal REST API that tracks feature flags. Developers constantly ask: "Is the dark-mode flag enabled in staging?" or "Which flags were modified this week?" Right now, they open a browser, navigate to the admin panel, and look it up manually. Let's give Claude that capability instead.
mkdir feature-flag-mcp && cd feature-flag-mcp
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript tsx @types/node
npx tsc --init
The @modelcontextprotocol/sdk package handles all the protocol plumbing—JSON-RPC serialization, capability negotiation, transport management. You focus on your domain logic.
// src/index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "feature-flags",
version: "1.0.0",
});
// Tool: Check if a flag is enabled in an environment
server.tool(
"check_flag",
"Check whether a feature flag is enabled in a given environment",
{
flag_name: z.string().describe("The feature flag key"),
environment: z.enum(["dev", "staging", "production"]).describe("Target environment"),
},
async ({ flag_name, environment }) => {
const response = await fetch(
`${process.env.FLAGS_API_URL}/api/flags/${flag_name}?env=${environment}`,
{ headers: { Authorization: `Bearer ${process.env.FLAGS_API_KEY}` } }
);
const data = await response.json();
return {
content: [
{
type: "text",
text: JSON.stringify(data, null, 2),
},
],
};
}
);
// Tool: List recently modified flags
server.tool(
"recent_changes",
"List feature flags modified in the last N days",
{
days: z.number().default(7).describe("Number of days to look back"),
},
async ({ days }) => {
const since = new Date(Date.now() - days * 86400000).toISOString();
const response = await fetch(
`${process.env.FLAGS_API_URL}/api/flags?modified_since=${since}`,
{ headers: { Authorization: `Bearer ${process.env.FLAGS_API_KEY}` } }
);
const data = await response.json();
return {
content: [
{
type: "text",
text: JSON.stringify(data, null, 2),
},
],
};
}
);
// Resource: Expose the flag schema as context
server.resource(
"flag-schema",
"flags://schema",
async (uri) => ({
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify({
flag_name: "string (kebab-case)",
enabled: "boolean",
environment: "dev | staging | production",
last_modified: "ISO 8601 timestamp",
modified_by: "string (email)",
}),
},
],
})
);
// Connect transport
const transport = new StdioServerTransport();
await server.connect(transport);
Notice the pattern: each tool has a name, a description (this is what Claude reads to decide when to use it), a Zod schema for input validation, and an async handler that does the actual work. The resource exposes the flag schema so Claude understands your data model without you repeating it every conversation.
Create a .mcp.json file in your project root:
{
"mcpServers": {
"feature-flags": {
"command": "npx",
"args": ["tsx", "src/index.ts"],
"env": {
"FLAGS_API_URL": "https://flags.internal.yourcompany.com",
"FLAGS_API_KEY": "your-api-key-here"
}
}
}
}
That's it. Next time Claude Code starts in this project directory, it discovers the server, negotiates capabilities, and now Claude can check feature flags and list recent changes—through natural language.
You can verify the connection by running /mcp inside Claude Code. It'll show your server, its status, and the tools it exposes.
Here's the actual flow when you ask Claude "Is the dark-mode flag enabled in staging?"
Claude reads your message and recognizes it matches the check_flag tool's description. It constructs a call with {flag_name: "dark-mode", environment: "staging"}. The MCP SDK serializes this as a JSON-RPC request over stdio. Your handler runs, hits your internal API, and returns the result. Claude receives the response and weaves it into its answer: "The dark-mode flag is currently disabled in staging. It was last modified by alice@yourcompany.com on March 19."
Claude didn't guess. It didn't hallucinate. It queried your actual system and reported the result. That's the shift—from probabilistic knowledge to grounded, real-time data.
MCP supports two active transport types (SSE is deprecated):
stdio is what we used above. Claude Code spawns your server as a child process and communicates over stdin/stdout. Best for local development, personal tools, and project-specific servers. Zero network overhead. The server runs on your machine alongside Claude.
HTTP (Streamable HTTP) is for remote servers. Your MCP server runs on a cloud instance or internal server, and Claude connects over HTTPS. Best for shared team tools, production services, or when the server needs access to resources not available on your local machine (like a production database replica).
For most developer workflows, start with stdio. It's simpler, faster, and doesn't require network configuration. Graduate to HTTP when you need to share the server across your team or connect to remote resources.
The most useful MCP servers don't just wrap a single API. They join data. Imagine a tool called customer_context that pulls a customer's subscription tier from your billing API, their last 5 support tickets from Zendesk, and their recent API usage from your analytics system—all in one call. Claude gets a complete picture without you manually gathering data from three dashboards.
Follow the principle of least privilege. Your first version should only read data. Add write operations (toggle a flag, create a ticket, trigger a deployment) only when you've validated the read-only version in production. When you do add writes, use Claude Code hooks to gate dangerous operations—a PreToolUse hook that blocks toggle_flag in the production environment unless explicitly approved.
Claude decides which tool to call based on the tool's name and description. A vague description like "manages flags" means Claude won't reliably match user intent. A precise description like "Check whether a specific feature flag is enabled or disabled in a given environment (dev, staging, or production)" gives Claude the semantic signal it needs. Invest time in your descriptions—they're the UX of your MCP server.
Don't make Claude guess your data model. Expose your database schema, API response formats, or enum values as MCP resources. Claude reads these automatically and uses them to construct better queries and interpret results more accurately. It's the difference between Claude asking "what format is the date in?" and Claude just knowing.
The MCP Inspector (npx @modelcontextprotocol/inspector) gives you a visual interface for testing tools, resources, and prompts without Claude Code in the loop. You can invoke tools, inspect responses, and catch errors before they surface in a conversation. Use it during development the same way you'd use Postman for REST APIs.
For automated testing, the SDK provides programmatic clients. Write integration tests that instantiate your server, call each tool with known inputs, and assert on outputs. Run these in CI. Your MCP server is production code—treat it like production code.
MCP servers run with the permissions of the process that spawns them. That means your API keys, database credentials, and internal URLs are all in play. A few non-negotiables:
Store credentials in environment variables, never hardcoded. Use the env field in .mcp.json or pull from a secrets manager. Use read-only database users unless write access is explicitly required. Add audit logging to every tool handler—log what was called, with what arguments, at what time. If you expose a tool that modifies state (toggle a flag, restart a service), gate it behind a Claude Code hook that requires explicit human approval.
The combination of MCP tools for capabilities and Claude Code hooks for governance is the security model. MCP gives Claude the power. Hooks ensure it uses that power responsibly.
If you've followed this series, you now have three layers of control over Claude Code:
CLAUDE.md defines the conversation context—your project's architecture, conventions, and guidelines. It shapes how Claude thinks about your codebase.
Hooks enforce hard constraints—quality gates that fire regardless of conversation depth. They ensure Claude operates within your safety boundaries.
MCP servers extend Claude's reach—giving it access to your internal systems, data, and domain-specific tools. They make Claude useful beyond the filesystem.
Together, these three layers turn Claude Code from a smart code assistant into a developer platform that understands your specific environment. Claude knows your conventions (CLAUDE.md), respects your guardrails (hooks), and can query your systems (MCP). That's not just AI-assisted development—it's AI-integrated development.
Start with one MCP server this week. Pick the system your team queries most—the one where developers alt-tab to a browser ten times a day. Build a read-only MCP server for it. Register it with Claude Code. See what happens when Claude can answer those questions directly.
You might find that the hardest part isn't building the server. It's choosing which system to connect first, because once you see how it works, you'll want to connect everything.