Container/runner/MCP lab polish (#41,#42,#45,#46) (#65)
Co-authored-by: claude <claude@jpaul.io> Co-committed-by: claude <claude@jpaul.io>
This commit was merged in pull request #65.
This commit is contained in:
@@ -173,7 +173,11 @@ containerize and run the app you already have.
|
|||||||
- The `tasks-app` folder from Module 1 (`tasks.py`, `cli.py`).
|
- The `tasks-app` folder from Module 1 (`tasks.py`, `cli.py`).
|
||||||
- A container engine. **Docker Desktop** (macOS/Windows) or **Docker Engine** (Linux) is the common
|
- A container engine. **Docker Desktop** (macOS/Windows) or **Docker Engine** (Linux) is the common
|
||||||
choice; **Podman** works too and the commands below map 1:1 (`podman` for `docker`). Verify with
|
choice; **Podman** works too and the commands below map 1:1 (`podman` for `docker`). Verify with
|
||||||
`docker --version` (or `podman --version`).
|
`docker --version` (or `podman --version`). **The engine must be *running* before you build:**
|
||||||
|
`docker --version` reports the client version even when the engine is stopped, so it's false
|
||||||
|
reassurance — `docker build` then fails with "Cannot connect to the Docker daemon." On
|
||||||
|
macOS/Windows start it first (launch Docker Desktop, or `podman machine start`); confirm the daemon
|
||||||
|
is up with `docker info` (or `podman info`), which only succeeds when the engine is actually live.
|
||||||
- The starter files from this module's `lab/`: [`Dockerfile`](lab/Dockerfile) and
|
- The starter files from this module's `lab/`: [`Dockerfile`](lab/Dockerfile) and
|
||||||
[`dockerignore-starter`](lab/dockerignore-starter).
|
[`dockerignore-starter`](lab/dockerignore-starter).
|
||||||
- Your AI assistant.
|
- Your AI assistant.
|
||||||
@@ -234,6 +238,16 @@ containerize and run the app you already have.
|
|||||||
> Git Bash), or from PowerShell — `${PWD}` resolves correctly in each. The other `docker run`
|
> Git Bash), or from PowerShell — `${PWD}` resolves correctly in each. The other `docker run`
|
||||||
> commands mount nothing of yours and are identical everywhere.
|
> commands mount nothing of yours and are identical everywhere.
|
||||||
|
|
||||||
|
> **On native Linux:** the container runs as root by default, and the bind mount maps that straight
|
||||||
|
> onto your real project folder — so the `__pycache__` directories Python writes during the test
|
||||||
|
> run land in your repo owned by `root:root`, and you can't delete them without `sudo rm -rf`.
|
||||||
|
> Prevent it by telling Python not to write bytecode in the container: add
|
||||||
|
> `-e PYTHONDONTWRITEBYTECODE=1` to the `docker run` line (with pytest you'd also pass
|
||||||
|
> `pytest -p no:cacheprovider` to suppress `.pytest_cache`). A `.gitignore` won't help — it hides
|
||||||
|
> the files from Git but they're still on disk and still sudo-only to remove. Avoid `--user
|
||||||
|
> $(id -u):$(id -g)` here: it fixes ownership but breaks any in-container `pip install` into the
|
||||||
|
> image's root-owned site-packages.
|
||||||
|
|
||||||
This is, in miniature, exactly what containerized CI does. If it passes here, it passes the same
|
This is, in miniature, exactly what containerized CI does. If it passes here, it passes the same
|
||||||
way on any machine with the engine — your laptop's local Python version is now irrelevant.
|
way on any machine with the engine — your laptop's local Python version is now irrelevant.
|
||||||
|
|
||||||
|
|||||||
@@ -208,7 +208,10 @@ account. The five deploy steps are real; only the *target* is your laptop instea
|
|||||||
**You'll need:**
|
**You'll need:**
|
||||||
|
|
||||||
- A container runtime from Module 16 — Docker or Podman. (Commands below use `docker`; if you run
|
- A container runtime from Module 16 — Docker or Podman. (Commands below use `docker`; if you run
|
||||||
Podman, `alias docker=podman` or substitute.)
|
Podman, `alias docker=podman` or substitute.) As in Module 16, the engine must be **running**
|
||||||
|
before you build or deploy — on macOS/Windows start Docker Desktop (or `podman machine start`);
|
||||||
|
`docker --version` succeeds even when the engine is stopped, so confirm it's live with
|
||||||
|
`docker info` first, or `deploy.sh`'s build step fails with "Cannot connect to the Docker daemon."
|
||||||
- The `tasks-app` from Modules 1–2, now a Git repo.
|
- The `tasks-app` from Modules 1–2, now a Git repo.
|
||||||
- `curl` (for the health check) and a bash-capable shell. On Windows, use WSL or Git Bash.
|
- `curl` (for the health check) and a bash-capable shell. On Windows, use WSL or Git Bash.
|
||||||
- Your AI assistant — by now, ideally editor-integrated (Module 4).
|
- Your AI assistant — by now, ideally editor-integrated (Module 4).
|
||||||
|
|||||||
@@ -222,7 +222,9 @@ a repo also works). If a real runner is too heavy right now, Track A alone satis
|
|||||||
(the same place your Module 14 `ci.yml` lives — for Actions-style forges that's
|
(the same place your Module 14 `ci.yml` lives — for Actions-style forges that's
|
||||||
`.github/`/`.forgejo/`/`.gitea/` under `workflows/`; the file comments tell you where). Commit and
|
`.github/`/`.forgejo/`/`.gitea/` under `workflows/`; the file comments tell you where). Commit and
|
||||||
push. It runs the same lint-and-test as Module 14, then prints the runner's hostname, OS, user,
|
push. It runs the same lint-and-test as Module 14, then prints the runner's hostname, OS, user,
|
||||||
whether it looks ephemeral, and whether it can reach the public internet.
|
whether it looks ephemeral, and whether it can reach the public internet. The receipt step carries
|
||||||
|
`if: always()` so it still prints even when lint or test fail — a diagnostic shouldn't disappear on
|
||||||
|
a red build (the job still reports red). On GitLab CI the same idea is `when: always` on the job.
|
||||||
|
|
||||||
2. **Read the receipt.** Open the job logs on your forge and read the `Where did this run?` step.
|
2. **Read the receipt.** Open the job logs on your forge and read the `Where did this run?` step.
|
||||||
You're now able to answer, for a real job, the question this module opened with: *whose computer
|
You're now able to answer, for a real job, the question this module opened with: *whose computer
|
||||||
|
|||||||
@@ -44,7 +44,11 @@ jobs:
|
|||||||
run: pytest -q
|
run: pytest -q
|
||||||
|
|
||||||
# The point of THIS workflow: make the runner identify itself.
|
# The point of THIS workflow: make the runner identify itself.
|
||||||
|
# if: always() so the receipt prints even when Lint/Test fail above — a diagnostic step
|
||||||
|
# shouldn't vanish on a red build. The job still reports red; only this step is unconditional.
|
||||||
|
# (On GitLab CI the same idea is `when: always` on the job/step.)
|
||||||
- name: Where did this run?
|
- name: Where did this run?
|
||||||
|
if: always()
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
echo "=== runner identity ==="
|
echo "=== runner identity ==="
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ By the end of this module you can:
|
|||||||
1. Explain the MCP client/server model — what a server exposes (tools, resources, prompts), what the
|
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
|
client (your agentic tool) does, and why "it's a protocol, not a vendor feature" is the whole
|
||||||
point.
|
point.
|
||||||
2. Connect an existing MCP server to your agentic tool and confirm the AI can call its tools.
|
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.
|
||||||
3. Build a tiny MCP server in Python that exposes one real capability over the `tasks-app`, and wire
|
3. Build a tiny MCP server in Python that exposes one real capability over the `tasks-app`, and wire
|
||||||
it into your tool.
|
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
|
||||||
@@ -243,6 +244,9 @@ is the one that lands the concept.
|
|||||||
**Python packages and which `python`** note just below *before* you run `pip`.
|
**Python packages and which `python`** note just below *before* you run `pip`.
|
||||||
- The starter files in this module's `lab/` folder: `tasks_mcp_server.py` and
|
- The starter files in this module's `lab/` folder: `tasks_mcp_server.py` and
|
||||||
`mcp-config-example.json`.
|
`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.
|
||||||
|
|
||||||
> **Python packages and which `python`.** This lab's one dependency is the MCP SDK, and *how* you
|
> **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:
|
> install it decides whether the server ever connects. Two things bite people:
|
||||||
@@ -273,15 +277,22 @@ is the one that lands the concept.
|
|||||||
> ~/workflow-course/tasks-app/.venv/bin/python -c "import mcp; print('mcp ok')"
|
> ~/workflow-course/tasks-app/.venv/bin/python -c "import mcp; print('mcp ok')"
|
||||||
> ```
|
> ```
|
||||||
|
|
||||||
### Part A — Connect an existing server (warm-up, ~10 min)
|
### Part A — Connect an existing server (optional warm-up, ~10 min)
|
||||||
|
|
||||||
Before building anything, prove the plumbing works by connecting a server someone else already
|
This part is **optional**: it proves the plumbing works by connecting a server someone else already
|
||||||
wrote. The MCP ecosystem ships a set of **reference servers** (filesystem, fetch, git, and more) —
|
wrote, but it's a warm-up, not the load-bearing concept — Part B/C land that on the Python SDK you
|
||||||
pick a simple, read-only one your tool's docs point you at (a "filesystem" or "fetch" server is a
|
already installed. The catch is the runtime: most **reference servers** (filesystem, fetch, git, and
|
||||||
good first choice).
|
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.
|
||||||
|
|
||||||
|
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
|
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` and `args`.
|
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
|
2. Restart or reload your agentic tool so it picks up the config. Confirm it reports the server as
|
||||||
**connected** and lists its tools.
|
**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 — e.g. with a fetch server, *"fetch
|
||||||
@@ -429,8 +440,9 @@ The honest caveats — and one of them is large enough that it gets its own modu
|
|||||||
|
|
||||||
**You're done when:**
|
**You're done when:**
|
||||||
|
|
||||||
- You connected an **existing** MCP server to your agentic tool and watched the AI call one of its
|
- (Optional, Part A) If you ran the warm-up, you connected an **existing** reference MCP server to
|
||||||
tools (Part A).
|
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
|
- 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.
|
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
|
- You asked the AI a question and it answered by **calling a tool** against the live system, and you
|
||||||
@@ -461,8 +473,9 @@ MCP is moving fast; re-check these at build/publish time rather than trusting th
|
|||||||
- [ ] **The `mcpServers` config shape.** Confirm it's still the widely-shared convention for stdio
|
- [ ] **The `mcpServers` config shape.** Confirm it's still the widely-shared convention for stdio
|
||||||
servers, and that the `command`/`args` fields are current. Keep the lesson tool-agnostic about
|
servers, and that the `command`/`args` fields are current. Keep the lesson tool-agnostic about
|
||||||
*where* the config file lives.
|
*where* the config file lives.
|
||||||
- [ ] **Reference servers (Part A).** Verify which first-party reference servers exist and how
|
- [ ] **Reference servers (optional Part A).** Verify which first-party reference servers exist and
|
||||||
they're launched today; the catalogue and launch commands change. Don't name a specific server
|
how they're launched today; the catalogue and launch commands change. Don't name a specific
|
||||||
that may have moved or been retired without checking.
|
server that may have moved or been retired without checking. Confirm the named runtimes (`npx`
|
||||||
|
via Node, `uvx` via uv) are still how the common reference servers are distributed.
|
||||||
- [ ] **Adoption framing.** Re-confirm the "open standard, adopted across vendors regardless of
|
- [ ] **Adoption framing.** Re-confirm the "open standard, adopted across vendors regardless of
|
||||||
model" claim is still accurate and still vendor-neutral; update if the ecosystem has shifted.
|
model" claim is still accurate and still vendor-neutral; update if the ecosystem has shifted.
|
||||||
|
|||||||
Reference in New Issue
Block a user