Level 3Lesson 22⏱️ 120 min

MCP: Connecting Claude to Everything

Model Context Protocol — the open standard that makes Claude 10x more powerful

What is MCP?

Model Context Protocol (MCP) is an open standard released by Anthropic in November 2024. It defines how AI models connect to external tools, data sources, and services in a standardized way. Think of it as USB-C for AI — one universal connector that works with everything.

Before MCP, every AI integration was a one-off custom build. With MCP, any tool that builds an MCP server is instantly compatible with Claude. The ecosystem is exploding: GitHub, Slack, Notion, Linear, Figma, Gmail, Google Calendar, and hundreds more already have MCP servers.

MCP Architecture

MCP Client

Claude (or Claude Code, Cowork). The client makes requests, receives tool results, and decides what to do next. Claude can call multiple MCP servers simultaneously.

MCP Server

A program that connects to a specific service and exposes its capabilities as callable "tools". Runs locally on your machine (stdio) or remotely (HTTP/SSE).

Tools

Named functions with defined inputs/outputs. Examples: search_github_repos, create_github_issue, list_slack_channels. Claude reads the tool descriptions automatically and decides which to call.

End-to-End: Setting Up GitHub + Filesystem MCPs

This is the most useful starting MCP pair. GitHub lets Claude interact with your repos. Filesystem gives Claude direct file access on your machine. Here's the complete setup in 5 steps:

Step 1: Get a GitHub Personal Access Token

Go to github.com/settings/tokens → Generate new token (classic) → Select scopes: repo, read:org, read:user → Copy the token (starts with ghp_).

Step 2: Open your Claude Code settings
# Create the directory if it doesn't exist
mkdir -p ~/.claude

# Open in your editor
nano ~/.claude/settings.json
Step 3: Add the MCP server configs

Paste this into ~/.claude/settings.json (replace the token and your username):

{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_your_token_here"
      }
    },
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/Users/yourname/Documents",
        "/Users/yourname/Desktop",
        "/Users/yourname/Downloads"
      ]
    },
    "brave-search": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-brave-search"],
      "env": {
        "BRAVE_API_KEY": "your_brave_api_key"
      }
    }
  }
}
Step 4: Restart Claude Code and verify
# Close and reopen Claude Code, then run:
claude

# Inside Claude Code, type:
/mcp

# You should see:
# ✓ github (connected) — 15 tools available
# ✓ filesystem (connected) — 8 tools available
# ✓ brave-search (connected) — 1 tool available
Step 5: Test with a real request

Type this into Claude Code to confirm everything works:

List all my GitHub repositories created in the last 6 months.
For each: name, description, primary language, and last commit date.
Format as a table.

Claude should call list_repos and return a formatted table from your actual GitHub account.

Real Workflow: GitHub MCP in Action

Here's an actual end-to-end workflow using GitHub MCP. This takes 2 minutes instead of 20:

The Goal: Weekly PR Review + Issue Triage

Every Monday, you want to: review open PRs, find stale issues (no activity in 14+ days), and create a priority list.

The Prompt (paste this into Claude Code):
Using GitHub MCP:
1. List all open PRs in my repo [owner/repo-name]
   - For each: title, author, days open, review status
2. Find all open issues with no activity in the last 14 days
   - Flag any with the "bug" label as high priority
3. Compile a Monday triage doc:
   - PRs needing my review (I am @your-github-username)
   - High priority stale issues
   - Everything else stale
4. Save the result to ~/Documents/weekly-triage.md
What Claude Does (you watch it work):

Claude calls list_pull_requestslist_issues with state=open → filters by last activity date → categorizes by priority → calls write_file via Filesystem MCP → saves the markdown doc.

Total time: ~45 seconds. Result: a complete priority list ready when you start your week.

Popular Public MCP Servers

Slack MCP

Read messages, search history, post to channels. Install: npx -y @modelcontextprotocol/server-slack. Needs SLACK_BOT_TOKEN and SLACK_TEAM_ID. Use for: daily briefings, status updates, monitoring channels.

Google Drive MCP

Search and read documents and spreadsheets. Install: npx -y @modelcontextprotocol/server-gdrive. Needs OAuth setup. Use for: referencing specs, analyzing data, working with team docs.

Notion MCP

Read/write Notion pages, search workspace, update databases. Needs NOTION_API_KEY. Use for: knowledge management, meeting notes, project tracking.

Linear MCP

Create/update tickets, search issues, manage sprints. Needs LINEAR_API_KEY. Integrates your Claude coding sessions directly with your issue tracker.

