MCP Servers

TL;DR

MCP (Model Context Protocol) is a protocol that lets Claude connect to external tools — databases, APIs, Slack, Jira, GitHub, and more. Claude calls tools via MCP servers you configure in settings.json.

Explain Like I'm 12
Think of USB ports on a computer. Your computer does not know in advance what you will plug in — a keyboard, a printer, a game controller — but it can talk to any of them because they all speak USB. MCP is the same idea for Claude. Any tool that "speaks MCP" can plug in, and Claude instantly knows how to use it.

Architecture Diagram

This diagram shows how Claude Code acts as the MCP client, connecting to one or more MCP servers, each of which bridges to an external service.

MCP architecture: Claude Code client connects to multiple MCP servers, each bridging to an external service like GitHub, PostgreSQL, or Slack

What is MCP?

MCP stands for Model Context Protocol. It is an open protocol created by Anthropic that standardizes how AI models connect to external data sources and tools. Before MCP, every integration was a one-off. Now there is a single protocol that any tool can implement.

Key facts about MCP

  • Open standard — anyone can build an MCP server or client
  • Client-server architecture — Claude Code is the client, your configured servers provide the tools
  • JSON-RPC 2.0 — the wire protocol, transported over stdio or Server-Sent Events (SSE)
  • Three primitives — Tools (actions the model can call), Resources (data the model can read), and Prompts (reusable templates)

Think of MCP like an API gateway specifically designed for AI. Instead of hardcoding integrations into the model, you declare which servers are available and the model discovers what each one can do at runtime.

How MCP Works

The flow is straightforward. When Claude Code starts, it reads your MCP server configuration and spawns each server process. The client and server then negotiate capabilities via an initialization handshake.

At runtime, the flow looks like this:

  1. Discovery — Claude asks each server "what tools do you offer?" and gets back a list with names, descriptions, and input schemas
  2. Selection — when you ask Claude a question, it decides which tool (if any) to call based on your request and the tool descriptions
  3. Invocation — Claude sends a JSON-RPC tools/call request to the server with the tool name and arguments
  4. Execution — the MCP server runs the action (queries a database, calls an API, reads a file) and returns the result
  5. Response — Claude incorporates the result into its answer

stdio vs SSE transport

stdio is the default — Claude spawns the server as a child process and communicates over stdin/stdout. Simple, fast, and works locally. SSE (Server-Sent Events) is for remote servers — the MCP server runs on a separate machine and Claude connects over HTTP. Use SSE when the server needs to run on a different host or be shared across users.

Configuring MCP Servers

MCP servers are configured in your settings.json file (project-level .claude/settings.json or user-level ~/.claude/settings.json). The key is mcpServers, and each entry follows the same pattern: a name, a command to spawn the server, optional arguments, and optional environment variables.

{
  "mcpServers": {
    "server-name": {
      "command": "executable",
      "args": ["arg1", "arg2"],
      "env": {
        "API_KEY": "your-key-here"
      }
    }
  }
}

That is the universal shape. Every MCP server follows it. The only things that change are the command, args, and env values.

Where to put secrets

Never hardcode API keys or tokens directly in settings.json if you commit it to version control. Use environment variable references, a .env file loaded by your shell, or the env field pointing to variables already set in your environment. The .claude/settings.json in your project should be in .gitignore if it contains secrets.

Example: Filesystem MCP Server

The filesystem MCP server lets Claude read and write files outside the current project directory. This is useful when you need to access config files, logs, or data stored elsewhere on your machine.

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/Users/you/Documents",
        "/Users/you/Desktop"
      ]
    }
  }
}

The paths in args are the directories the server is allowed to access. Claude can only read and write within those directories — it cannot escape to the rest of your filesystem.

Permission scoping

Each MCP server only exposes the capabilities it is designed for. The filesystem server can only access the directories you explicitly list. This is a core security principle of MCP — servers are sandboxed to their declared scope.

Example: Database MCP Server (PostgreSQL)

Want Claude to query your database directly? The PostgreSQL MCP server exposes tools for running SQL queries, listing tables, and inspecting schemas.

{
  "mcpServers": {
    "postgres": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-postgres",
        "postgresql://localhost:5432/mydb"
      ]
    }
  }
}

Once configured, you can ask Claude things like "show me the top 10 orders from last week" and it will write and execute the SQL query through the MCP server, returning the results directly in your conversation.

Use read-only connections for production

When connecting to production databases, use a read-only database user. MCP servers execute whatever the model asks them to — a DROP TABLE is just as easy to run as a SELECT. Scope the database user's permissions to match what you actually want Claude to do.

Example: GitHub MCP Server

The GitHub MCP server gives Claude access to repositories, issues, pull requests, and more through the GitHub API.

{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-github"
      ],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_your_token_here"
      }
    }
  }
}

With this configured, Claude can create pull requests, read issue details, search repositories, review code, and manage branches — all through natural language commands.

# Now you can ask Claude things like:
# "Create a PR for the changes on the feature/auth branch"
# "List all open issues labeled 'bug'"
# "What changed in PR #42?"

Token permissions

Create a fine-grained personal access token with only the permissions Claude needs. For read-only workflows, grant repo:read and issues:read. For PR creation, add pull_requests:write. Follow the principle of least privilege.

Building a Custom MCP Server

You can build your own MCP server in Python using the FastMCP framework. This is the quickest path to exposing any API or service to Claude.

Python quickstart with FastMCP

from mcp.server.fastmcp import FastMCP

# Create the server
mcp = FastMCP("my-tools")

