Module 7 of 30
MCP
Model Context Protocol

The universal standard that connects AI models to any data source or tool. Build once. Plug in everywhere. Six concepts, from "why MCP exists" to a production server topology.

Track 2: Tool Use ⏱ ~18 min read · 6 concepts 7 / 30
Concept 1 of 6

USB-C for AI tools

Before MCP, every AI model had its own way of plugging into every tool. Three models × ten tools meant 30 custom integrations, each different, each fragile. Add a model and you rebuild ten connectors. Add a tool and you rebuild three.

MCP collapses that N×M into N+M. Each model speaks MCP once. Each tool speaks MCP once. They interoperate automatically. The protocol is open, JSON-RPC based, and not Anthropic-specific — the same MCP server works with Claude, Cursor, Zed, or any compatible host.

Concept 1 of 6

The phone-charger drawer

BEFORE: Picture the drawer of cables most of us had a decade ago — Micro-USB, Lightning, Mini-USB, barrel jacks, proprietary plugs from every laptop maker. Every device shipped with its own cord, and a friend's charger usually didn't fit your phone.

PAIN: Travel with three devices and you packed three chargers, three bricks, three sets of adapters. Lose one and you bought a fourth cable that worked only with that exact device. The integration cost lived in your bag for years.

MAPPING: USB-C ended that. One plug shape. Any cable, any device, any port. MCP is that for AI: one protocol shape between any model (host) and any tool (server). Plug in a new server, every host can use it tomorrow.

Concept 1 of 6

What's actually on the wire

Strip the buzzwords away and MCP is three small ideas stitched together. Every conversation between host and server uses these:

  1. JSON-RPC 2.0 messagesEach call is a tiny JSON object: a method name (tools/call, resources/read), some parameters, an id. The server replies with a result or an error using the same id.
  2. Capability negotiationThe very first exchange isn't real work — it's the host asking "what do you support?" and the server answering with a list of tools, resources, and prompts. No SDK is required to know what's there.
  3. Transport choiceThe same JSON travels over either stdio (host launches the server as a local subprocess and pipes JSON over stdin/stdout) or HTTP+SSE (server lives on a network endpoint, client uses HTTP requests + Server-Sent Events).
  4. The host is in chargeThe model never talks to the network directly. The host (Claude Desktop, Claude Code, Cursor) controls which servers are connected, mediates every call, and presents results back to the model.

That's the whole protocol surface. Everything else — tool schemas, resource URIs, prompt templates — is just data riding on top of it.

Concept 1 of 6

When to reach for MCP

# Question: should this integration be MCP?

IF capability is needed by >1 host:
  # Claude Desktop AND Cursor AND your app
  USE MCP # write once, every host gets it

ELIF capability touches live state:
  # DB rows, files, Slack, GitHub PRs
  USE MCP # skills can't fetch live data

ELIF capability is a workflow / pattern:
  # "here is how we deploy to k8s"
  USE a Skill # markdown is auditable

ELIF single-app, single-call function:
  USE raw tool_use (Module 5)
  # MCP overhead not justified

ELSE:
  START with raw tools, refactor later

Default rule: build it as MCP the moment a second host wants the same capability. That's when N+M starts paying for itself.

Concept 1 of 6

Misconceptions

"MCP is just another REST or GraphQL."
REST and GraphQL are general-purpose web standards. MCP is purpose-built for AI: capability discovery, version negotiation, and the Resource / Tool / Prompt typology that maps cleanly to how models use context. You wouldn't build a web app with MCP, and you wouldn't connect 20 tools to an AI with raw REST.
"MCP servers must run on the public internet."
The opposite is the default. Most MCP servers run as local subprocesses via stdio — the host launches your script and talks through pipes. No auth, no public endpoint, faster, safer for personal data. HTTP+SSE is the exception, used only when the server must live remotely.

MCP is the standardized cable between AI hosts and external systems. Open protocol, JSON-RPC on the wire, two transports (stdio + HTTP+SSE), three primitives.

If you remember nothing else: N+M, not N×M. Build a capability once as an MCP server and every compatible host can use it tomorrow.

Concept 2 of 6

Two parties, three primitives

An MCP system has exactly two roles. The client (also called the host) is the AI-facing app: Claude Desktop, Claude Code, Cursor, your own program. The server is a small process you write that exposes data or actions. They talk over JSON-RPC 2.0; nothing else.

What can the server expose? Three primitives, no more: Resources (read-only data identified by URIs), Tools (executable functions with schemas), and Prompts (parameterized templates). Every MCP feature you'll ever see is one of those three.

