Skip to content

Chapter 7: MCP — Connect Claude to Everything ​

Appendix links: A06 MCP Protocol Deep Dive covers the wire protocol, capability negotiation, transport layers, and building production-grade MCP servers.

What You'll Build ​

Three real MCP integrations that extend Claude beyond the filesystem:

  1. GitHub MCP — Analyze your own repos, triage issues, review PRs, all without leaving Claude
  2. Context7 MCP — Look up live documentation for any library, always current, never stale
  3. Playwright MCP — Automate browser testing, take screenshots, verify your deployed app works

These are the same MCP servers used in Everything Claude Code's .mcp.json config. Not theoretical — this is how production teams wire Claude into their toolchain.

How MCP Works ​

MCP (Model Context Protocol) is a standardized interface between Claude and external tools. Think of it as USB for AI — any tool that speaks MCP can plug into Claude and become a native tool.

Claude Code
  |
  |-- Built-in: Read, Write, Edit, Bash, Glob, Grep
  |
  +-- MCP Layer
       |-- GitHub Server     --> PRs, issues, branches, code search
       |-- Context7 Server   --> Live docs for any library
       |-- Playwright Server --> Browser automation, screenshots
       |-- Exa Server        --> Web search with citations
       |-- Memory Server     --> Cross-session knowledge graph
       |-- Your custom server --> Whatever you need

Why MCP Instead of Bash? ​

Without MCP, Claude can still call gh issue list via Bash. MCP is better because:

  • Type safety: MCP tools have JSON Schema definitions. Claude knows exactly what parameters are valid.
  • Process isolation: Each MCP server runs in its own process. A buggy tool can't crash Claude.
  • Semantic clarity: Claude understands what mcp__github__list_issues does at a deeper level than parsing gh CLI output.
  • Permission control: You can allow GitHub MCP tools while blocking raw gh commands.

Tool Naming ​

All MCP tools follow the pattern mcp__<server>__<tool>:

mcp__github__list_issues       -- GitHub server, list_issues tool
mcp__context7__query-docs      -- Context7 server, query-docs tool
mcp__playwright__navigate      -- Playwright server, navigate tool

This naming makes them easy to target in hooks:

json
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "mcp__github__.*",
      "hooks": [{ "type": "command", "command": "echo 'GitHub API call'" }]
    }]
  }
}

Configuration ​

Two ways to add MCP servers:

CLI (recommended):

bash
claude mcp add github -e GITHUB_PERSONAL_ACCESS_TOKEN -- npx -y @modelcontextprotocol/server-github

Manual .mcp.json:

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

Scope ​

ScopeFileBest For
user~/.claude/.mcp.jsonTools you use everywhere (GitHub, Memory)
project.mcp.json in project rootProject-specific tools (commit to Git)

Security rule: Never hardcode tokens. Use environment variables:

bash
# In your .bashrc or .zshrc
export GITHUB_PERSONAL_ACCESS_TOKEN=ghp_xxx

# Then reference it
claude mcp add github -e GITHUB_PERSONAL_ACCESS_TOKEN -- npx -y @modelcontextprotocol/server-github

ECC's .mcp.json Reference ​

Here's what a production .mcp.json looks like, based on ECC's config:

json
{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "" }
    },
    "context7": {
      "command": "npx",
      "args": ["-y", "@anthropic-ai/context7-mcp"]
    },
    "playwright": {
      "command": "npx",
      "args": ["-y", "@anthropic-ai/claude-code-playwright"]
    },
    "memory": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-memory"]
    },
    "exa": {
      "command": "npx",
      "args": ["-y", "exa-mcp-server"],
      "env": { "EXA_API_KEY": "" }
    }
  }
}

You don't need all of them. Start with GitHub and Context7 — they cover 80% of real use cases.


Demo 18: GitHub MCP — Manage Your Repos from Claude ​

18
GitHub MCP Integration
Intermediate~15 min

The Problem ​

You're working on a project and need to check open issues, find stale PRs, or review recent changes across multiple repos. Right now, that means switching to a browser, clicking around GitHub, losing context. With GitHub MCP, Claude does it all inline.

Set Up ​

1. Create a GitHub Token ​