# Define a tool using the @mcp.tool() decorator
@mcp.tool()
def get_weather(city: str) -> str:
    """Get the current weather for a city."""
    # In real code, call a weather API here
    return f"Weather in {city}: 72°F, sunny"

# Define a resource (read-only data Claude can access)
@mcp.resource("config://app")
def get_app_config() -> str:
    """Return the current application configuration."""
    return "{ \"debug\": false, \"version\": \"2.1.0\" }"

# Run the server
if __name__ == "__main__":
    mcp.run()

The @mcp.tool() decorator turns any function into an MCP tool. The function's docstring becomes the tool description that Claude reads when deciding which tool to call. Type hints define the input schema automatically.

Registering your custom server

{
  "mcpServers": {
    "my-tools": {
      "command": "python",
      "args": ["path/to/my_server.py"]
    }
  }
}

The resource pattern

Resources are read-only data endpoints. Unlike tools (which perform actions), resources expose data that Claude can pull in as context. Use resources for configuration files, documentation, database schemas — anything Claude should be able to read but not modify through the resource itself.

JavaScript/TypeScript alternative

If you prefer Node.js, the official @modelcontextprotocol/sdk package provides the same capabilities:

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: "my-tools",
  version: "1.0.0"
});

// Define a tool
server.tool(
  "get_weather",
  { city: z.string() },
  async ({ city }) => ({
    content: [{
      type: "text",
      text: `Weather in ${city}: 72°F, sunny`
    }]
  })
);

// Connect via stdio
const transport = new StdioServerTransport();
await server.connect(transport);

Security Considerations

MCP servers run code on your machine with your permissions. Treat them like any other dependency — trust matters.

Security checklist

  • Only use trusted servers. An MCP server has access to whatever you grant it — files, databases, APIs. Only install servers from sources you trust.
  • Environment variables for secrets. Never put API keys in plain text in committed files. Use env fields referencing shell variables, or use a secrets manager.
  • Permission scoping. Give each server the minimum access it needs. Filesystem server? Only list the directories it should see. Database server? Use a read-only user.
  • Review server source code. Before installing a third-party MCP server, read the code. Know what it does with your data.
  • Network isolation. If a server does not need internet access, run it in an environment without it. MCP servers over stdio run locally by default.

Debugging MCP Servers

When an MCP server is not working, here is how to diagnose the problem.

Check MCP logs

Claude Code logs MCP server activity. Check the logs for connection errors, failed tool calls, or server crashes:

# MCP logs are in the Claude Code log directory
# Look for connection and initialization messages
claude --verbose

Test with mcp-inspector

The mcp-inspector is a standalone tool that connects to your MCP server and lets you test tools interactively — without involving Claude at all.

# Install and run the inspector
npx @modelcontextprotocol/inspector

# It opens a web UI where you can:
# - See all tools the server exposes
# - Call tools with custom arguments
# - View raw JSON-RPC messages

Common errors and fixes

  • "Server failed to start" — the command in your config is wrong, or the executable is not in your PATH. Try running the command manually in your terminal first.
  • "Tool not found" — the server started but Claude cannot find the tool. Check the tool name matches exactly. Use mcp-inspector to verify what tools are exposed.
  • "Connection closed" — the server crashed mid-conversation. Check the server logs for unhandled exceptions. Add error handling to your custom server code.
  • Timeout errors — the server is taking too long to respond. MCP has a default timeout. For slow operations, consider streaming results or increasing the timeout in your server implementation.
Advertisement

Test Yourself

Q: What does MCP stand for, and who created it?

Model Context Protocol, created by Anthropic. It is an open standard for connecting AI models to external tools and data sources.

Q: Where do you configure MCP servers in Claude Code?

In settings.json, under the "mcpServers" key. Each entry specifies a command, optional args, and optional env.

Q: What are the two transport methods MCP supports?

stdio (local child process, stdin/stdout communication) and SSE (Server-Sent Events over HTTP, for remote servers).

Q: What is the difference between MCP Tools and Resources?

Tools are actions the model can invoke (query a database, create a PR). Resources are read-only data endpoints the model can pull in as context (config files, schemas, documentation).

Q: How do you test an MCP server without involving Claude?

Use npx @modelcontextprotocol/inspector to launch the MCP Inspector. It connects to your server and lets you list tools, call them with custom arguments, and inspect the raw JSON-RPC messages.

Interview Questions

Q: How would you design an MCP server that integrates with a third-party API that has rate limits?

Implement rate limiting inside the MCP server itself. Use a token bucket or sliding window algorithm to track API calls. When the limit is approached, queue requests or return a descriptive error to the model so it can retry later. Expose the rate limit status as a resource so the model can check remaining quota before making a call. Cache responses where possible to reduce API hits.

Q: What are the security implications of running MCP servers, and how would you mitigate them?

MCP servers run with the user's OS permissions, so a malicious server could access files, network, or credentials. Mitigations: (1) only install servers from trusted sources and audit the code, (2) use the principle of least privilege — scope filesystem access, use read-only database users, create fine-grained API tokens, (3) store secrets in environment variables or a secrets manager rather than in config files, (4) for network-sensitive servers, use network isolation or firewall rules, (5) monitor server logs for unexpected behavior.

Q: Explain the MCP protocol lifecycle: what happens from server startup to tool invocation?

The client (Claude Code) spawns the server process and opens a JSON-RPC 2.0 channel (stdio or SSE). First, an initialize handshake negotiates protocol version and capabilities. Then the client sends tools/list to discover available tools (names, descriptions, input schemas). When the user makes a request that needs a tool, the client sends tools/call with the tool name and arguments. The server executes the action and returns the result. The client can also request resources/read for context data. The connection stays open for the session duration, and the server is shut down when the session ends.