Concept 2 of 6

The well-run restaurant

BEFORE: You walk into a restaurant with no menu, no waitstaff, and the kitchen exposed. Want food? Walk in, learn the chef's workflow, the ingredient inventory, the prep tools — every restaurant works completely differently.

PAIN: Every visit is a new fight to discover what's possible. Switch restaurants and start from zero. Your code grows brittle adapters for every "kitchen" it ever talked to.

MAPPING: A real restaurant gives you a menu, a waiter (the protocol), and three sections: ingredients you can browse (Resources), dishes you can order that get prepared (Tools), and the chef's recommended pairings (Prompts). MCP is that menu — standardized across every kitchen your AI ever visits.

Concept 2 of 6

Lifecycle in three phases

Every MCP connection follows the same shape, regardless of language or transport.

  1. Initialize (handshake)Client sends initialize with its protocol version. Server replies with what it supports (which primitives, which features). Client confirms with initialized. Capabilities are now negotiated.
  2. DiscoverClient asks "what's on the menu?" via tools/list, resources/list, prompts/list. The server answers with names, descriptions, and JSON Schemas. No custom SDK needed.
  3. OperateClient makes calls: tools/call, resources/read, prompts/get. Server returns results. Repeat indefinitely until either side closes the connection.
  4. ShutdownEither party can end the session cleanly. For stdio, that's typically the host killing its subprocess on exit. For HTTP+SSE, the client closes its event stream.

The same three phases run whether your server is a 50-line Python script or a multi-tenant cloud service.

Concept 2 of 6

Message flow on the wire

Trim the boilerplate and a full MCP session is just this exchange:

# 1. HANDSHAKE
client → server: initialize {
  protocol_version: "2024-11-05",
  client_info: { name: "Claude Desktop" }
}
server → client: { capabilities: {
  tools: {}, resources: {}, prompts: {}
}}
client → server: initialized # confirmed

# 2. DISCOVER
client → server: tools/list
server → client: [ {name, description, schema}, ... ]

# 3. OPERATE (repeats)
client → server: tools/call {
  name: "read_file",
  arguments: { path: "app.py" }
}
server → client: { content: [...] }

# 4. SHUTDOWN
client closes transport → server exits.

Notice the model is nowhere on this diagram. The host turns model-emitted tool_use blocks into MCP calls behind the scenes.

Concept 2 of 6

Misconceptions

"The model talks to the MCP server directly."
It doesn't. The model emits tool_use content; the host translates that to tools/call on the right server, runs it, and returns the result as a tool_result in the next turn. The host is the bridge — the model never opens a socket.
"You always need all three primitives."
Not at all. A read-only docs server might expose only Resources. A workflow server might expose only Prompts. A Slack server is mostly Tools. Use what fits and skip the rest — the protocol doesn't require completeness.

Architecture in one line: two roles (client/host + server), three primitives (Resources, Tools, Prompts), one wire format (JSON-RPC 2.0).

The lifecycle — initializediscoveroperateshutdown — is identical for a 50-line script or a cloud service. Master that shape and every MCP server you read or write becomes obvious.

Concept 3 of 6

Declare three things, then run

An MCP server is small. In Python's FastMCP or the official Node SDK, the entire program is: import the library, declare any tools, declare any resources, declare any prompts, call run(). The library handles the JSON-RPC parsing, schema generation, and transport loop for you.

That means a useful filesystem server is roughly 50 lines, not the 150 you'd need for an equivalent REST service (HTTP routing, auth, CORS, OpenAPI). And the same 50 lines work with every MCP-compatible host on the planet — no per-client adapter code.

Concept 3 of 6

The food truck

BEFORE: Imagine wanting to share your cooking. The traditional path: lease a building, hire staff, build a kitchen, pass inspections, open a brick-and-mortar restaurant. Most great recipes never made it to the public because the overhead was crushing.

PAIN: Building a custom REST API to expose your data to AI feels the same. HTTP server, auth flow, serialization, OpenAPI spec, then a different SDK adapter for every model that wants to use it. Months of plumbing before anyone tastes your dish.

MAPPING: An MCP server is a food truck. Define your menu (tools), stock the ingredients (resources), open the service window. Any customer who speaks the protocol can order — no reservations, no per-customer adapters. Park it anywhere, serve everywhere.

Concept 3 of 6