Go to github.com/settings/tokens?type=beta and create a fine-grained token with:

  • Repository access: Select your repos (or all)
  • Permissions: Issues (R/W), Pull Requests (R/W), Contents (R/W)

Save it:

bash
# Add to your shell profile
echo 'export GITHUB_PERSONAL_ACCESS_TOKEN=ghp_your_token_here' >> ~/.bashrc
source ~/.bashrc

2. Add the Server ​

bash
claude mcp add github -e GITHUB_PERSONAL_ACCESS_TOKEN -- npx -y @modelcontextprotocol/server-github

Verify:

bash
claude mcp list
# Expected: github: npx -y @modelcontextprotocol/server-github (stdio)

3. Use It on Your Real Repos ​

bash
mkdir -p ~/claude-demos/demo-18 && cd ~/claude-demos/demo-18
git init
claude

Here's where it gets useful. Use your actual repositories:

Triage open issues:

List all open issues in <your-username>/<your-repo>, sorted by most recent. 
For each one, summarize the problem in one sentence.
terminal
> List all open issues in myuser/web-app, sorted by most recent.
  For each one, summarize the problem in one sentence.

  ● mcp__github__list_issues
    owner: "myuser"
    repo: "web-app"
    state: "open"
    sort: "created"
    direction: "desc"

  Allow? [y/n/a]: y

  ✔ 4 open issues found:

  #23 (May 25) — Dashboard chart fails to render when date range
       spans a DST transition
  #21 (May 22) — Login page shows blank screen on Safari 17.4
  #18 (May 15) — CSV export truncates rows after 10,000 entries
  #12 (Apr 30) — Dark mode toggle does not persist across sessions

Find stale PRs:

Find all open PRs in <your-username>/<your-repo> that haven't been updated 
in the last 14 days. List them with the author and last activity date.

Review a specific PR:

Get the files changed in PR #42 of <your-username>/<your-repo>. 
Summarize what the PR does and flag any concerns.

Create an issue from a finding:

I found a SQL injection vulnerability in src/api/users.js:10. Create a 
GitHub issue for it with appropriate labels, referencing the exact code location.

What Just Happened? ​

1
mcp__github__list_issues
myuser/web-app
↓
2
Claude formatting
Issue list

Key takeaways:

  • The MCP server handled authentication automatically using your environment variable
  • Claude received structured JSON, not raw CLI output, so parsing was reliable
  • You approved the tool call just like any built-in tool (Read, Bash, etc.)
  • The MCP server runs as a child process -- it started when Claude launched and stops when you exit

What Happens Under the Hood ​

You: "List open issues in myuser/myrepo"
  |
  v
Claude selects: mcp__github__list_issues
  |
  v
Claude Code ---(stdio)---> GitHub MCP Server ---(HTTPS)---> GitHub API
  |
  v
GitHub returns JSON array of issues
  |
  v
Claude formats and presents the results

The MCP server runs as a child process of Claude Code. It starts automatically when you begin a session and stops when you exit.

Real-World Patterns ​

Morning standup context: Combine with a SessionStart hook to automatically show your assigned issues and PR review requests.

Issue-driven development: Ask Claude to read an issue, then implement the fix, then create a PR linking to the issue — all without leaving the terminal.

Cross-repo search: Search for code patterns across multiple repos using mcp__github__search_code.


Demo 19: Context7 MCP — Always-Current Documentation ​

19
Context7 MCP for Live Docs
Intermediate~10 min

The Problem ​

You're using a library and need to check the API for a specific function. Claude's training data might be months old. The docs might have changed. You could open a browser and search, or you could ask Claude to look it up in real-time.

Context7 is a documentation server that fetches live docs for thousands of libraries. It's listed in ECC's recommended MCP config.

Set Up ​

bash
claude mcp add context7 -- npx -y @anthropic-ai/context7-mcp

Verify:

bash
claude mcp list
# Should show: context7: npx -y @anthropic-ai/context7-mcp (stdio)

Use It ​

bash
cd ~/claude-demos/demo-18
claude

Look up current API docs:

