Skip to main content

Anatomy of MCP Servers

Model Context Protocol servers, capabilities, security, and setup.

MCP Servers

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.json at 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โ€‹

ConceptPlain English
ClientClaude Code itself
ServerA small program that exposes tools/data
ProtocolJSON-RPC 2.0 messages over a transport
ToolA callable function with a JSON Schema
ResourceRead-only data exposed via a URI
PromptA reusable prompt template the server provides
TransportHow bytes flow: stdio / HTTP / WebSocket
CapabilityA 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โ€‹

TransportWhen to useTradeoff
stdioLocal servers (default)Simple, fast, secure โ€” but one process per client
HTTP / HTTPSRemote / shared serversEasy to deploy & scale, needs auth
WebSocketReal-time, bidirectionalLong-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 before claude launch.

๐Ÿ‘ฅ Small team (2โ€“10)โ€‹

  • Commit .mcp.json with env-var placeholders.
  • Add a .env.example so 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 README with copy-paste .mcp.json snippet.
  • 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โ€‹

PartPurpose
Identityname, version, description, advertised capabilities
Transportstdio / HTTP / WebSocket
Toolscallable functions with input/output JSON Schemas
ResourcesURI-addressable read-only data
Promptsreusable prompt templates with variables
Auth & Securitytokens, scopes, allowed origins
Lifecycleinitialize โ†’ ready โ†’ running โ†’ shutdown
Error handlingstandard MCP error codes for predictable client UX
Loggingstructured logs for debugging & audit

Lifecycle Eventsโ€‹

EventWhat happens
initializeClient connects, exchanges capabilities
tools/listServer returns its callable tools
tools/callClient invokes a tool
notifications/*Server pushes async events
shutdownGraceful close
terminatedConnection 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โ€‹

SymptomCauseFix
"Server not found"Wrong command or npx not on PATHCheck which npx; install package
"No tools listed"Server never finished initializeRun server standalone; verify logs
Tool call failsSchema mismatchCompare args against tools/list schema
"Permission denied"Token scope too narrowRe-issue with required scopes
Random hangsNo timeout configuredSet timeoutMs on the client side
TimeoutsSlow upstream (e.g., DB)Add caching or increase timeout
Wrong resultsServer reads stale dataValidate 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"