# Model Context Protocol

> MCP is a communication standard that lets Claude access pre-built tools, resources, and prompts from specialized servers without you writing integration code. Connect to a GitHub MCP server instead of writing GitHub API tools yourself. Servers are configured in .mcp.json (project) or ~/.claude.json (user).

**Domain:** D2 · Tool Design + Integration (18% of CCA-F exam)
**Canonical:** https://claudearchitectcertification.com/concepts/mcp
**Last reviewed:** 2026-05-04

## Quick stats

- **Primitives:** 3
- **Config levels:** 2
- **Transports:** 3
- **Exam domain:** D2
- **Pre-built sets:** 18+

## What it is

Model Context Protocol (MCP) is a standardized interface for connecting Claude to external systems: files, APIs, databases, web services. Think of it as the USB-C of AI integrations. Instead of each application building custom tools, MCP defines a protocol that any tool provider implements once, and any MCP-enabled client uses immediately. A .mcp.json configuration file lists servers; each server exposes tools, resources, and prompts that Claude can invoke.

Two server categories exist: installed (community-maintained or custom, running locally) and cloud-hosted (managed by the vendor, authenticated via env vars). A local server might expose Grep, Read, Bash in the same process as your app. A cloud server (e.g. GitHub MCP) runs on the vendor's infra and authenticates via your token. Both appear in Claude's tool list identically; the distinction is who maintains the code.

The architecture is asymmetric: Claude (the client) sends tool_use blocks; the MCP server receives the request, executes the tool, and returns a result. Claude never talks directly to an external API, the server acts as a proxy. This isolation prevents token leakage (Claude never sees raw credentials), enables caching (server-side response cache), and allows request validation (the server can reject malformed calls before forwarding).

Production failures stem from stale .mcp.json configuration, mismatched env vars, vague tool descriptions inherited from old integrations, and forgetting that the server owns error handling. The JSON file is version-controlled; if a teammate updates GitHub's API and doesn't re-run the MCP setup, their tools silently fail. Errors must bubble to Claude as structured data, not be swallowed by the server.

## How it works

At startup, your app loads .mcp.json. Each entry specifies a server: installed (local executable) or cloud (managed remote). Installed servers are spawned as child processes; cloud servers are contacted via HTTP with credential headers. Each server is interrogated: "what tools do you expose?" The server responds with names, descriptions, and JSON schemas. All tools merge into a single namespace; Claude sees them as a unified set.

When Claude's loop produces a tool_use block (e.g. {name: "Grep", input: {query: "..."}}), the MCP framework routes the request to the server that owns that tool. The server's code runs: validates input, executes the operation, returns a result. The result is wrapped in a tool_result block and appended to the message list. Claude never knows where the tool ran or what authentication was used.

Each server can expose resources: catalogs of data Claude can query without invoking a tool. A GitHub MCP server might expose a resource listing all open issues; Claude inspects that catalog to decide which tools to call. Resources reduce exploratory tool calls: instead of calling list_issues 20 times, Claude reads the resource once and calls targeted tools. Resources are read-only caches; tools are the write path.

Error handling flows back to Claude via tool_result. If the server hits an API error (timeout, 403, invalid request), it returns a structured message in content: {"error": "github_api_timeout", "hint": "retry after 30s"}. Claude reads the error and adjusts. Silent error suppression (returning empty as success) is the #1 production failure: Claude doesn't know the tool failed and re-requests indefinitely.

## Where you'll see it in production

### Team-shared GitHub integration

Project uses GitHub MCP server: PR queries, issue lists, code search, all pre-built. Configure in .mcp.json (committed to repo). Team members run claude mcp sync once. Replaces hand-rolled tool definitions and auth wrappers; tokens come from team env vars.

### Custom MCP server for legacy billing

Company has a Unix-only billing API with no SDK. Build a Python MCP server wrapping the API; expose via stdio in .mcp.json. Agent calls standard MCP tools; the server hides the legacy weirdness. New team members inherit the integration automatically.

### Resources for upfront schema discovery

Database MCP exposes a resources endpoint listing all tables and columns. Agent reads the resource ONCE at session start instead of calling list_tables / describe_table N times. Cuts exploratory tool calls from 8 → 0 per session.

## Code examples

### .mcp.json, team-shared MCP configuration

```json
{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"
      }
    },
    "postgres": {
      "command": "uvx",
      "args": ["mcp-server-postgres", "--readonly"],
      "env": {
        "DATABASE_URL": "${DATABASE_URL}"
      }
    },
    "billing-legacy": {
      "command": "python3",
      "args": ["./mcp-servers/billing.py"],
      "env": {
        "BILLING_API_HOST": "${BILLING_API_HOST}",
        "BILLING_API_KEY": "${BILLING_API_KEY}"
      }
    }
  }
}

```

> Env-var expansion (${VAR}) keeps secrets out of the committed file. Team members run claude mcp sync to install all servers consistently.

### Custom MCP server skeleton (FastMCP)