I'm using Drizzle ORM with PostgreSQL. Look up the current documentation for 
defining a schema with relations. Show me the exact API for one-to-many relationships.
terminal
> Look up the current Drizzle ORM docs for one-to-many relationships.

  ● mcp__context7__resolve-library-id
    libraryName: "drizzle-orm"
    → Resolved: /drizzle-team/drizzle-orm (confidence: 0.97)

  ● mcp__context7__query-docs
    libraryId: "/drizzle-team/drizzle-orm"
    query: "one-to-many relations schema definition"

  ✔ Documentation retrieved (2,847 tokens):

  ## Relations in Drizzle ORM

  To define a one-to-many relation, use the `relations()` helper:

  ```typescript
  import { relations } from 'drizzle-orm';
  import { pgTable, serial, text, integer } from 'drizzle-orm/pg-core';

  export const users = pgTable('users', {
    id: serial('id').primaryKey(),
    name: text('name'),
  });

  export const posts = pgTable('posts', {
    id: serial('id').primaryKey(),
    authorId: integer('author_id').references(() => users.id),
    title: text('title'),
  });

  export const usersRelations = relations(users, ({ many }) => ({
    posts: many(posts),
  }));
  ...

Claude calls mcp__context7__resolve-library-id to find the Drizzle docs, then mcp__context7__query-docs to fetch the relevant section.

Check for breaking changes:

Look up the Tailwind CSS v4 docs. What changed in the configuration format 
compared to v3? I need to know if my tailwind.config.js needs updating.

Get the latest API for a function you're using:

Look up the current Next.js docs for the App Router's generateMetadata function. 
I need the TypeScript types and all available fields.

What Just Happened? ​

1
mcp__context7__resolve-library-id
drizzle-orm
↓
2
mcp__context7__query-docs
/drizzle-team/drizzle-orm

Key takeaways:

  • Two-step resolution: first find the library, then query its docs
  • The docs returned are live and current, not from Claude's training data
  • Context7 returns focused sections, not entire documentation sites, saving context tokens

Why This Beats Training Data ​

Claude's training data has a cutoff. Libraries ship new versions constantly. Context7 fetches the current documentation, so you get:

  • Accurate API signatures (not deprecated ones)
  • Current configuration formats
  • Latest migration guides
  • Recently added features

Pro Tip: Combine with /review ​

Add this to your /review skill instructions:

markdown
When reviewing code that uses third-party libraries, use Context7 to verify 
that the API usage is current and follows recommended patterns.

Now your code reviews catch outdated API usage automatically.


Demo 20: Playwright MCP — Browser Automation ​

20
Playwright MCP for Browser Testing
Intermediate~15 min

The Problem ​

You've deployed your app. Is it actually working? Does the login flow complete? Are there console errors? You could open a browser and click through manually, or have Claude do it and report back with screenshots.

Set Up ​

bash
# Install the MCP server
claude mcp add playwright -- npx -y @anthropic-ai/claude-code-playwright

# Install browser engines (first time only)
npx playwright install chromium

Build a Test Page ​

bash
mkdir -p ~/claude-demos/demo-20 && cd ~/claude-demos/demo-20

cat > index.html << 'EOF'
<!DOCTYPE html>
<html>
<head>
  <title>Task Manager</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: system-ui, sans-serif; max-width: 600px; margin: 40px auto; padding: 0 20px; }
    h1 { margin-bottom: 20px; }
    .add-form { display: flex; gap: 8px; margin-bottom: 20px; }
    .add-form input { flex: 1; padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px; }
    .add-form button { padding: 8px 16px; background: #2563eb; color: white; border: none; border-radius: 6px; cursor: pointer; }
    .task { display: flex; align-items: center; gap: 8px; padding: 12px; border-bottom: 1px solid #eee; }
    .task.done span { text-decoration: line-through; color: #999; }
    .task input[type=checkbox] { width: 18px; height: 18px; }
    .task span { flex: 1; }
    .task button { background: none; border: none; color: #ef4444; cursor: pointer; font-size: 18px; }
    #count { color: #666; margin-top: 12px; }
    #error { color: #ef4444; margin-top: 8px; display: none; }
  </style>
</head>
<body>
  <h1>Task Manager</h1>
  <div class="add-form">
    <input type="text" id="taskInput" placeholder="Add a task..." />
    <button onclick="addTask()">Add</button>
  </div>
  <div id="tasks"></div>
  <div id="count"></div>
  <div id="error"></div>

  <script>
    let tasks = [];
    function addTask() {
      const input = document.getElementById('taskInput');
      const text = input.value.trim();
      if (!text) {
        document.getElementById('error').style.display = 'block';
        document.getElementById('error').textContent = 'Task cannot be empty';
        return;
      }
      document.getElementById('error').style.display = 'none';
      tasks.push({ text, done: false });
      input.value = '';
      render();
    }
    function toggleTask(i) { tasks[i].done = !tasks[i].done; render(); }
    function deleteTask(i) { tasks.splice(i, 1); render(); }
    function render() {
      const container = document.getElementById('tasks');
      container.innerHTML = tasks.map((t, i) =>
        `<div class="task ${t.done ? 'done' : ''}">
          <input type="checkbox" ${t.done ? 'checked' : ''} onchange="toggleTask(${i})" />
          <span>${t.text}</span>
          <button onclick="deleteTask(${i})">x</button>
        </div>`
      ).join('');
      const remaining = tasks.filter(t => !t.done).length;
      document.getElementById('count').textContent = `${remaining} task${remaining !== 1 ? 's' : ''} remaining`;
    }
    document.getElementById('taskInput').addEventListener('keypress', e => { if (e.key === 'Enter') addTask(); });
    render();
  </script>
</body>
</html>
EOF

Start the server:

bash
python -m http.server 8765 &

Use It ​

bash
claude

Visual smoke test:

Navigate to http://localhost:8765/ and take a screenshot. Tell me if the page 
renders correctly and what UI elements you see.
terminal
> Navigate to http://localhost:8765/ and take a screenshot.

  ● mcp__playwright__navigate
    url: "http://localhost:8765/"
    → Page loaded (200 OK, 1.2s)

  ● mcp__playwright__screenshot
    → Screenshot captured (1280x720)

  The page renders correctly. I can see:
  - A heading: "Task Manager"
  - An input field with placeholder "Add a task..."
  - An "Add" button (blue, rounded)
  - A counter showing "0 tasks remaining"
  - No console errors detected

Functional test:

On http://localhost:8765/, test the full task workflow:
1. Add a task called "Deploy to staging"
2. Add a task called "Write unit tests"
3. Check off "Deploy to staging"
4. Take a screenshot showing the current state
5. Tell me if the remaining count is correct
terminal
> Test the full task workflow...

  ● mcp__playwright__fill
    selector: "#taskInput"
    value: "Deploy to staging"

  ● mcp__playwright__click
    selector: ".add-form button"
    → Task added

  ● mcp__playwright__fill
    selector: "#taskInput"
    value: "Write unit tests"

  ● mcp__playwright__click
    selector: ".add-form button"
    → Task added

  ● mcp__playwright__click
    selector: ".task:first-child input[type=checkbox]"
    → Checkbox toggled

  ● mcp__playwright__screenshot
    → Screenshot captured (1280x720)

  ✔ Results:
  - "Deploy to staging" shows with strikethrough (checked off)
  - "Write unit tests" is unchecked
  - Counter reads "1 task remaining" — correct
  - All UI interactions worked as expected

Error handling test:

Try to add an empty task (click Add without typing anything). 
Take a screenshot — does it show an error message?

Report:

Give me a QA report for http://localhost:8765/ covering:
- Does the page load correctly?
- Can you add tasks?
- Does checking/unchecking work?
- Does the counter update?
- Does error handling work for empty input?
Include screenshots for each test.

What Just Happened? ​

1
mcp__playwright__navigate
http://localhost:8765/
↓
2
mcp__playwright__fill
#taskInput
↓
3
mcp__playwright__click
.add-form button
↓
4
mcp__playwright__screenshot
Full page

Key takeaways:

  • Claude controlled a real browser, not a simulation -- these are actual DOM interactions
  • The screenshot gives Claude visual confirmation, not just DOM state
  • Each Playwright action waited for the element to be ready before interacting
  • The full workflow (navigate, fill, click, screenshot) mirrors what a QA engineer would do manually

Cleanup ​

bash
kill %1 2>/dev/null  # Stop the HTTP server

Real-World Applications ​

  • Post-deploy verification: After git push, have Claude check if your staging URL is up and working
  • Visual regression: Take screenshots before and after a CSS change, have Claude compare them
  • E2E smoke tests: Run a full user flow (login, create item, verify) on your deployed app
  • Competitive analysis: Navigate to a competitor's product and take notes on their UX patterns

ECC's /e2e-testing skill uses Playwright MCP with the Page Object Model pattern for structured, repeatable browser tests.


When Things Go Wrong ​

MCP Server Crash ​

The server process exits unexpectedly during a session:

terminal
> List open issues in myuser/web-app

  ● mcp__github__list_issues
    ⚠ Error: MCP server 'github' is not connected.
    The server process exited with code 1.

Diagnosis and fix:

bash
# 1. Check if the server can start at all
npx -y @modelcontextprotocol/server-github 2>&1
# If you see "Error: GITHUB_PERSONAL_ACCESS_TOKEN is not set", the env var is missing

# 2. Verify the environment variable is exported
echo $GITHUB_PERSONAL_ACCESS_TOKEN
# Should print your token. If empty, re-export it:
export GITHUB_PERSONAL_ACCESS_TOKEN=ghp_your_token_here

# 3. Restart Claude to reconnect all MCP servers
# Exit with Ctrl+C, then:
claude

MCP servers restart automatically when you start a new Claude session. If one crashes mid-session, exit and re-enter.

Token Expired or Revoked ​

GitHub returns 401 errors through the MCP server:

terminal
> Create an issue in myuser/web-app

  ● mcp__github__create_issue
    ⚠ Error: GitHub API returned 401 Unauthorized
    {"message":"Bad credentials","documentation_url":"https://docs.github.com/rest"}

Fix:

  1. Go to github.com/settings/tokens
  2. Check if your token is expired or revoked
  3. Generate a new token with the required permissions
  4. Update your shell profile and re-export:
    bash
    export GITHUB_PERSONAL_ACCESS_TOKEN=ghp_new_token_here
  5. Restart Claude (MCP servers read env vars at startup)

Server Timeout ​

A slow network or overloaded API causes the MCP call to hang:

terminal
> Look up the Drizzle ORM docs for relations

  ● mcp__context7__resolve-library-id
    libraryName: "drizzle-orm"
    ... (waiting)
    ⚠ Error: MCP call timed out after 30s

Common causes:

  • Corporate proxy or VPN blocking outbound connections from npx child processes
  • The upstream API (GitHub, Context7) is experiencing downtime
  • DNS resolution failure in restricted network environments

Fix:

bash
# Test connectivity directly
curl -s https://api.github.com/rate_limit | head -5

# If behind a proxy, ensure npx inherits proxy settings
export HTTPS_PROXY=http://your-proxy:8080

Context7 Missing Docs for a Library ​

Not all libraries are indexed. When Context7 cannot find documentation:

terminal
> Look up the docs for my-obscure-library

  ● mcp__context7__resolve-library-id
    libraryName: "my-obscure-library"
    → No matching library found (confidence: 0.0)

  I could not find documentation for "my-obscure-library" in Context7.
  This library may not be indexed yet. I can try alternative approaches:
  - Check the npm/PyPI registry for a README
  - Read the source code directly if it is installed locally
  - Search GitHub for the repository

Workaround: When Context7 does not have docs, Claude falls back to its training data or uses Bash to query package registries. You can also file a request to add the library to Context7's index.


Building Custom MCP Servers ​

When off-the-shelf servers don't cover your needs, build your own.

TypeScript (using the official SDK) ​

typescript
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: "internal-api", version: "1.0.0" });

server.tool(
  "query_deployments",
  "Get deployment status for a service",
  {
    service: z.string().describe("Service name, e.g. auth-api, web-app"),
    environment: z.enum(["staging", "production"]).default("staging"),
  },
  async ({ service, environment }) => {
    const res = await fetch(
      `https://deploy.internal.co/api/status/${service}?env=${environment}`,
      { headers: { Authorization: `Bearer ${process.env.DEPLOY_TOKEN}` } }
    );
    const data = await res.json();
    return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
  }
);

