fix(M7-27+capstone): apply AI-drives-git reframe, lesson=theory, de-slop course-wide

Phase 2 sweep — all modules are post-pivot, so the learner directs the AI agent
(Claude Code as the worked example) to do the git/setup work and verifies, instead
of typing commands by hand; no re-teaching basics. Lesson sections are theory with
example output; all execution lives in the labs. De-slopped ("prose" etc. gone
course-wide, em-dash density thinned). /path/to placeholders -> ~/ai-workflow-course.

Every deliberate teaching device verified intact: M10 ai-change.patch trap,
M12 bad-clear-snippet, M13/M27 planted pending_count bug, M15 secret+typosquat+MD5,
M18 BREAK=1, M21 absent-.gitignore, M22 poisoned skill, M24 no-op patch, M25 --simulate.
Labs compile/parse (py/sh/yaml/json); no junk.

Closes #83
Closes #86
Closes #89

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TfzV5QvtPDz8LJS3Pu5VLT
This commit is contained in:
2026-06-22 21:58:17 -04:00
parent a29823f4b3
commit f925fd9645
38 changed files with 1735 additions and 1424 deletions
@@ -1,7 +1,7 @@
# Module 20 — MCP Servers: Giving the AI Hands
> **Until now the AI could read and write files in your repo and nothing else. MCP lets it reach
> your real tools, data, and systems your task tracker, your database, your docs, your APIs
> your real tools, data, and systems (your task tracker, your database, your docs, your APIs)
> through a standard interface instead of working blind.** And because MCP is an open protocol, not
> a vendor feature, the connections you build outlive whichever model you're running.
@@ -9,14 +9,14 @@
## Prerequisites
- **Module 1** the `tasks-app` running example, an editor, and a terminal. The lab gives the AI
hands on this exact app.
- **Module 2** you read a project's state from Git and you trust `git restore` to undo a mess.
- **Module 1** gave you the `tasks-app` running example, an editor, and a terminal. The lab gives
the AI hands on this exact app.
- **Module 2** taught you to read a project's state from Git and trust `git restore` to undo a mess.
That safety net matters more here than anywhere so far: you're about to let the AI *act on real
systems*, not just edit files.
- **Module 4** the AI lives in your editor or CLI (an "agentic tool") and edits files directly.
That same tool is the **MCP client** in this module; MCP is how you extend what it can reach.
- **Module 5** you commit the AI's config to the repo. MCP server configuration is more config
- **Module 4** put the AI in your editor or CLI (an "agentic tool"), editing files directly. That
same tool is the **MCP client** in this module; MCP is how you extend what it can reach.
- **Module 5** had you commit the AI's config to the repo. MCP server configuration is more config
worth committing, and the same "make it travel with the repo" instinct applies.
Helpful but not required: **Module 16** (containers) and **Module 17** (secrets) get referenced when
@@ -32,14 +32,14 @@ editing your code and shipping it. Unit 4 is about giving it reach beyond the re
By the end of this module you can:
1. Explain the MCP client/server model what a server exposes (tools, resources, prompts), what the
client (your agentic tool) does, and why "it's a protocol, not a vendor feature" is the whole
point.
2. Connect an MCP server to your agentic tool and confirm the AI can call its tools — an existing
reference server (the optional Part A warm-up) or the one you build in Part B/C.
1. Explain the MCP client/server model: what a server exposes (tools, resources, prompts), what the
client (your agentic tool) does, and why "it's a protocol, not a vendor feature" is what makes
your work survive a model swap.
2. Connect an MCP server to your agentic tool and confirm the AI can call its tools, using either an
existing reference server (the optional Part A warm-up) or the one you build in Part B/C.
3. Build a tiny MCP server in Python that exposes one real capability over the `tasks-app`, and wire
it into your tool.
4. Watch the AI *use* that server read and change real state through a tool call and verify the
4. Watch the AI *use* that server (read and change real state through a tool call) and verify the
effect outside the chat.
5. State precisely what MCP does and doesn't give you, including the one caveat this module
deliberately defers: **installing an MCP server is installing code that runs with access to your
@@ -52,23 +52,23 @@ By the end of this module you can:
### The wall the AI keeps hitting
Everything so far has given the AI exactly one kind of reach: **files in your repo.** Module 4 let
it read and write `cli.py`; Module 2 let it read your Git history. That's a lot but watch where it
it read and write `cli.py`; Module 2 let it read your Git history. That's a lot, but watch where it
stops.
Ask your agentic tool, *"how many tasks are in my list and which are done?"* and it can answer,
because the data happens to live in a file it can read. Now ask it something one inch further out:
- *"How many active users signed up this week?"* — the answer is in a database it can't query.
- *"Is this docs page out of date versus the changelog?"* — the docs live in a system it can't read.
- *"File a ticket for this bug."* — the tracker is an API it can't call.
- *"How many active users signed up this week?"* The answer is in a database it can't query.
- *"Is this docs page out of date versus the changelog?"* The docs live in a system it can't read.
- *"File a ticket for this bug."* The tracker is an API it can't call.
The AI's response to all three is some flavour of *"I can't access that, but here's a script you
could run"* and you're back in the copy-paste loop from Module 1, just one level up. The model is
could run,"* and you're back in the copy-paste loop from Module 1, just one level up. The model is
plenty smart enough to do the work. It's **blind and handless** beyond your files. It can reason
about your systems; it can't *touch* them.
You could solve this the bad way: paste a database dump into the chat, copy the AI's SQL out and run
it yourself, paste the results back. That's Module 1's seam all over again you as the integration
it yourself, paste the results back. That's Module 1's seam all over again: you as the integration
layer, manually shuttling data between the AI and the real system. MCP exists to delete that loop.
### What MCP is
@@ -76,7 +76,7 @@ layer, manually shuttling data between the AI and the real system. MCP exists to
The **Model Context Protocol (MCP)** is an open standard for connecting AI applications to external
tools and data through a uniform interface. Two roles:
- An **MCP server** exposes capabilities "here are the things I can do and the data I can provide."
- An **MCP server** exposes capabilities: "here are the things I can do and the data I can provide."
- An **MCP client** (embedded in your agentic tool) discovers those capabilities and calls them on
the AI's behalf.
@@ -87,25 +87,24 @@ system, and the result comes back into the AI's context. No pasting, no scripts
If you've ever written or consumed an HTTP API, the instinct transfers cleanly: a server advertises
a set of operations; a client calls them with arguments and gets structured results back. The
difference is what it's *for* MCP is shaped specifically so an AI can **discover** what's available
difference is what it's *for*: MCP is shaped specifically so an AI can **discover** what's available
at runtime (names, descriptions, argument schemas) and decide which call to make, rather than a human
reading docs and hardcoding the call.
### Why "a protocol, not a vendor feature" is the whole point
### Why "a protocol, not a vendor feature" changes everything
This is the course thesis showing up in the architecture itself. MCP is a **standard**, like HTTP or
SQL not a button inside one company's product. The consequences are exactly the ones this course
SQL, not a button inside one company's product. The consequences are exactly the ones this course
keeps promising:
- **Write a server once; every compliant client can use it.** The `tasks` server you'll build in the
lab works with any agentic tool that speaks MCP today's and next year's. You are not building for
lab works with any agentic tool that speaks MCP, today's and next year's. You are not building for
a vendor; you're building for the protocol.
- **Swap the model underneath and your servers don't care.** The server exposes `add_task`; it has
no idea which model is on the other end of the client. Change models which you will and every
connection you built keeps working. That's the durable-skill payoff stated in Module 1, now load-
bearing instead of aspirational.
- **The ecosystem compounds.** Because it's a shared standard, there's a large and growing catalogue
of servers other people already wrote — for databases, cloud providers, ticket trackers, docs,
no idea which model is on the other end of the client. Change models, which you will, and every
connection you built keeps working. That's the durable-skill payoff Module 1 promised, made real.
- **The catalogue grows on its own.** Because it's a shared standard, there's a large and growing
set of servers other people already wrote: databases, cloud providers, ticket trackers, docs,
browsers, your own internal tools. Connecting one is usually configuration, not coding.
MCP originated with one vendor and was released as an open spec; it's since been adopted across major
@@ -119,11 +118,11 @@ An MCP server can offer three kinds of things. You'll mostly care about the firs
- **Tools** — *actions the AI can take.* A tool is a named function with typed arguments and a
description: `add_task(title)`, `run_query(sql)`, `create_issue(title, body)`. The AI reads the
description, decides to call it, supplies the arguments, and gets a result. This is the "hands"
half of the module title tools are how the AI *does* things. (Tools can have side effects: they
half of the module title; tools are how the AI *does* things. (Tools can have side effects: they
write to your database, hit your API, change real state. That power is exactly why Module 22
exists.)
- **Resources** — *data the AI can read.* Read-only context the server makes available: a file, a
database record, a docs page, the contents of a config. Where tools *do*, resources *inform*
database record, a docs page, the contents of a config. Where tools *do*, resources *inform*:
they're how the AI gets eyes on a system, the parallel to "durable memory it can read" from
Module 2, extended past your repo.
- **Prompts** — *reusable prompt templates the server offers* for common operations against it (e.g.
@@ -139,16 +138,16 @@ The client has to launch or reach the server and exchange messages with it. Two
the distinction is practical:
- **stdio (local).** The client launches the server as a subprocess on your machine and talks to it
over standard input/output the same pipes a normal command-line program uses. This is the right
over standard input/output, the same pipes a normal command-line program uses. This is the right
default for anything local: your `tasks` server, a server that reads your filesystem, one that
drives a local tool. No network, no ports, no auth to set up. **This is what the lab uses.**
- **HTTP-based (remote).** For a server running somewhere else a shared internal service, a
vendor's hosted server the client reaches it over HTTP. This is where authentication and network
- **HTTP-based (remote).** For a server running somewhere else (a shared internal service, a
vendor's hosted server), the client reaches it over HTTP. This is where authentication and network
access enter the picture, and where the security stakes climb.
You don't pick the transport at random; it follows from where the server runs. Local tool over a
real system on your box → stdio. Shared or third-party service → HTTP. (The exact name of the HTTP
transport in the spec has changed more than once see *Verify-before-publish* but the local-vs-
transport in the spec has changed more than once (see *Verify-before-publish*), but the local-vs-
remote split is the durable idea.)
### Configuring a server: where the wiring lives
@@ -162,7 +161,7 @@ like this:
"mcpServers": {
"tasks": {
"command": "python",
"args": ["/absolute/path/to/tasks-app/tasks_mcp_server.py"]
"args": ["/home/you/ai-workflow-course/tasks-app/tasks_mcp_server.py"]
}
}
}
@@ -171,17 +170,17 @@ like this:
Read it plainly: *"there's a server called `tasks`; to start it, run `python <that file>` and talk to
it over stdio."* That's the whole contract for a local server.
Two honest notes, both flowing from the course's core promises:
Two notes, both flowing from the course's core promises:
- **The filename and location of this config are tool-specific, and we won't pin them.** Some tools
keep it in a project file, some in a user-level file, some let you add servers from a UI. The
`mcpServers` *shape* above is widely shared, but check your tool's docs for where it reads it. The
principle "a server is a name plus how to launch or reach it" outlives any one tool's filename,
principle ("a server is a name plus how to launch or reach it") outlives any one tool's filename,
exactly like the committed-instructions file in Module 5.
- **This config is worth committing with care.** A project-level MCP config means every teammate
- **This config is worth committing, with care.** A project-level MCP config means every teammate
and every agent that opens the repo gets the same tools wired up, which is the Module 5 instinct
applied one level out. But MCP config often points at paths or, for HTTP servers, endpoints and
credentials and **credentials never go in the repo** (that's Module 17, and it's a hard rule).
credentials, and **credentials never go in the repo** (that's Module 17, and it's a hard rule).
Commit the wiring; keep the secrets in the environment.
### Where this is in the repo's reach, and where it's heading
@@ -189,7 +188,7 @@ Two honest notes, both flowing from the course's core promises:
Stack the units up and the picture is clear. Module 4 put the AI in your editor. This module gives
that same AI hands beyond the repo. The next three modules build directly on it:
- **Module 21 (Skills)** teaches the AI *playbooks* repeatable procedures it runs your way. Skills
- **Module 21 (Skills)** teaches the AI *playbooks*, repeatable procedures it runs your way. Skills
and MCP compose: MCP gives the AI the tools; a skill tells it *how and when* to use them.
- **Module 22 (Securing third-party MCP servers and skills)** handles the danger this module is
deliberately deferring (see *Where it breaks*). Read it before you install anything you didn't
@@ -201,24 +200,24 @@ that same AI hands beyond the repo. The next three modules build directly on it:
## The AI angle
Most integration work wires systems together for *programs* to use fixed clients calling fixed
Most integration work wires systems together for *programs* to use: fixed clients calling fixed
endpoints. MCP is shaped for a different consumer: **an AI that decides at runtime what it needs.**
That changes what matters about the integration.
- **Discovery, not hardcoding.** A traditional client is written against specific API calls by a
human. An MCP client hands the AI a *menu* tool names, descriptions, argument schemas and the
human. An MCP client hands the AI a *menu* (tool names, descriptions, argument schemas) and the
AI picks. Which means the **description you write for a tool is part of the interface**: it's how
the model knows when to reach for `add_task` versus `list_tasks`. A vague docstring is a vague tool.
(You'll feel this in the lab the docstrings on the server functions are not decoration; they're
(You'll feel this in the lab: the docstrings on the server functions are not decoration; they're
what the AI reads.)
- **It closes Module 1's loop at the systems layer.** The original copy-paste pain was shuttling code
between a chat and a file. The same pain reappears one level out: shuttling *data* between the AI
and your database, your tracker, your docs. MCP is the editor-integration moment for systems the
and your database, your tracker, your docs. MCP is the editor-integration moment for systems: the
AI reaches them directly instead of you being the integration layer.
- **It's the model-agnostic bet made concrete.** Every other module argues the workflow outlasts the
model. MCP *is* that argument in protocol form: the server you write is bound to a standard, not a
model. Swap the model and your hands stay attached.
- **The reach is the risk.** The very thing that makes MCP powerful real access to real systems
- **The reach is the risk.** The very thing that makes MCP powerful, real access to real systems,
is why it needs its own security module. An AI with hands can do real damage as easily as real
work. That's not a reason to avoid it; it's the reason Module 22 comes right after.
@@ -231,71 +230,74 @@ machine, any OS.
You'll do two things: **connect an existing MCP server** to confirm the client/server wiring works
at all, then **build your own tiny server** over the `tasks-app` and watch the AI use it. The second
is the one that lands the concept.
is where the idea sticks.
**You'll need:**
- The `tasks-app` from Module 1/2 (a folder with `tasks.py`, `cli.py`, and ideally a Git repo so you
can see and undo what the AI does Module 2).
can see and undo what the AI does, per Module 2).
- Your agentic coding tool from Module 4, which is the **MCP client**. Find, in its docs, *where it
reads MCP server configuration* and *how it shows that a server is connected* (often a list of
connected servers or available tools).
- Python 3.10+ and the official MCP Python SDK, installed into a virtual environment — read the
**Python packages and which `python`** note just below *before* you run `pip`.
- Python 3.10+ and the official MCP Python SDK, installed into a virtual environment. Read the
**Python packages and which `python`** note just below before you have the agent set this up.
- The starter files in this module's `lab/` folder: `tasks_mcp_server.py` and
`mcp-config-example.json`.
- **Only for the optional Part A warm-up:** the reference server your tool points you at typically
runs via `npx` (needs Node) or `uvx` (needs uv) install whichever its documented `command`
needs. Part B/C, the load-bearing path, need only the Python SDK above, so you can skip this.
runs via `npx` (needs Node) or `uvx` (needs uv); install whichever its documented `command`
needs. Part B/C need only the Python SDK above, so you can skip this.
> **Python packages and which `python`.** This lab's one dependency is the MCP SDK, and *how* you
> install it decides whether the server ever connects. Two things bite people:
> **Python packages and which `python`.** This lab's one dependency is the MCP SDK, and *how* it
> gets installed decides whether the server ever connects. Two things bite people, and one is the
> reason you point the agent at the work and then check the result yourself:
>
> - **PEP 668 ("externally-managed-environment").** On modern Debian/Ubuntu and Homebrew Python, a
> global `pip install` is refused on purpose. The clean fix is a virtual environment per project:
> global `pip install` is refused on purpose. The clean fix is a virtual environment per project.
> Direct Claude Code (or sub your own agent) to set it up:
>
> ```bash
> cd ~/ai-workflow-course/tasks-app
> python3 -m venv .venv # one-time
> source .venv/bin/activate # Windows: .venv\Scripts\activate
> python3 -m pip install "mcp[cli]"
> ```
> > *"In `~/ai-workflow-course/tasks-app`, create a `.venv` virtual environment, install `mcp[cli]`
> > into it, then tell me the absolute path to that venv's python interpreter."*
>
> (If you'd rather not manage a venv: `pipx`, or `pip install --break-system-packages` — but a venv
> is the clean default and keeps this lab's dependency out of your system Python.)
> - **The install interpreter must match the config's launch command.** Your MCP client starts the
> server by running the `"command"` in its config — *not* your activated shell — so activating a
> venv does nothing to help the client find the SDK. You must point `"command"` at the venv's
> **absolute** python path (e.g. `~/ai-workflow-course/tasks-app/.venv/bin/python`, or
> `...\.venv\Scripts\python.exe` on Windows). If they don't match, the server dies on `import mcp`
> and your tool just says "not connected" with no obvious reason — the exact failure this lab is
> about avoiding.
> It will run the equivalent of `python3 -m venv .venv` and `.venv/bin/python -m pip install
> "mcp[cli]"`, and report a path like `/home/you/ai-workflow-course/tasks-app/.venv/bin/python`.
> (If you'd rather not use a venv, the agent can fall back to `pipx` or
> `pip install --break-system-packages`; a venv is the clean default and keeps this dependency out
> of your system Python.)
> - **The install interpreter must match the config's launch command.** This is the load-bearing
> gotcha of the whole lab, so understand it even though the agent does the typing. Your MCP client
> starts the server by running the `"command"` in its config, *not* from your activated shell, so
> activating a venv does nothing to help the client find the SDK. The config's `"command"` must be
> the venv's **absolute** python path (the one the agent just reported, e.g.
> `/home/you/ai-workflow-course/tasks-app/.venv/bin/python`, or `...\.venv\Scripts\python.exe` on
> Windows). If they don't match, the server dies on `import mcp` and your tool just says "not
> connected" with no obvious reason: the exact failure this lab is about avoiding.
>
> Before wiring anything, verify with the *same* interpreter the config will launch:
> Before wiring anything, confirm the SDK is reachable from the *same* interpreter the config will
> launch. Run this one-line check yourself against the path the agent reported:
>
> ```bash
> ~/ai-workflow-course/tasks-app/.venv/bin/python -c "import mcp; print('mcp ok')"
> /home/you/ai-workflow-course/tasks-app/.venv/bin/python -c "import mcp; print('mcp ok')"
> ```
### Part A — Connect an existing server (optional warm-up, ~10 min)
This part is **optional**: it proves the plumbing works by connecting a server someone else already
wrote, but it's a warm-up, not the load-bearing concept — Part B/C land that on the Python SDK you
already installed. The catch is the runtime: most **reference servers** (filesystem, fetch, git, and
wrote, but it's a warm-up. Parts B/C carry the real lesson on the Python SDK you already installed.
The catch is the runtime: most **reference servers** (filesystem, fetch, git, and
more) are distributed for `npx` (Node) or `uvx` (uv), *not* Python, so this warm-up needs whichever
runtime its documented command uses. If you don't already have Node or uv and don't want to install
one for a 10-minute warm-up, **skip straight to Part B** you lose nothing the rest of the lab needs.
one for a 10-minute warm-up, **skip straight to Part B**; you lose nothing the rest of the lab needs.
To do it: pick a simple, read-only reference server your tool's docs point you at (a "filesystem" or
"fetch" server is a good first choice), and install the runtime its command needs (Node for `npx`, uv
for `uvx`).
1. Add the server to your tool's MCP config, following the tool's docs. Most reference servers are
launched the same stdio way as the JSON shape shown in *Key concepts* a `command` (e.g. `npx` or
launched the same stdio way as the JSON shape shown in *Key concepts*: a `command` (e.g. `npx` or
`uvx`) and `args`.
2. Restart or reload your agentic tool so it picks up the config. Confirm it reports the server as
**connected** and lists its tools.
3. Ask the AI to do something only that server enables — e.g. with a fetch server, *"fetch
3. Ask the AI to do something only that server enables. For example, with a fetch server, *"fetch
example.com and summarize it"*; with a filesystem server scoped to a folder, *"list the files in
that folder."* Watch the AI **call a tool** rather than tell you it can't.
@@ -303,14 +305,21 @@ That's the entire client/server loop, end to end, with zero code you wrote. Now
> **Stop before you install anything you don't fully trust.** A reference server from the protocol's
> own maintainers is a reasonable warm-up. A random server off the internet is untrusted code that
> will run with your permissions vetting that is **Module 22's** job, and it's not optional. For
> will run with your permissions; vetting that is **Module 22's** job, and it's not optional. For
> now, stick to first-party reference servers or the one you write next.
### Part B — Build a one-tool server over the tasks-app
1. Copy this module's `lab/tasks_mcp_server.py` into your `tasks-app` folder, next to `tasks.py` and
`cli.py`. (It reuses `tasks.py` and shares the same `tasks.json`, so anything it changes shows up
in `python cli.py list`.) The whole server is two tools:
1. Have Claude Code (or sub your own agent) copy this module's `lab/tasks_mcp_server.py` into your
`tasks-app` folder, next to `tasks.py` and `cli.py`, and confirm it landed there:
> *"Copy the starter file at `modules/20-mcp-servers-giving-the-ai-hands/lab/tasks_mcp_server.py`
> into `~/ai-workflow-course/tasks-app/`, next to `tasks.py` and `cli.py`, then show me the
> contents so I can read it."*
Then open the copied file yourself and read it. (It reuses `tasks.py` and shares the same
`tasks.json`, so anything it changes shows up in `python cli.py list`.) The whole server is two
tools:
```python
@mcp.tool()
@@ -327,41 +336,50 @@ That's the entire client/server loop, end to end, with zero code you wrote. Now
return f"added: {title}"
```
That's it a tool is a normal function plus the docstring the AI reads to decide when to use it.
That's it: a tool is a normal function plus the docstring the AI reads to decide when to use it.
2. Sanity-check it starts. From inside `tasks-app`:
2. Sanity-check that it starts (optional, but it's a useful feel for what stdio does). Ask the agent
to run the server with the venv python and report what happens:
```bash
python3 -m pip install "mcp[cli]" # into the venv from the note above, once
python tasks_mcp_server.py # it will sit there waiting for a client — that's correct
```
> *"Run `~/ai-workflow-course/tasks-app/.venv/bin/python tasks_mcp_server.py` from inside
> `tasks-app` and tell me what it does, then stop it."*
It looks like it's hanging. It isn't a stdio server waits for a client on its stdin/stdout.
Press Ctrl-C; you don't run it by hand, the client launches it.
It looks like it's hanging. It isn't: a stdio server waits for a client on its stdin/stdout, so
there's nothing to print and no prompt to return to until a client connects. That waiting *is*
the correct behavior. You don't run it by hand for real; the client launches it.
### Part C — Wire it into your agentic tool
3. Open `lab/mcp-config-example.json`. Copy the `tasks` entry into wherever your tool reads MCP
config. Set `"command"` to the **absolute path of the python that has `mcp` installed** — the venv
python from the note above, *not* a bare `python` — and set `args` to the **absolute** path to
your `tasks_mcp_server.py`:
3. Have the agent write the `tasks` config entry. It already knows both absolute paths (the venv
python it just reported and the server file it just copied), so let it fill them in. Point it at
wherever your tool reads MCP config, using `lab/mcp-config-example.json` as the shape:
> *"Add a `tasks` MCP server entry to <my tool's MCP config file>, using the shape in
> `lab/mcp-config-example.json`. Set `command` to the absolute venv python path you reported and
> `args` to the absolute path of the copied `tasks_mcp_server.py`. Do not use a bare `python`."*
The entry it writes should look like this, with real absolute paths swapped in for the
placeholders:
```json
"tasks": {
"command": "/ABSOLUTE/PATH/TO/ai-workflow-course/tasks-app/.venv/bin/python",
"args": ["/ABSOLUTE/PATH/TO/ai-workflow-course/tasks-app/tasks_mcp_server.py"]
"command": "/home/you/ai-workflow-course/tasks-app/.venv/bin/python",
"args": ["/home/you/ai-workflow-course/tasks-app/tasks_mcp_server.py"]
}
```
(On Windows the venv python is `...\.venv\Scripts\python.exe`.) A bare `"command": "python"` is the
single most common reason the server "won't connect": the client launches whatever `python` is on
*its* PATH, which is usually not the interpreter that has the SDK.
(On Windows the venv python is `...\.venv\Scripts\python.exe`.) *Where* the config file lives is
tool-specific; if your tool adds servers from a UI or your agent can't reach its config, edit the
entry by hand as the fallback. Either way, a bare `"command": "python"` is the single most common
reason the server "won't connect": the client launches whatever `python` is on *its* PATH, which
is usually not the interpreter that has the SDK. That's why the `"command"` must be the absolute
venv path.
4. Reload your agentic tool and confirm it shows the `tasks` server **connected**, with `list_tasks`
4. Reload your agentic tool and verify it shows the `tasks` server **connected**, with `list_tasks`
and `add_task` among its available tools. If it doesn't connect, the usual culprits are a wrong
path, the wrong `python`, or the SDK not installed for that interpreter — re-run the
`... .venv/bin/python -c "import mcp"` check from the note above against the *exact* path you put
in `"command"`, then check the tool's MCP logs.
path, the wrong `python`, or the SDK not installed for that interpreter. Re-run the
`... .venv/bin/python -c "import mcp"` check from the note above against the *exact* path in
`"command"`, then check the tool's MCP logs.
### Part D — Watch the AI use its new hands
@@ -369,16 +387,16 @@ That's the entire client/server loop, end to end, with zero code you wrote. Now
> *"What's on my task list right now?"*
The AI should call `list_tasks` and answer from the live result not from reading a file, not
The AI should call `list_tasks` and answer from the live result, not from reading a file and not
from memory. Many tools show the tool call inline ("called `tasks.list_tasks`"); watch for it.
6. Now have it act:
> *"Add a task: review the Module 20 lab."*
It should call `add_task("review the Module 20 lab")`. Then **verify the effect outside the AI**,
which is the whole point — the change is real. Verify it the way you'd verify any runtime effect:
by reading the *state*, not the repo:
It should call `add_task("review the Module 20 lab")`. Then **verify the effect outside the AI**.
This is the part that matters: the change is real, and the proof lives outside the chat. Check it
the way you'd verify any runtime effect, by reading the *state*, not the repo:
```bash
python cli.py list # the new task is there, because the server wrote the same tasks.json
@@ -387,7 +405,7 @@ That's the entire client/server loop, end to end, with zero code you wrote. Now
The AI just changed real state in a real system through a tool call. Notice what you did *not*
reach for: `git diff`. `tasks.json` is deliberately gitignored (Module 2's `.gitignore` treats it
as generated runtime state, not source), so `git diff` stays empty here and that's correct, not a
as generated runtime state, not source), so `git diff` stays empty here, and that's correct, not a
bug. The proof the task list changed is the live state (`python cli.py list` / `cat tasks.json`),
not version control; runtime data the app owns is exactly the kind of thing you keep *out* of
history. No copy-paste, no script you ran by hand, no pasting `tasks.json` into a chat. That's
@@ -402,20 +420,20 @@ That's the entire client/server loop, end to end, with zero code you wrote. Now
## Where it breaks
The honest caveats and one of them is large enough that it gets its own module.
The caveats, and one of them is large enough that it gets its own module.
- **Installing an MCP server is installing code that runs with your access and this module does not
- **Installing an MCP server is installing code that runs with your access, and this module does not
secure it.** A server you connect runs on your machine (stdio) or is trusted by your client (HTTP),
with whatever permissions you give it: your files, your network, your credentials. A malicious or
compromised server is malware with an AI driving it, and a server's tool descriptions can even
carry instructions that try to steer the model (prompt injection). **This module deliberately
stops here.** The attack surface vetting servers, pinning versions, least-privilege, prompt
injection is **Module 22 (Securing Third-Party MCP Servers and Skills)**, and you should treat
stops here.** The attack surface (vetting servers, pinning versions, least-privilege, prompt
injection) is **Module 22 (Securing Third-Party MCP Servers and Skills)**, and you should treat
it as required reading before connecting anything you didn't write. In this module: only first-
party reference servers and the one you build yourself.
- **A tool with side effects can do real damage as easily as real work.** Your `add_task` writes to
real state. A `run_query` or `delete_user` tool does too. An AI that confidently calls the wrong
tool with the wrong arguments isn't a typo in a file you can `git restore` it might be a row
tool with the wrong arguments isn't a typo in a file you can `git restore`; it might be a row
deleted from a database Git never backed up (Module 12's limit). Keep destructive tools behind
confirmation, scope them narrowly, and lean on the safety net: do this against test data first.
- **The AI still has to *choose* the tool correctly.** MCP gives the model hands; it doesn't give it
@@ -428,7 +446,7 @@ The honest caveats — and one of them is large enough that it gets its own modu
kills it.")
- **The spec and SDKs move fast.** This is expansion-zone material. Transport names, SDK APIs, and
config conventions have all churned and will again. The *client/server, servers-offer-clients-call*
model is durable; specific commands and field names are not verify them at build time.
model is durable; specific commands and field names are not, so verify them at build time.
- **stdio servers are local-only by nature.** The lab's server runs on your machine for you. Sharing
a server with a team, or reaching one that needs to run elsewhere, means the HTTP transport, which
drags in auth, network access, and the containerization story from Module 16. Don't reach for that
@@ -441,16 +459,16 @@ The honest caveats — and one of them is large enough that it gets its own modu
**You're done when:**
- (Optional, Part A) If you ran the warm-up, you connected an **existing** reference MCP server to
your agentic tool and watched the AI call one of its tools. Skipping it costs nothing Part C
your agentic tool and watched the AI call one of its tools. Skipping it costs nothing; Part C
connects the server you build and shows the same tool call.
- You built `tasks_mcp_server.py`, wired it into your tool, and saw the `tasks` server report as
connected with `list_tasks` and `add_task` available.
- You asked the AI a question and it answered by **calling a tool** against the live system, and you
asked it to add a task and then **verified the change outside the AI** by reading the runtime state
(`python cli.py list` / `cat tasks.json`) not `git diff`, because `tasks.json` is deliberately
(`python cli.py list` / `cat tasks.json`), not `git diff`, because `tasks.json` is deliberately
gitignored (Module 2).
- You can explain the client/server model in one breath *servers expose tools/resources/prompts;
the client (your agentic tool) discovers and calls them on the AI's behalf* and why "it's a
- You can explain the client/server model in one breath (*servers expose tools/resources/prompts;
the client (your agentic tool) discovers and calls them on the AI's behalf*) and why "it's a
protocol, not a vendor feature" means your server survives a model swap.
- You can state the one caveat this module defers: connecting an MCP server is running code with
access to your systems, and **Module 22** is where that risk gets handled.