Anatomy of MCP Servers
Model Context Protocol servers, capabilities, security, and setup.

The building blocks that connect Claude Code to the outside world โ GitHub, databases, Slack, files, browsers โ through one open protocol.
TL;DRโ
- MCP (Model Context Protocol) is Anthropic's open standard so Claude can talk to any external tool through one common language.
- An MCP server is a small program that exposes Tools, Resources, and Prompts to Claude.
- You configure them in
.mcp.jsonat your project root โ Claude auto-discovers them on launch. - Transports: stdio (local, default), HTTP/HTTPS (remote), WebSocket (real-time).
- Treat MCP servers like signed-in employees: least privilege, scoped tokens, audit logs.
Why this mattersโ
Think of Claude as a brilliant consultant locked in a glass office. MCP servers are the doors and badges that let it reach the file cabinet, the database, the Slack channel โ without you copy-pasting context. One protocol replaces a dozen bespoke integrations.
Bottom line: MCP is to AI tools what USB is to hardware โ plug it in and it works.
The Big Pictureโ
Bottom line: One client, many servers, one protocol.
Core Conceptsโ
| Concept | Plain English |
|---|---|
| Client | Claude Code itself |
| Server | A small program that exposes tools/data |
| Protocol | JSON-RPC 2.0 messages over a transport |
| Tool | A callable function with a JSON Schema |
| Resource | Read-only data exposed via a URI |
| Prompt | A reusable prompt template the server provides |
| Transport | How bytes flow: stdio / HTTP / WebSocket |
| Capability | A feature flag the server advertises at handshake |
.mcp.json Referenceโ
{
"mcpServers": {
"github": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@anthropic/mcp-github"],
"env": { "GITHUB_TOKEN": "${GITHUB_TOKEN}" }
},
"postgres": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@anthropic/mcp-postgres"],
"env": { "DATABASE_URL": "${DATABASE_URL}" }
},
"slack": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@anthropic/mcp-slack"],
"env": { "SLACK_BOT_TOKEN": "${SLACK_TOKEN}" }
},
"filesystem": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@anthropic/mcp-filesystem", "./src", "./tests"]
}
}
}
๐ก Tip: Use
${ENV_VAR}interpolation โ never paste raw tokens.
Building an MCP Server (TypeScript)โ
npm i @modelcontextprotocol/sdk
// src/server.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: "hello-server",
version: "1.0.0",
capabilities: { tools: {} }
});
// Register a tool with input schema
server.tool(
"create_pr",
{ title: z.string(), body: z.string(), head: z.string(), base: z.string() },
async ({ title, body, head, base }) => {
// ... actual GitHub API call here
return { content: [{ type: "text", text: `PR opened: ${title}` }] };
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP server running on stdio");
Then register it in .mcp.json:
"hello": { "type": "stdio", "command": "node", "args": ["./dist/server.js"] }
Calling a Tool (raw JSON-RPC example)โ
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "create_pr",
"arguments": {
"title": "Add user analytics",
"body": "Adds dashboard and events.",
"head": "feature/analytics",
"base": "main"
}
}
}
You'll rarely write this yourself โ Claude does it for you. But knowing the wire format is gold when debugging.
Transport Optionsโ
| Transport | When to use | Tradeoff |
|---|---|---|
| stdio | Local servers (default) | Simple, fast, secure โ but one process per client |
| HTTP / HTTPS | Remote / shared servers | Easy to deploy & scale, needs auth |
| WebSocket | Real-time, bidirectional | Long-lived connections; more ops overhead |
Common Server Types You'll Actually Useโ
- Code hosting โ GitHub, GitLab, Bitbucket
- Databases โ PostgreSQL, MySQL, MongoDB, Redis
- Communication โ Slack, Discord, email
- Search & docs โ Notion, Confluence, Google Drive
- DevOps & cloud โ AWS, Docker, Kubernetes
- Utilities โ Filesystem, shell, generic web APIs
Variants for Different Team Settingsโ
๐ค Solo developerโ
- One
.mcp.json, GitHub + Filesystem only. - Tokens in
~/.env, sourced beforeclaudelaunch.
๐ฅ Small team (2โ10)โ
- Commit
.mcp.jsonwith env-var placeholders. - Add a
.env.exampleso teammates know what to set. - Shared Slack/GitHub bot tokens with scoped permissions.
๐ข Enterprise / regulatedโ
- Host MCP servers behind HTTPS + mTLS.
- Tokens injected from a secrets vault (Azure Key Vault, AWS Secrets Manager).
- Add a gateway server that audits every call.
- Whitelist tools per team in
settings.json.
๐ Open-source / communityโ
- Publish your server to npm / PyPI / crates.io.
- Ship a
READMEwith copy-paste.mcp.jsonsnippet. - Provide a Docker image so contributors avoid local installs.
Bottom line: Same protocol, different deployment posture as risk grows.
Anatomy of a Single Unit โ One MCP Serverโ
| Part | Purpose |
|---|---|
| Identity | name, version, description, advertised capabilities |
| Transport | stdio / HTTP / WebSocket |
| Tools | callable functions with input/output JSON Schemas |
| Resources | URI-addressable read-only data |
| Prompts | reusable prompt templates with variables |
| Auth & Security | tokens, scopes, allowed origins |
| Lifecycle | initialize โ ready โ running โ shutdown |
| Error handling | standard MCP error codes for predictable client UX |
| Logging | structured logs for debugging & audit |
Lifecycle Eventsโ
| Event | What happens |
|---|---|
initialize | Client connects, exchanges capabilities |
tools/list | Server returns its callable tools |
tools/call | Client invokes a tool |
notifications/* | Server pushes async events |
shutdown | Graceful close |
terminated | Connection ended |
Common Patterns & Recipesโ
Pattern 1 โ Scoped filesystem accessโ
"filesystem": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@anthropic/mcp-filesystem", "./src", "./tests"]
}
Only those two folders are exposed โ Claude cannot escape.
Pattern 2 โ Read-only databaseโ
"postgres-readonly": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@anthropic/mcp-postgres", "--readonly"],
"env": { "DATABASE_URL": "${RO_DATABASE_URL}" }
}
Pattern 3 โ Remote HTTP server with bearer tokenโ
"analytics": {
"type": "http",
"url": "https://mcp.mycorp.com/analytics",
"headers": { "Authorization": "Bearer ${ANALYTICS_TOKEN}" }
}
Pattern 4 โ Per-environment overrides via settings.local.jsonโ
{ "mcpServers": { "postgres": { "env": { "DATABASE_URL": "postgres://localhost/dev" } } } }
Pattern 5 โ Healthcheck command before Claude startsโ
# scripts/check-mcp.sh
npx -y @anthropic/mcp-github --healthcheck || { echo "MCP github not ready"; exit 1; }
Best Practices โ โ
- Least privilege โ give each server only the scopes it needs.
- Never hard-code secrets โ always
${ENV_VAR}. - Validate all inputs at the server boundary.
- Use scoped tokens (e.g., GitHub fine-grained PATs).
- Audit logs for sensitive actions (writes, deletes).
- Isolate untrusted servers in containers or VMs.
- Fail safely & loudly โ return clear error codes.
Anti-Patterns โโ
- One "god" MCP server with full admin rights.
- Tokens committed to the repo.
- Servers that silently ignore unknown args.
- Exposing the entire filesystem (
/). - No timeout on long-running tool calls.
Troubleshootingโ
| Symptom | Cause | Fix |
|---|---|---|
| "Server not found" | Wrong command or npx not on PATH | Check which npx; install package |
| "No tools listed" | Server never finished initialize | Run server standalone; verify logs |
| Tool call fails | Schema mismatch | Compare args against tools/list schema |
| "Permission denied" | Token scope too narrow | Re-issue with required scopes |
| Random hangs | No timeout configured | Set timeoutMs on the client side |
| Timeouts | Slow upstream (e.g., DB) | Add caching or increase timeout |
| Wrong results | Server reads stale data | Validate data-source mapping |
Starter Server Skeleton (TypeScript, full)โ
// my-mcp-server/src/server.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: "hello-server",
version: "1.0.0",
capabilities: { tools: {}, resources: {}, prompts: {} }
});
server.tool(
"hello",
{ name: z.string() },
async ({ name }) => ({
content: [{ type: "text", text: `Hello, ${name}!` }]
})
);
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP server running on stdio");
Template layout:
my-mcp-server/
โโโ src/server.ts
โโโ tools/*.ts
โโโ schemas/*.json
โโโ utils/*.ts
โโโ tests/*.test.ts
โโโ README.md
โโโ package.json
โโโ tsconfig.json
โโโ .env.example
โโโ Dockerfile # optional
โโโ LICENSE
Security Checklistโ
- Least privilege scopes
- No hard-coded secrets
- Validate all inputs
- Scope tokens per environment
- Audit logs on writes
- Fail safely with standard error codes
Glossaryโ
- MCP โ Model Context Protocol, Anthropic's open spec.
- JSON-RPC 2.0 โ Lightweight remote-procedure-call format.
- Transport โ How JSON-RPC bytes are moved (stdio, HTTP, WS).
- Tool โ A server-exposed function Claude can call.
- Resource โ Read-only data a server exposes via a URI.
- Prompt โ A reusable prompt template provided by the server.
- Capability โ A feature flag exchanged during
initialize.
Further Readingโ
- Search: "Model Context Protocol spec"
- Search: "@modelcontextprotocol/sdk GitHub"
- Search: "awesome MCP servers list"
- Search: "Anthropic MCP quickstart"