const transport = new StdioServerTransport();
await server.connect(transport);

Register it:

bash
claude mcp add internal-api -e DEPLOY_TOKEN -- npx tsx internal-mcp.ts

Python (using FastMCP) ​

python
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("internal-api")

@mcp.tool()
def search_logs(service: str, query: str, hours: int = 1) -> str:
    """Search application logs by service and query string."""
    # Your log search logic here
    return f"Found 42 matches for '{query}' in {service} (last {hours}h)"

if __name__ == "__main__":
    mcp.run(transport="stdio")
bash
claude mcp add internal-api -- python internal_mcp.py

Debugging MCP ​

When things don't work:

bash
# 1. Check server list
claude mcp list

# 2. Test the server manually (see error output)
npx -y @modelcontextprotocol/server-github 2>&1

# 3. In a Claude session, check available tools
# Type: /tools

# 4. Force a tool call to test
# Ask: "Call mcp__github__list_issues for owner/repo"

Common issues:

ProblemCauseFix
Tools don't appearServer didn't startCheck npx can run the package
"Permission denied"Missing tokenVerify environment variable is set
TimeoutSlow network/serverCheck connectivity, increase timeout
"Server disconnected"Process crashedCheck MCP logs in ~/.claude/logs/

Exercise: Multi-MCP Workflow ​