PostgreSQL MCP

Query databases directly. Install: npx -y @modelcontextprotocol/server-postgres. Needs POSTGRES_URL. Use for: data analysis, debugging data issues, generating reports from your DB.

Browse all: github.com/modelcontextprotocol/servers — community-maintained list of MCP servers

Setting Up MCPs in Cowork

In Cowork mode, MCPs are managed through plugins. The Gmail, Google Calendar, Figma, and other MCPs you see in the toolbar are all MCP-backed. To add a new MCP server:

  • Open Cowork Settings → Connected Tools
  • Or use the setup-cowork skill: type "Help me connect GitHub to Cowork"
  • Or install a plugin that bundles the MCP config (e.g., the Engineering plugin includes GitHub MCP)
Cowork vs Claude Code MCP configs: Cowork has its own MCP management UI. Claude Code uses ~/.claude/settings.json. They can share the same MCP servers but are configured separately.

Building a Simple Custom MCP Server

Any internal tool or data source can become an MCP server. Here's a working minimal example in TypeScript — connects Claude to an internal REST API:

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const server = new Server(
  { name: "company-api", version: "1.0.0" },
  { capabilities: { tools: {} } }
);

// Tell Claude what tools are available
server.setRequestHandler("tools/list", async () => ({
  tools: [
    {
      name: "get_customer",
      description: "Fetch customer data by customer ID from our internal CRM",
      inputSchema: {
        type: "object",
        properties: {
          customer_id: { type: "string", description: "The customer ID (e.g. CUST-1234)" }
        },
        required: ["customer_id"]
      }
    },
    {
      name: "list_open_tickets",
      description: "List all open support tickets for a customer",
      inputSchema: {
        type: "object",
        properties: {
          customer_id: { type: "string" },
          priority: { type: "string", enum: ["high", "medium", "low", "all"] }
        },
        required: ["customer_id"]
      }
    }
  ]
}));

// Handle tool calls
server.setRequestHandler("tools/call", async (req) => {
  const { name, arguments: args } = req.params;

  if (name === "get_customer") {
    const res = await fetch(`https://internal-api.company.com/customers/${args.customer_id}`, {
      headers: { "Authorization": `Bearer ${process.env.INTERNAL_API_KEY}` }
    });
    const data = await res.json();
    return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
  }

  if (name === "list_open_tickets") {
    const res = await fetch(
      `https://internal-api.company.com/tickets?customer=${args.customer_id}&status=open&priority=${args.priority || 'all'}`,
      { headers: { "Authorization": `Bearer ${process.env.INTERNAL_API_KEY}` } }
    );
    const data = await res.json();
    return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
  }
});

new StdioServerTransport(server).start();

Add it to settings.json:

"company-api": {
  "command": "node",
  "args": ["/path/to/company-api-server/index.js"],
  "env": {
    "INTERNAL_API_KEY": "your_api_key"
  }
}

Now Claude can answer: "Get me customer CUST-4821's profile and all their open high-priority tickets" — pulling live data from your internal system.

Hands-On Exercise (~30 min)

Task A: Connect GitHub MCP (15 min)

Follow the 5-step setup above. Get your PAT, configure ~/.claude/settings.json, restart Claude Code, run /mcp to confirm connection. Then run the weekly triage prompt against one of your real repos.

Task B: Connect One More MCP (10 min)

Pick one more MCP relevant to your work: Slack, Notion, Linear, or PostgreSQL. Follow the same pattern: get API key → add to settings.json → verify with /mcp → run one real test query.

Task C: Design Your Custom MCP (5 min)

Think about an internal tool at your company that Claude doesn't have access to. Sketch out: what 3 tools would you expose? What would each tool take as input and return? (No code needed — just the design.)

Lesson 22 Quick Reference
~/.claude/settings.json

Global Claude Code config. Add mcpServers here with command, args, and env. Restart after editing.

/mcp command

Lists all connected MCP servers and their available tools. Use to verify connections and debug.

npx -y @mcp/server-*

Standard install pattern for public MCP servers. npx downloads and runs without a separate install step.

GITHUB_PERSONAL_ACCESS_TOKEN

Required env var for GitHub MCP. Get from github.com/settings/tokens. Needs repo + read:org scopes.

Custom MCP

Any REST API or internal tool can become an MCP server. Expose 3-5 key tools. Claude auto-discovers and uses them.

stdio transport

Local MCP servers communicate via stdin/stdout. Fast, no network overhead. Used by all npx-based servers.