Skip to content

Commit e6bcfe7

Browse files
authored
Merge pull request #770 from hideyukiMORI/docs/mcp2-survey-and-estimate-correction
docs: mcp 2.0 移行の実測調査を記録し、過大見積もりを訂正する (v1.8.168)
2 parents 017c01b + 27bc8f5 commit e6bcfe7

5 files changed

Lines changed: 270 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,32 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
88
99
---
1010

11+
## [1.8.168] — 2026-08-12
12+
13+
セッションの実測を記録として残し、mcp 2.0 移行規模の過大見積もりを訂正した。
14+
15+
### Documentation
16+
- [日報 2026-08-12](docs/review/2026-08-12.md) を追加。**§2 は mcp 2.0 移行の設計ノート**で、
17+
依存の実体・2.0.0 での変更点・落ちた 4 本のテストが守っていた保証・移行方針 4 案と推し・
18+
測定の再現手順を収録。移行ボール着手時の一次資料になる。
19+
- 🔴 **訂正**: v1.8.167 で mcp 2.0 移行を「`src/nene2/mcp/``src/example/mcp.py`
20+
全面書き換えが必要」と見積もったが、**これは過大評価であり誤りだった**
21+
根拠が「`uv sync` 後にテストが 4 本落ちた」ことだけで、依存の実体を測っていなかった。
22+
実測すると `src/` 側の fastmcp 依存は **`server.py` の 1 行のみ**で、4 本が落ちたのは
23+
`nene2/mcp/__init__.py` の import カスケード。**実際の移行規模は概算 11 行**
24+
`docs/todo/current.md` と本 CHANGELOG の該当箇所に、訂正の事実と根拠を残す形で記載した。
25+
訂正後の数字は着手判断に、訂正前の失敗(測らずに見積もった)は次に見積もりを立てるときに使う。
26+
- `docs/todo/current.md` に mcp バージョン境界の決定が移行の着手条件である旨を明記。
27+
本移行も `mcp` 下限の引き上げも [ADR-0011](docs/adr/0011-mcp-as-core-dependency.md) の決定
28+
(MCP をコア依存に置く)とは矛盾しない — 動くのはバージョン境界であってコア依存かどうかではない。
29+
30+
### Changed
31+
- リポジトリ整理: マージ済みブランチ 24 本(リモート 4 / ローカル 20)と
32+
前セッションの locked worktree 4 本を削除。いずれも `git cherry` と内容比較で
33+
main への取り込みを確認済み。
34+
35+
---
36+
1137
## [1.8.167] — 2026-08-12
1238

1339
依存関係の既知脆弱性 22 件を `uv.lock` の更新のみで閉鎖し、CI を緑に戻した。
@@ -34,8 +60,10 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
3460
- `pyproject.toml`**依存宣言は無変更**(すべて下限のみのため `uv.lock` の解決だけで到達。
3561
変更は `version` の 1.8.166 → 1.8.167 のみ)。したがって PyPI 配布物の依存要件は変わらない。
3662
- `mcp`**1.x 系最新の 1.29.0 で止めた**。2.0.0 は `mcp.server.fastmcp` モジュールを
37-
削除しており `src/nene2/mcp/``src/example/mcp.py` の全面移行が必要になるため、
38-
別タスクとして分離した。advisory の要求は 1.28.1 以上であり 1.29.0 で充足する。
63+
削除しているため、別タスクとして分離した。advisory の要求は 1.28.1 以上であり 1.29.0 で充足する。
64+
> 🔴 **本項の当初記述を v1.8.168 で訂正**: ここに「`src/nene2/mcp/``src/example/mcp.py`
65+
> 全面移行が必要」と書いたが、依存の実体を測らずに出した過大な見積もりだった。
66+
> 実測の移行規模は概算 11 行(詳細は [日報 2026-08-12 §2](docs/review/2026-08-12.md))。
3967
4068
---
4169