```python
# Minimal MCP server using the FastMCP Python library.
# Run: python billing_server.py
# Configure in .mcp.json with command: python3, args: [path/to/billing_server.py]

from mcp.server.fastmcp import FastMCP
import os

mcp = FastMCP("billing-legacy")

@mcp.tool()
def get_invoice(invoice_id: str) -> dict:
    """Fetch invoice by ID from the legacy billing API.

    Use when a customer asks for invoice details. Returns
    {invoice_id, customer_id, amount, status, line_items}.
    """
    api_host = os.environ["BILLING_API_HOST"]
    # ... call legacy API, return result
    return {"invoice_id": invoice_id, "amount": 247.83, "status": "paid"}

@mcp.tool()
def issue_credit(invoice_id: str, amount: float, reason: str) -> dict:
    """Issue a credit note against an invoice. Max amount $500
    without manager approval. Returns {credit_id, status}.
    """
    if amount > 500:
        return {"status": "blocked", "reason": "exceeds_policy"}
    # ... call legacy API
    return {"credit_id": "CR-9999", "status": "issued"}

@mcp.resource("billing://schema")
def get_schema() -> str:
    """Returns the billing data dictionary so the agent doesn't
    need exploratory tool calls. Read once per session."""
    return open("billing_schema.md").read()

if __name__ == "__main__":
    mcp.run()

```

> @mcp.tool decorators expose functions as MCP tools. @mcp.resource exposes read-only data so the agent loads schema once instead of calling list_* repeatedly.

## Looks-right vs actually-wrong

| Looks right | Actually wrong |
|---|---|
| MCP and tool-use are competing approaches; pick one. | MCP is infrastructure (who provides tools); tool-use is the mechanism (Claude calls them). They're complementary, every MCP tool fires through the tool-use protocol. |
| Hardcode API tokens in .mcp.json so the team has a single source of truth. | .mcp.json is committed to git. Hardcoded secrets leak. Use ${ENV_VAR} expansion; secrets stay in CI/CD or local .env.local files. |
| Expose 30 tools in one MCP server for completeness. | Same 18-tool degradation applies. Scope MCP servers to focused domains (one per service). Use 'resources' to expose data upfront and avoid forcing N exploratory tool calls. |
| MCP servers run inside Claude's process, so they can read in-memory state from the host app. | MCP servers run as separate processes (stdio child processes for local servers, remote HTTP for cloud). The protocol is JSON-RPC over a stream; there's no shared memory. Anything the server needs must come through tool inputs or env vars, this isolation is the security boundary, not a bug. |
| An MCP server is a one-time setup, just install it and forget. | MCP servers have a capability handshake on every client connection: tools, resources, and prompts are re-fetched. If you upgrade the server (new tool added) without restarting clients, those clients never see the new tool. Restart the Claude Code session or run claude mcp sync after server-side changes. |

## Comparison

| Aspect | MCP server | Custom in-app tools | SDK built-ins |
| --- | --- | --- | --- |
| Setup effort | Low (config in .mcp.json) | Medium (write tool defs in code) | Zero (Read/Edit/Bash) |
| Reusability | High (any Claude client) | Per-app | Built into Claude Code |
| Best for | Standard integrations | Bespoke business logic | File ops + shell |
| Auth | Env-var expansion | Per-app config | Inherits user permissions |
| Transport | stdio (child process) or HTTP/SSE (remote) | In-process function calls | In-process; OS-level for Bash |
| Failure isolation | Server crash isolated; client sees tool_error | Crash takes down the whole agent | Bash failures bubble; Read/Edit are safe |

## Decision tree

1. **Is this integration with a known service (GitHub, Slack, Postgres, etc.)?**
   - **Yes:** Search for an existing MCP server. Almost always saves work over a custom build.
   - **No:** Go to next question.

2. **Does the team need to share this integration?**
   - **Yes:** Put config in .mcp.json (project root, committed). Use ${ENV_VAR} for secrets.
   - **No:** Use ~/.claude.json (personal, not committed).

3. **Will the agent need exploratory schema/data lookups?**
   - **Yes:** Expose a 'resources' endpoint on your MCP server. Read once, save N exploratory tool calls.
   - **No:** Tools alone are sufficient.

4. **Is the integration internet-facing (third-party API) or internal (intranet/local)?**
   - **Yes:** Prefer HTTP/SSE transport with a hosted MCP server, lets ops control rate limits, observability, and rotation.
   - **No:** Use stdio transport (local child process). Lower latency and simpler for desktop / dev workflows.

5. **Will multiple Claude Code users hit the same backend simultaneously?**
   - **Yes:** Run a shared HTTP MCP server behind a load balancer. stdio servers are per-process and won't pool connections, leading to resource exhaustion at scale.
   - **No:** stdio is fine; one process per session.

## Exam-pattern questions

### Q1. You add a new MCP server to .mcp.json but Claude Code doesn't see its tools. What did you forget?

Restart the Claude Code session. The framework caches the tool list at startup. Changes to .mcp.json require a restart, or a manual mcp restart command in newer versions.

