Skip to main content
Glama
README.md
# 從 MCP 到 Multi-Agent:用一台本機 GPU 走完 30 天

> iThome 鐵人賽 2026 · 全程零 API 成本,所有程式碼都在本機模型上真的跑過。

這個 repo 是整個系列的**程式碼與實測數據**。文章本身發表在 iThome,
不放在這裡(同一份內容放兩個地方會變成重複內容)。

`outputs/` 底下那 11 份 `.txt` 是關鍵:文章裡每一段「執行結果」都不是手打的,
是用 `scripts/run_day.sh` 從真機抓回來、再原封不動貼上去的。
你可以拿它們跟自己機器上跑出來的數字對照。

---

## 這個系列在做什麼

30 天疊出一個叫 **devbench** 的東西:一個本機開發者工作台。
它從一個 MCP Server 開始,長成一群會分工的 Agent。

```
Day 1-5    Python 地基      型別 / async / 裝飾器 —— 全部是 MCP SDK 的語法基石
Day 6-10   MCP 協定         三大原語 / JSON-RPC / 傳輸層 / 寫出第一個 Server
Day 11-20  Agent 框架       安全治理 / ADK / LangGraph / ReAct / Plan-and-Execute
Day 21-30  Multi-Agent      Supervisor / 記憶 / 評估 / OpenClaw / NVIDIA PAIR
```

### 兩條伏筆

**① 一個 endpoint 走到底。**
從 Day 5 起,全專案只有 `src/ironman/config.py` 一個地方知道推論端點在哪。
Day 29 裝上 NVIDIA PAIR 之後,同一個 port 會變成整個區網的推論叢集,
而前面 28 天的程式碼**一行都不用改**。
`tests/test_config.py` 有一條測試在守這件事——誰把端點寫死,CI 就擋誰。

**② Day 1-4 不是無關的 Python 教學。**

| Day | 語法主題 | 在 Day 10 的回收處 |
|---|---|---|
| 2 | 型別提示 / dataclass / Pydantic | tool 的 input schema 就是 `model_json_schema()` |
| 3 | async / await / asyncio | MCP 協定全非同步 |
| 4 | 裝飾器 / context manager / generator | `@mcp.tool()`、lifespan、streaming 的真面目 |

---

## 環境

這個系列跑在一台 **NVIDIA GB10(DGX Spark 級)** 上:

```
Ubuntu 24.04.4 LTS / aarch64 · 20 核 · 121 GB 統一記憶體
NVIDIA GB10, driver 580.142
Ollama 0.32.14
```

但你不需要同款機器。除了 Day 28-29 的多節點實測之外,
任何跑得動 Ollama 的機器都能完整重現——把模型換小一點就行。

### 模型選型

不是拍腦袋決定的,是 Day 1 實測六個模型跑出來的結果
(完整數據見 [`outputs/day01.txt`](outputs/day01.txt)):

| 角色 | 模型 | 為什麼 |
|---|---|---|
| 主力 | `nemotron-3.5-lightning:30b` | 86.4 tok/s,而且是唯一答對「MCP 是什麼」的模型 |
| 小模型 | `llama3.2:3b` | 59.7 tok/s,夠小才開得起高並行(Day 3 / 22 / 29) |
| 對照組 | `gpt-oss:20b` | 交叉驗證 tool schema 沒有綁死單一模型 |

---

## 開始

```bash
# 1. 裝 uv(user-level,不碰系統 Python)
curl -LsSf https://astral.sh/uv/install.sh | sh

# 2. 建環境
git clone git@github.com:jrlinjr/local-mcp-agent-30days.git
cd local-mcp-agent-30days
uv sync

# 3. 確認 Ollama 通了、模型都在
uv run ironman doctor

# 4. 隨便問一句
uv run ironman chat "用一句話說明 MCP" --stream
```

### 跑每一天

```bash
uv run python days/day01_bench/main.py          # 模型選型實測
uv run python days/day02_typing/main.py         # dataclass vs Pydantic
uv run python days/day03_async/main.py          # 並行度掃描
uv run python days/day04_decorators/main.py     # 手刻 @tool 裝飾器
uv run python days/day05_first_llm/main.py      # 第一支 LLM 呼叫
uv run python days/day07_primitives/main.py     # MCP 三大原語
uv run python days/day08_jsonrpc/raw_client.py  # 手刻 JSON-RPC(不用 SDK)
uv run python days/day08_jsonrpc/raw_client.py --sdk   # 手刻 client 打官方 server
uv run python days/day09_transport/main.py      # stdio vs Streamable HTTP
uv run python days/day10_mcp_server/client_demo.py     # devbench MCP Server

uv run pytest -q                                # 全部測試
```

### 把 devbench 掛進 Claude Code

repo 根目錄已經有一份 `.mcp.json`:

```json
{
  "mcpServers": {
    "devbench": {
      "command": "uv",
      "args": ["run", "python", "days/day10_mcp_server/server.py"]
    }
  }
}
```

在專案目錄開 Claude Code 就會自動載入,`devbench` 的四個工具直接可用。

---

## 遠端開發:本機編輯,遠端執行

如果你的 GPU 機器不是你打字的那台(我就是),
`scripts/` 底下兩支腳本讓你不用在筆電裝任何東西:

```bash
cp .env.example .env.local    # 填入你的遠端主機(.env.local 不進版控)
bash scripts/sync.sh          # 本機 → 遠端(rsync,本機是唯一權威來源)
bash scripts/run_day.sh 03    # 在遠端跑 day03,輸出抓回 outputs/day03.txt
```