docs/review/2026-08-12.md

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
# 日報 — 2026-08-12
2+
3+
**作業者**: hideyukiMORI
4+
**バージョン**: v1.8.168(セッション終了時点)
5+
6+
---
7+
8+
## 本日の作業サマリー
9+
10+
2026-05-30 を最後に約 2 ヶ月半コミットが無く、その間 CI も一度も走っていなかった。
11+
**コードは 1 行も変わっていないのに、上流の advisory データのほうが動いて CI が赤になっていた。**
12+
13+
本日はその赤を閉じ、待たされていた PR をマージし、次に来る mcp 2.0 移行の実測調査を行った。
14+
15+
### 完了した PR(2 本)
16+
17+
| PR | 内容 | 種別 |
18+
|---|---|---|
19+
| #769 | chore(deps): 依存の既知脆弱性 22 件を解消し CI を緑に戻す(v1.8.167) | security |
20+
| #767 | chore(deps): bump nanoid to 3.3.18 in lockfile(GHSA-2v37-7h3g-55p8) | security |
21+
22+
`#767` は 2ヶ月半前から存在した pip-audit の既存赤に阻まれて open のままだった。
23+
`#769` で赤を閉じてから rebase して通した。
24+
25+
---
26+
27+
## 1. 依存の既知脆弱性 22 件(v1.8.167)
28+
29+
`pip-audit` の出力は **27 行**だが、`pip` / `pyjwt` / `starlette` に重複行があり
30+
**ユニークでは 22 件 / 8 パッケージ**
31+
32+
| パッケージ | 変更 | 解消した ID | 件数 |
33+
|---|---|---|---|
34+
| pyjwt | 2.12.1 → 2.13.0 | PYSEC-2026-175 / 176 / 177 / 178 / 179 | 5 |
35+
| cryptography | 48.0.0 → 50.0.0 | PYSEC-2026-3552 / 3553 / 3554・GHSA-537c-gmf6-5ccf | 4 |
36+
| starlette | 1.0.1 → 1.6.0 | PYSEC-2026-248 / 249 / 2280 / 2281 | 4 |
37+
| mcp | 1.27.1 → 1.29.0 | PYSEC-2026-3481 / 3482 / 3483 | 3 |
38+
| python-multipart | 0.0.29 → 0.0.32 | PYSEC-2026-3036 / 3037 / 3040 | 3 |
39+
| msgpack | 1.1.2 → 1.2.1 | PYSEC-2026-3625 | 1 |
40+
| pydantic-settings | 2.14.1 → 2.15.0 | GHSA-4xgf-cpjx-pc3j | 1 |
41+
| pip | 26.1.1 → 26.2.1 | PYSEC-2026-196 | 1 |
42+
43+
- **`uv.lock` の更新のみで全件解消**。依存宣言はすべて下限のみだったため `pyproject.toml`
44+
依存宣言は無変更(変更は `version` のみ)= **PyPI 配布物の依存要件は変わっていない**
45+
- **新規の `--ignore-vuln` はゼロ。** 除外は既存の `PYSEC-2025-183` 1 件のみ据え置き。
46+
- 段階更新(①低リスク 5 点 → ②cryptography → ③starlette → ④mcp)で進め、
47+
各段で `pytest` を実行。**④で実際に落ちた**ので、そこで止めて原因を切り分けた(§2)。
48+
49+
### 根本原因
50+
51+
コードは 2026-05-30 以降変更されていない。**変わったのは上流の advisory データのほう。**
52+
`uv.lock` を据え置いたまま CI が一度も走らなかったので、脆弱性だけが静かに積み上がった。
53+
54+
**凍結中のプロジェクトほど静かに腐る。** これは「コードを変えない限り安全」という
55+
直感が成り立たない領域で、定期再検査の装置が無いことの帰結である。
56+
再発防止は `docs/todo/current.md` の優先タスクに起票した。
57+
58+
---
59+
60+
## 2. mcp 2.0 移行の実測調査
61+
62+
`uv lock --upgrade-package mcp`**2.0.0(メジャー跨ぎ)** まで上げるが、テストが落ちる。
63+
advisory の要求は 1.28.1 以上なので **1.29.0 で停止**し、移行は別タスクへ分離した。
64+
65+
> 🔴 **見積もりの訂正 — 測らずに出した数字を記録として残す**
66+
>
67+
> 当初この移行を **`src/nene2/mcp/``src/example/mcp.py` の全面書き換えが必要」**
68+
> 見積もった。**これは過大評価であり、誤りだった。**
69+
>
70+
> そう書いた根拠は「`uv sync` 後に 4 本のテストが収集エラーになった」ことだけで、
71+
> **依存の実体を測っていなかった**`grep` 2 発で確かめられることを確かめずに重い見積もりを
72+
> 出すと、その見積もり自体が着手を止める装置になる。
73+
>
74+
> 実測すると `src/` 側の fastmcp 依存は **`server.py` の 1 行のみ**
75+
> 4 本が落ちたのは `nene2/mcp/__init__.py``server.py` を import するカスケードであって、
76+
> 4 本それぞれが fastmcp を使っているからではない。
77+
> **実際の移行規模は概算 11 行。**
78+
>
79+
> 訂正後の数字(11 行)は着手判断に使う。訂正前の失敗(測らずに見積もった)は
80+
> 次に見積もりを立てるときに使う。**両方を残す。**
81+
82+
### 2-1. 依存の実体
83+
84+
```
85+
$ grep -rn 'fastmcp' src/ tests/
86+
src/nene2/mcp/server.py:10: from mcp.server.fastmcp import FastMCP
87+
tests/example/test_http_mcp_parity.py:23: from mcp.server.fastmcp.exceptions import ToolError
88+
```
89+
90+
| ファイル | 行数 | fastmcp 依存 |
91+
|---|---|---|
92+
| `src/nene2/mcp/__init__.py` | 12 | なし(re-export のみ) |
93+
| `src/nene2/mcp/server.py` | 48 | 🔴 **唯一の依存点** |
94+
| `src/nene2/mcp/http_client.py` | 119 | なし(httpx のみ) |
95+
| `src/example/mcp.py` | 147 | なし(`LocalMcpServer` 越し・15 ツール登録) |
96+
97+
**`LocalMcpServer`(48 行)がアンチコラプションレイヤーとして機能している。**
98+
`example/mcp.py` の 15 ツールも、MCP 系テスト 22 本も、すべてこの 48 行の裏側に守られており、
99+
**メジャー更新の衝撃が 1 ファイルに閉じている。** 元の設計が正しかったことの実証データである。
100+
101+
### 2-2. 2.0.0 で実際に変わったもの(隔離 venv で実測・Python 3.14 / uv 0.11.15)
102+
103+
| 1.x | 2.0.0 |
104+
|---|---|
105+
| `mcp.server.fastmcp` | **削除**`mcp.server.mcpserver`(パッケージごと改名) |
106+
| `FastMCP` | `MCPServer` |
107+
| `mcp.server.fastmcp.exceptions.ToolError` | `mcp.server.mcpserver.exceptions.ToolError`(クラス名は同一) |
108+
109+
`LocalMcpServer` が使っている 4 機能の移行可否:
110+
111+
| 使用箇所 | 2.0.0 での状況 | 判定 |
112+
|---|---|---|
113+
| `FastMCP(name, instructions=, host=, port=)` | `MCPServer(...)`**`host` / `port` はコンストラクタから `run()` へ移動**(消滅ではない) | 🟡 要追随 |
114+
| `.tool(description=...)` | キーワード互換 | 🟢 |
115+
| `._tool_manager.list_tools()` | **同名・同挙動で残存** | 🟢(ただし私有 API) |
116+
| `.run(transport=...)` | `transport` ごとの overload。`sse` / `streamable-http``host=` `port=` を受ける | 🟡 |
117+
118+
### 2-3. 唯一の実質的な破壊点 — `call_tool()` の戻り値
119+
120+
| | 1.x | 2.0.0 |
121+
|---|---|---|
122+
| 戻り値 | content block の列(tuple のことがある) | **`CallToolResult`** |
123+
| フィールド || `meta` / `content` / `structured_content` / `is_error` / `result_type` |
124+
| 本文の取り出し | `raw[0] if isinstance(raw, tuple) else raw` | `result.content` |
125+
126+
- **「1 要素 1 content block」という性質は 2.0 でも成立**(実測)。
127+
- **ツール内例外は 2.0 でも `ToolError` で送出される**`is_error` フラグに落ちるのではない)。
128+
実測: `ToolError: Error executing tool boom: bad input`
129+
- = パリティテストの異常系アサーションは **import パスの変更だけで生き残る**
130+
131+
### 2-4. 落ちた 4 本が守っていた保証
132+
133+
| テスト | 件数 | 守っている保証 | 移行後 |
134+
|---|---|---|---|
135+
| `tests/nene2/mcp/test_server.py` | 5 | `LocalMcpServer` の公開契約(登録したツールが `list_tools()` に名前で現れる・デコレータ付き関数が素の関数としても呼べる) | **変更不要**。移行の合否判定器になる |
136+
| `tests/example/test_mcp.py` | 3 | Note の作成→取得→更新→削除ライフサイクル | **変更不要** |
137+
| `tests/nene2/mcp/test_http_client.py` | 14 | `HttpxMcpClient` の HTTP 挙動 | **変更不要**(fastmcp と無関係・巻き添えで落ちただけ) |
138+
| `tests/example/test_http_mcp_parity.py` | 5 | 🔴 **差別化そのもの**(同一ストア上での両サーフェス等価性・巨大 title / 空 body が両方で拒否される) | **要修正 3 行** |
139+
140+
パリティテストが守る不変条件は **UseCase の Input DTO 側にあって MCP ライブラリ側には無い**
141+
(v1.8.166)。だから mcp のメジャー更新で保証が揺らがない。移行が小さく済む理由もここにある。
142+
143+
### 2-5. 依存の増分
144+
145+
2.0.0 は **`httpx2` へ乗り換える**。1.29.0 → 2.0.0 で:
146+
147+
- **追加**: `httpx2` / `httpx2-jsfetch` / `mcp-types==2.0.0`(完全一致ピン)/ `opentelemetry-api` / `truststore`
148+
- **削除**: `httpx-sse`
149+
- `requires-python``>=3.10` のまま = **3.12 / 3.14 サポートに影響なし**
150+
151+
自艦は `httpx>=0.27` を直接依存に持つため、2.0 を入れると **httpx と httpx2 が同居する**
152+
(競合はしない)。「httpx2 へ寄せるか」は本移行とは**別軸の判断**として記録しておく。
153+
154+
### 2-6. 移行方針(案と推し)
155+
156+
|| 内容 | 代償 |
157+
|---|---|---|
158+
| A | import パスと host/port だけ直す(約 11 行) | `_tool_manager` 私有 API 依存が残る |
159+
| **B-2****推し**| A + ツール名を自前で保持し私有 API 依存を外す | デコレータのラップ数行 |
160+
| B-1 | `list_tools()` を 2.0 の async 公開 API に寄せる | **公開 API の破壊的変更**。得られるのは体裁だけ |
161+
| C | `lowlevel` に降りて fastmcp 相当を自前実装 | 🔴 スキーマ生成・引数検証・content 変換を全部抱える。**「薄く包む」方針の変更** |
162+
| D | 移行せず 1.x に留まる | 上流の 1.x サポート終了まで先送り |
163+
164+
**推しは B-2。** `_tool_manager` は 2.0 でたまたま同名で生き残っただけで契約されていない。
165+
パッケージ名ごと作り替える上流である以上、次も残る保証がない。
166+
**同じ移行を 2 回やらないために、今回のついでに外す。**
167+
168+
見積もり: `server.py` 約 15 行 + パリティテスト 3 行 = **半日以内**
169+
既存 27 本のテストがそのまま合否判定器になる。
170+
171+
### 2-7. 着手条件
172+
173+
🔴 **`pyproject.toml``mcp` バージョン境界をどうするかの決定が先。**
174+
`mcp>=2` に上げるのか、`[tool.uv] constraint-dependencies` で 1.x に留めるのかで、
175+
**この移行の着手是非そのものが決まる。**
176+
177+
1.x に固定するなら、本移行は**上流の 1.x サポート終了まで塩漬けが正しい**
178+
(advisory は 1.29.0 で全て閉じているので急ぐ理由がない)。
179+
180+
なお本移行も `mcp` 下限の引き上げも、**[ADR-0011](../adr/0011-mcp-as-core-dependency.md)
181+
(MCP をコア依存として含める)の決定とは矛盾しない** — 動くのはバージョン境界であって、
182+
コア依存かどうかではない。着手時の ADR は ADR-0011 を上書きするのではなく、その上に積む形になる。
183+
184+
### 2-8. 測定の再現手順(次に読む人へ)
185+
186+
```bash
187+
mkdir /tmp/mcp20 && cd /tmp/mcp20
188+
uv venv -p 3.14 && uv pip install "mcp==2.0.0"
189+
ls .venv/lib/python3.14/site-packages/mcp/server/ # fastmcp が無いことを確認
190+
```
191+
192+
`mcp.server.mcpserver` から `MCPServer` を import し、`.tool()` で 2 本登録して
193+
`_tool_manager.list_tools()` が名前を返すこと、`call_tool()``CallToolResult` を返すこと、
194+
ツール内例外が `ToolError` になることを確認する。
195+
196+
⚠️ **本節の数値は 2026-08-12 時点の `mcp==2.0.0` の実測。** 着手時には版が進んでいる
197+
可能性があるので、`mcp.server.mcpserver` の存在と `call_tool()` の戻り値型だけは
198+
**測り直してから**書き始めること。
199+
200+
---
201+
202+
## 3. リポジトリの整理
203+
204+
- **マージ済みリモートブランチ 4 本**`docs/translate-{de,fr,pt-br,zh}`)を削除。
205+
- **locked worktree 4 本**`.claude/worktrees/agent-*`)を削除。未コミット成果物が無いことを
206+
`git status --porcelain --ignored` で確認してから外した。
207+
- **ローカルブランチ 20 本**を削除。リモート・ローカルとも `main` のみになった。
208+
209+
> **測り方の教訓**: 当初 `git diff origin/main...<branch>` でマージ済み判定を行い、
210+
> 「10 本は差分が残っていて消せない」と誤判定した。三点 diff は**マージベース以降に
211+
> ブランチ側で入った変更**を出すので、main が squash merge で同じ内容を持っていても
212+
> 差分として見える。`git cherry`(patch-id 照合)と内容比較で測り直したところ、
213+
> **未取り込みは実際にはゼロ**だった。
214+
> **マージ済み判定に三点 diff を使わない。`git branch --merged` も squash merge を
215+
> 未マージと報告するので同様。** 使うのは `git cherry` か内容比較。
216+
217+
---
218+
219+
## 4. 明日以降の方針
220+
221+
| 優先度 | タスク |
222+
|---|---|
223+
|| **mcp 2.0 移行**(本日の調査 §2 が設計ノート。バージョン境界の決定が着手条件) |
224+
|| **依存の定期再検査を仕組み化**`schedule:` トリガー CI か月次 `uv lock --upgrade` 運用) |
225+
| 判断待ち | `pyproject.toml``mcp` / `python-multipart` / `pydantic-settings` の下限引き上げ(利用者影響あり) |
226+
|| PyJWT 推移的 CVE(PYSEC-2025-183)— mcp 修正待ち |
227+
228+
オープン Issue / PR: ゼロ。main クリーン。**CI グリーン**(3.12 / 3.14・実 DB 統合テスト含む)。

0 commit comments

Comments
 (0)