### Q2. Two MCP servers expose tools with the same name (Read). What happens?

The config is invalid. Tool names must be globally unique across all MCP servers. Prefix tools by server name (GithubRead, FileRead) or disable one of the conflicting servers.

### Q3. A cloud MCP server returns {error: "rate_limited"}. Your agent retries 5 times and exhausts budget. Better approach?

Server should return structured rate-limit info: {error: "rate_limited", retry_after: 30}. Your harness reads this and waits before retrying. Don't hammer; respect the hint.

### Q4. You hardcoded a GitHub token in .mcp.json. A teammate clones the repo and your token leaks. Fix?

Use ${GITHUB_TOKEN} env-var expansion. Each developer sets their own token in their environment. The .mcp.json is committed; the token is not.

### Q5. An MCP server resource lists 1000 items. Claude calls a tool 1000 times, one per item. Why?

The resource is a catalog (read-only data Claude can scan), not a list of pre-staged actions. Reduce tool calls by structuring the resource so Claude can scan and pick targeted items, not iterate.

### Q6. Your custom MCP server crashes when Claude calls a tool with malformed JSON. What's the right error handling?

Wrap tool execution in try/except. Return {error: "invalid_input", detail: "..."} as the tool_result. Claude reads it and retries with corrected input. Crashes leave the framework hanging.

### Q7. You want to add a custom internal API. Should you build a custom MCP server or expose tools directly via the SDK?

MCP for shared/team use (multiple developers, multiple Claude sessions). Direct SDK tools for app-specific custom logic. MCP adds a small protocol overhead but enables reuse.

### Q8. An MCP server's tool description is auto-generated from the underlying API and reads like docstring noise. The model misroutes. Fix?

Override the description in your MCP server config. The vendor's auto-generated description may be too generic. Write a 4-line description with what/when/edge-cases/boundaries.

## FAQ

### Q1. What's the difference between MCP resources and tools?

Resources are read-only data the model browses; tools are actions the model invokes. Resources have URIs (billing://schema); tools have JSON-Schema inputs. Use resources for catalogs/schemas/manifests; use tools for queries and mutations.

### Q2. Can two MCP servers expose tools with the same name?

Yes, but Claude Code prefixes them: github__create_issue vs gitlab__create_issue if both expose create_issue. Name collisions are namespaced, so there's no hidden override. Verify via claude mcp list to see the resolved tool names.

### Q3. How does authentication work for cloud MCP servers?

Most use OAuth 2.1 with PKCE or bearer tokens via env vars. The server ingests the token at connection time (e.g., GITHUB_PERSONAL_ACCESS_TOKEN) and uses it for downstream API calls. Claude never sees the token, that's the whole point of the proxy architecture.

### Q4. Can I run an MCP server in my Convex / Vercel deployment?

Yes for HTTP MCP servers, no for stdio. Convex / Vercel functions are stateless HTTP endpoints, perfect for transport: "http" MCP servers. Stdio assumes a long-lived child process, which serverless platforms don't support.

### Q5. What happens when an MCP server crashes mid-session?

The MCP framework returns a structured error to Claude (tool_error block) and attempts reconnection on the next tool call. Claude sees the error and can adjust strategy. Without graceful error handling on your side, repeated crashes look like "the tool just stopped working."

### Q6. How do I scope MCP servers to specific projects?

Place .mcp.json at the project root and Claude Code only loads it for that project. Personal servers go in ~/.claude.json and apply globally. Mixing scopes (project + personal both define github) merges with project taking precedence.

### Q7. Are MCP tool calls cached by prompt caching?

The tool definitions advertised by the server can be cached as part of the tools array (mark with cache_control). The tool results are not cached, they're dynamic per-call. MCP servers can implement their own response cache, separate from prompt caching.

### Q8. What's the cost difference between an MCP tool and a custom tool?

Identical from Claude's API billing perspective, both are tool calls. The cost difference is engineering time (MCP saves it via reuse) and hosting cost (HTTP MCP servers add infra). Stdio MCP servers are free to run.

### Q9. Can MCP servers receive context (e.g., the user's request) for better tool routing?

No, by design. Tools are stateless, the server only sees the input dict for the call it's executing. If you need request-aware behavior, embed the context in the tool input. The model's job is to pass the right context; the server's job is to execute deterministically.

### Q10. How do I debug 'tool not appearing in the agent's tool list'?

Three checks: (1) claude mcp list shows the server is connected, (2) the server's capability handshake completed (look for tool definitions in the connect log), (3) the .mcp.json is at project root, not buried in a subdirectory. Restart the Claude Code session after .mcp.json edits, the file is read at startup.

---

**Source:** https://claudearchitectcertification.com/concepts/mcp
**Vault sources:** ACP-T03 §4.2; GAI-K04 §13; ASC-A01 Courses 7 + 10
**Last reviewed:** 2026-05-04

**Evidence tiers** — 🟢 official Anthropic doc / API contract · 🟡 partial doc / inferred · 🟠 community-derived · 🔴 disputed.