Set up GitHub + Context7 MCP servers, then run this workflow:

  1. Use GitHub MCP to list your repo's open issues
  2. Pick one that involves a library you use
  3. Use Context7 to look up the current docs for that library
  4. Have Claude propose a fix based on the current documentation
  5. Use GitHub MCP to create a branch and PR with the fix

This is the workflow ECC teams use daily — Claude as a full-loop development partner, from issue triage to PR creation.

Success Criteria ​

  • [ ] Both MCP servers are configured and responding
  • [ ] GitHub MCP can list issues and create PRs
  • [ ] Context7 returns current documentation (not stale training data)
  • [ ] Claude combines both sources to produce a real fix
  • [ ] Tokens are stored in environment variables, not hardcoded

Knowledge Check ​

Why is MCP preferred over using the gh CLI via Bash for GitHub operations?
MCP is faster because it avoids spawning a shell process
MCP provides type-safe JSON Schema definitions, process isolation, and granular permission control
MCP works offline while the gh CLI requires internet access
MCP supports more GitHub features than the gh CLI
You added a GitHub MCP server but its tools do not appear when you type /tools. What is the most likely cause?
The .mcp.json file has a syntax error
The MCP server process failed to start, likely due to a missing environment variable or npx failure
You need to restart your computer for MCP changes to take effect
MCP tools only appear after you make your first API call
Context7 uses a two-step process: resolve-library-id then query-docs. Why not combine them into one call?
It is a limitation of the MCP protocol that tools can only do one thing
Separation lets Claude verify it found the right library before querying, avoiding wasted tokens on wrong docs
The two steps run on different servers for load balancing
The library ID is needed for caching purposes only
Where should you store MCP server tokens like GITHUB_PERSONAL_ACCESS_TOKEN?
Directly in the .mcp.json file so the server can read them
In a .env file committed to your repository
In your shell profile (~/.bashrc or ~/.zshrc) as exported environment variables
In the CLAUDE.md file so Claude knows the token

Summary ​

MCP turns Claude from a code editor into a connected development platform. The three servers in this chapter — GitHub, Context7, and Playwright — cover the most common integration needs.

Key points:

  • Start with GitHub and Context7 — they provide the most value for the least setup
  • Always use environment variables for tokens, never hardcode
  • User scope for universal tools, project scope for team-shared configs
  • Custom MCP servers take ~30 lines of TypeScript or Python
  • MCP tools are hookable — use mcp__github__.* matchers to audit API calls

Going deeper: See A06 MCP Protocol Deep Dive for the wire protocol details, capability negotiation, transport options (stdio vs SSE), and patterns for building production-grade MCP servers with error handling and retry logic.

Next: Chapter 8: Subagent Architecture — Build specialized agents for architecture analysis, security scanning, and test generation.

Released under MIT License