The four chunks of any server

  1. Imports + safety boundaryBring in the SDK, then define what's in-bounds: an allowlist of file paths, a read-only DB role, a scoped API token. This is the most important code in the file — the AI can only do what your boundary permits.
  2. Tool definitionsDecorate each function with @mcp.tool() (or the SDK equivalent). The decorator auto-generates the JSON Schema from your type hints, so path: str becomes {"type":"string"} in the wire schema automatically.
  3. Resources + promptsDecorate read-only data getters with @mcp.resource("uri://...") and reusable templates with @mcp.prompt(). These are NOT tools — the host treats them differently (no confirmation prompt for resources, dropdown menu for prompts).
  4. RunCall mcp.run(). The library reads stdin, parses JSON-RPC, dispatches to your decorated functions, writes responses to stdout. That single call is the entire transport layer.

Skip the safety boundary at your peril — an MCP server runs with the privileges of whoever launched it.

Concept 3 of 6

Server skeleton

# Bootstrap the server
server = NEW MCPServer(name="filesystem")
SET ALLOWED_ROOT = env("ALLOWED_PATHS")

# 1. TOOL: action with side effects
DEFINE TOOL "write_file":
  inputs: path, content
  ON_CALL(input):
    IF NOT is_inside(input.path, ALLOWED_ROOT):
      RETURN error("path out of bounds")
    fs.write(input.path, input.content)
    RETURN { ok: true, bytes: len(input.content) }

# 2. RESOURCE: read-only data, addressed by URI
DEFINE RESOURCE "file-tree://cwd":
  ON_READ():
    RETURN walk(ALLOWED_ROOT, depth_limit=3)

# 3. PROMPT: reusable template
DEFINE PROMPT "file_summary":
  args: path
  template: "Summarize {path}: 1) one-liner 2) deps 3) issues"

# 4. Listen on stdio (host launches us)
server.run(transport="stdio")

Three primitives, one boundary check, one run() — that's a complete, deployable server.

Concept 3 of 6

Misconceptions

"I need to write JSON Schemas by hand."
No. Modern MCP SDKs derive schemas from your function's type hints. Type your parameters (path: str, limit: int) and add a docstring — the SDK fills in inputSchema automatically. Hand-written schemas are only needed for unusual constraints.
"I can skip the path / scope check — the model is well-behaved."
Don't. The MCP server runs with the same OS privileges as the user that launched it. Without a boundary check, a single prompt-injected message can read ~/.ssh/id_rsa or any file the OS will let you open. Always validate inputs against an explicit allowlist.

Building a server is 90% declaration, 10% safety. The SDK handles transport, schema, and dispatch; you write small functions and decorate them.

Always anchor the file with a safety boundary — an allowlist for paths, a read-only role for the DB, a scoped token for the API. The convenience of MCP is exactly why a boundary check is non-negotiable.

Concept 4 of 6

One config entry, one launched subprocess

To attach a server to Claude Desktop or Claude Code you don't write code — you write JSON. A single entry in claude_desktop_config.json (or .mcp.json for Claude Code) names a command, its arguments, and any environment variables. The host handles everything else.

On startup the host reads the config, spawns each server as a subprocess, runs the MCP handshake, and merges every server's tools, resources, and prompts into one unified menu the model sees. You can attach as many servers as you want; from the model's perspective they look like one big toolbox.

Concept 4 of 6

Pairing a Bluetooth device

BEFORE: Remember setting up a printer in 2002? You hunted for the right driver CD, ran an installer, restarted, and prayed the OS found the device. Every new model meant another driver, another reboot, another fight.

PAIN: Connecting AI models to tools used to feel exactly like that — per-tool SDKs, per-vendor auth flows, glue code that broke whenever a tool released a new version. Adding a fifth integration meant integrating with four other people's quirks.

MAPPING: Wiring an MCP server is like pairing Bluetooth: tell the host where to find the server (one config entry), they handshake automatically (capability discovery), and it Just Works. No drivers, no per-client SDK, no glue layer.

Concept 4 of 6

What happens when the host starts

  1. Read the configHost reads claude_desktop_config.json (Claude Desktop) or .mcp.json (Claude Code). Each entry has a command, args, and env vars.
  2. Spawn subprocessesFor every entry, the host runs the command (e.g. python my_server.py) as a child process and pipes stdin/stdout. Each server is fully isolated — if one crashes, the others keep working.
  3. Handshake each oneHost sends initialize on each subprocess, gets capabilities back, sends initialized. This happens in parallel across servers.
  4. Discover and mergeHost calls tools/list, resources/list, prompts/list on every server and merges results into a single menu prefixed with the server name (so filesystem.read_file doesn't collide with github.read_file).
  5. Run foreverThe model now sees the merged toolbox. Tool calls get routed back to the right subprocess. When the host quits, every server gets terminated.
Concept 4 of 6

Client config — what you actually write

# claude_desktop_config.json (or .mcp.json)
{
  "mcpServers": {

    "filesystem": {
      "command": "python",
      "args": ["-m", "my_fs_server"],
      "env": {
        "ALLOWED_PATHS": "/Users/me/projects"
      }
    },

    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": { "GITHUB_TOKEN": "${GH_TOKEN}" }
      # ↑ ALWAYS use env-var expansion, never inline secrets
    }

  }
}