主機名刻意不寫死在版控裡——它是內部基礎設施資訊,不該出現在公開 repo。
`run_day.sh` 抓回來的輸出會先過 `scripts/scrub.py`,把絕對路徑與主機名換成
`<專案根目錄>` 之類的中性字樣,因為那些輸出會被原封不動貼進文章。

---

## 把關機制

```bash
uv run pytest -q                      # 36 項端到端測試,不 mock
python3 scripts/verify_outputs.py     # 文章數字 vs outputs/ 逐字比對
```

**`verify_outputs.py`** 掃過每篇文章的程式碼區塊,把有數字的實測宣稱(tok/s、秒數、byte 數、pid、JSON-RPC 訊息框)挑出來,到 `outputs/` 裡逐字比對,找不到就失敗。程式重跑之後數字會變,這條檢查會逼我把文章一起更新,而不是留著舊值。

文章不在這個 repo 裡,所以 clone 下來跑它會說「沒有文章可查核」,那是正常的。留著它是為了說清楚 `outputs/` 裡那些數字是怎麼被把關的。

**`tests/test_config.py`** 裡有一條測試在守伏筆一:全專案只有 `config.py` 可以寫死推論端點,誰違反誰就掛。

**`scripts/scrub.py`** 在擷取輸出時自動抹掉絕對路徑與主機名——那些輸出會被原封不動貼進文章,不該帶著我的使用者名稱跑出去。

---

## 每日索引

文章發表於 iThome 鐵人賽 2026。這裡列出對應的程式碼與實測輸出。

| Day | 主題 | 程式碼 | 實測輸出 |
|---|---|---|---|
| 1 | 開賽宣言:模型選型實測 | [`day01_bench`](days/day01_bench/) | [day01.txt](outputs/day01.txt) |
| 2 | 型別提示、dataclass 與 Pydantic | [`day02_typing`](days/day02_typing/) | [day02.txt](outputs/day02.txt) |
| 3 | async / await 與 asyncio | [`day03_async`](days/day03_async/) | [day03.txt](outputs/day03.txt) |
| 4 | 裝飾器、Context Manager、Generator | [`day04_decorators`](days/day04_decorators/) | [day04.txt](outputs/day04.txt) |
| 5 | 用 uv 建專案 + 呼叫第一支 LLM API | [`day05_first_llm`](days/day05_first_llm/) | [day05.txt](outputs/day05.txt) |
| 6 | MCP 是什麼:Host / Client / Server | 觀念篇 | — |
| 7 | MCP 三大原語:Tools、Resources、Prompts | [`day07_primitives`](days/day07_primitives/) | [day07.txt](outputs/day07.txt) |
| 8 | JSON-RPC 2.0 與 MCP 訊息生命週期 | [`day08_jsonrpc`](days/day08_jsonrpc/) | [day08.txt](outputs/day08.txt) · [day08_sdk.txt](outputs/day08_sdk.txt) |
| 9 | 傳輸協定:stdio vs Streamable HTTP | [`day09_transport`](days/day09_transport/) | [day09.txt](outputs/day09.txt) |
| 10 | 實作:用 Python SDK 寫一個 MCP Server | [`day10_mcp_server`](days/day10_mcp_server/) | [day10.txt](outputs/day10.txt) · [測試](outputs/day10_tests.txt) |

---

## 目錄

```
src/ironman/       共用地基
  config.py        ★ 唯一的設定來源(Day 29 的伏筆)
  models.py        Day 2 的資料模型,後面一路沿用
  llm.py           ★ 全系列唯一打 Ollama 的地方
  cli.py           uv run ironman ...
days/              每一天的可執行範例
outputs/           真實執行輸出(文章裡每個數字的來源)
tests/             端到端測試,不 mock
scripts/           遠端執行、輸出清洗、數字查核
```

## 版本注意

MCP Python SDK 用的是 **2.x**。v1 的 `from mcp.server.fastmcp import FastMCP`
已改名為 `from mcp.server.mcpserver import MCPServer`,
且回應物件的欄位從 camelCase 改成 snake_case
(`protocolVersion` → `protocol_version`)。
網路上多數教學還停在 v1,照抄會直接爆——Day 10 有完整說明。

---

## 授權

程式碼採用 [MIT License](LICENSE),隨你使用、修改、散布。

文章本身不在這個 repo 裡(發表於 iThome),著作權另計。
若你在自己的文章或簡報引用這裡的實測數據,附上出處連結就好。

TDQS

A3.9/5.0

Scored across 4 tools

Disambiguation5/5

Each tool performs a distinct operation: listing files, running tests, viewing git history, and reading file contents. There is no overlap or ambiguity between any pair of tools.

Naming Consistency4/5

The verb_noun pattern is mostly consistent (list_files, run_tests, read_file), but git_log deviates slightly as it reads more like a noun compound than a verb phrase. Overall, naming is clear and predictable.

Tool Count5/5

Four tools is a well-scoped set for a development inspection environment. Each tool serves a clear purpose without redundancy, and the count feels neither too thin nor bloated.

Completeness4/5

The tool set covers the core read-only workflows: enumerating, reading, testing, and version tracking. Missing write/edit capabilities could be a gap in some augmentation contexts, but for a benchmark or inspection tool, this surface is mostly sufficient.

Maintenance

ActivityMaintained
ResponsivenessNo issues