That's the entire integration code. Add an entry, restart Claude Desktop, the new server's tools appear in the menu.

Concept 4 of 6

Misconceptions

"I'll just paste my API token straight into the config."
Don't. .mcp.json typically gets committed to git, and even claude_desktop_config.json ends up in backups. Use ${ENV_VAR} expansion exclusively, and put real values in your shell environment or a secret manager.
"More servers always means more capability."
It also means more attack surface and more confusion for the model. Each connected server's tools fill the model's context. Five well-scoped servers beats fifteen overlapping ones — pick the smallest set that covers your workflow.

Connecting a client to a server is a JSON edit, not a code change. One entry per server: command, args, env. The host handles spawning, handshake, discovery, and merging.

Two non-negotiables: secrets via ${ENV_VAR} expansion, and the smallest set of servers that does the job.

Concept 5 of 6

Resources, Tools, Prompts — pick the right one

The choice between Resource, Tool, and Prompt isn't cosmetic — it's a safety and cost decision. Resources are read-only, browsable, and don't trigger user-confirmation prompts. Tools have side effects and usually do. Prompts are server-authored templates that show up as one-click workflows in the host's UI.

Misclassify a read-only operation as a Tool and your users get a confirmation dialog every time the AI looks at a schema. Misclassify a write as a Resource and the safety guardrails vanish. The primitive shapes the user experience.

Concept 5 of 6

The library, the services desk, the recommended-reading list

BEFORE: Walk into a large library that has no signs, no catalog, no staff. Books are shelved randomly. You can't tell what's reference-only, what you can check out, or what requires a librarian's help.

PAIN: Without categories, AI models face the same problem with your data. They can't tell what's safe to read, what they're allowed to modify, and what workflow to follow. Everything looks like an undifferentiated blob of "stuff I might call."

MAPPING: MCP gives you three labeled shelves. Resources are the reference section — browse and read freely, no side effects. Tools are the services desk — submit a request, get a result, the world changes. Prompts are the librarian's recommended-reading lists — pre-curated workflows the user can launch with one click.

Concept 5 of 6

How each one shows up to the user

  1. Resource → URL barThe host shows it like a file. The user (or model) can browse the list, click a URI, and see the contents. No confirmation, no token cost beyond the response payload.
  2. Tool → function callThe model emits tool_use; the host typically prompts for confirmation (especially on writes), then dispatches. Each call costs ~200 framing tokens for the schema and result.
  3. Prompt → slash command / dropdownThe host surfaces the prompt as a one-click action. User picks "Code Review", fills in the parameter, and the server returns a pre-structured message array that gets injected into the conversation.
  4. Wire signal: the method nameThe verb tells you the primitive: resources/read vs tools/call vs prompts/get. If you see one of those in a log, you instantly know what's happening.
Concept 5 of 6

Resources vs. Tools vs. Prompts

  Resource Tool Prompt
Side effects? Never Yes None (returns text)
Identified by URI Name + schema Name + args
Wire method resources/read tools/call prompts/get
Returns Raw content Action result Message array
Confirmation? No Usually yes Triggered by user
Best fit File, schema, doc Query, write, deploy Code review template
Token overhead Just the payload ~200 framing tokens Pre-built messages

Rule of thumb: no side effects → Resource. State changes → Tool. Reusable workflow → Prompt.

Concept 5 of 6

Misconceptions

"Make everything a Tool — Tools are more powerful."
Common over-engineering trap. Tools cost ~200 extra tokens per call for the tool-use framing AND trigger user confirmations. A 50-call session with reads misclassified as tools wastes 10,000 tokens and annoys the user with a dialog they have to keep approving.
"Prompts are just fancy system prompts."
System prompts are static text you set once. MCP Prompts are parameterized templates that live on the server, are discoverable by the client, and generate context-specific message arrays on demand. The server author owns the template; the user just fills in the blanks.

Pick the primitive that matches the operation's nature, not its perceived "power." Resources for reads, Tools for state-changing actions, Prompts for reusable workflows.

Misclassification is one of the cheapest bugs to ship and one of the most expensive to fix — it bakes UX friction and token cost into every session.

Concept 6 of 6

Production reaches for canonical servers

The toy filesystem server is great for learning the protocol, but real teams reach for a small, well-known set: GitHub (PRs, issues, code), Postgres (read-only queries), Slack (channels + alerts), Jira (tickets + sprints), and one or two custom internal-API servers. Composing these turns Claude into a genuine teammate inside your engineering loop.

Two production disciplines matter most: least-privilege (read-only by default, write only when explicitly needed) and knowing when NOT to use MCP (skills are usually the better answer for static knowledge or workflow patterns).

Concept 6 of 6

Hiring contractors for your house

BEFORE: You can technically hire any contractor with a hammer to do anything in your house — rewire it, repaint it, knock down a wall. They'll show up willing.

PAIN: Without scoped permissions, one over-eager contractor can demolish a wall you just wanted patched. The blast radius of a single misunderstanding is your entire kitchen.

MAPPING: Production MCP works the same way. Each server is a contractor; least-privilege is the contract. Postgres MCP gets a read-only DSN, GitHub MCP gets a scoped PAT, Slack MCP sees only the channels you allowlist. The model can't break what you didn't authorize the server to touch.

Concept 6 of 6

Production discipline patterns

  1. Read-only by defaultFor 9 out of 10 tasks, the agent needs to read state, not change it. Configure two Postgres entries: one read-only for exploration, one read-write gated behind explicit opt-in. Only enable the write server when you actually need it.
  2. Scope at the server, not the promptAllowlist Slack channels, Jira projects, GitHub orgs at the MCP env level (e.g. JIRA_PROJECT_KEYS=ENG,INFRA). Don't rely on the prompt to convince the model not to call wrong scopes — restrict at the credential.
  3. Secrets via env vars onlyEvery credential comes from ${ENV_VAR} expansion, never inline in .mcp.json. Pair with secret-manager loading at host startup so the values never touch your repo.
  4. Audit before installingstdio servers run with full local privilege. Read the source of any community MCP server before launching it as a subprocess on your laptop. Treat them like installing an unsigned binary.
  5. Pair with PreToolUse hooksFor sensitive servers, add a PreToolUse hook (Module 26) that double-checks the tool name and parameters before execution — cheap defense-in-depth.
Concept 6 of 6

Skill, MCP, or both?

# Common architectural question:
# do I write a Skill or an MCP server?

IF capability is workflow / domain knowledge:
  # "here is how we deploy to k8s"
  USE a Skill
  # markdown is auditable; team can read it

ELIF capability needs LIVE data or actions:
  # "query current state of prod orders"
  USE an MCP server
  # skills can't fetch / write live state

ELIF capability is reusable command:
  # "/run-migration"
  USE slash command + Skill
  # slash = entry point; skill = procedure

ELIF needs both knowledge AND live state:
  # "resolve a deploy incident"
  USE Skill that calls an MCP server
  # skill describes; MCP acts

ELSE:
  DEFAULT to Skill
  # auditable beats opaque when in doubt

Heuristic: if the answer is "I want Claude to know something", write a skill. If it's "I want Claude to do something against live state", build an MCP server.

Concept 6 of 6

Misconceptions

"Wire up every MCP server you can find."
Each server's tool descriptions live in the model's context, even when unused. Twenty connected servers means thousands of tokens of menu-noise on every turn, plus a wider attack surface. Pick the smallest set that does the job.
"MCP can replace skills."
It can't, and shouldn't try. Skills are auditable markdown that any teammate can read; MCP servers are opaque executables. For static workflow patterns, default to skills. Reach for MCP only when you genuinely need live data or remote actions.

Production MCP is 80% configuration discipline, 20% server code. Read-only by default, secrets via env vars, scopes at the credential, audit before install, hooks for defense-in-depth.

And remember the architectural fork: skills for knowledge, MCP for live state, both together when the workflow needs both.

One question per concept

Tap a card to reveal the answer.

Open the full module on desktop

The desktop version walks through building a real MCP server with FastMCP, registering all three primitives, wiring it into Claude Desktop via claude_desktop_config.json, comparing stdio vs HTTP+SSE in code, and shipping a production-ready .mcp.json with GitHub, Postgres, Slack, and Jira servers.

Open M07 on Desktop → Full code · FastMCP server · Production .mcp.json · ~75 min

Module 7 of 30 · Track 2: Tool Use