あなたは最初、MCPは不要と言った!
EarendilはかつてPiがMCPをサポートしないと公言していたが、今ではそれをコアに組み込んだ。この記事では、この1年でMCPが何を変えたのか、なぜもはや単なる拡張機能では不十分なのか、そしてCodemodeとJevを使ってGitHub issueを処理する新しい方法を解説する。
日本語
コピー

最初は「MCP はいらない」と言っていたはずでは!
日付:2026年9月29日(火) 差出人:Earendil Engineering 宛先:あなた 件名:「MCP はいらないと言ったよね?」
以前 pi.dev を訪れたことがあるなら、誇らしげな一文を目にしたはずだ。Pi は MCP をサポートしない。Pi について話したポッドキャストを聴いたことがあるなら、MCP を快く思っていない発言を何度も耳にしたはずだ。Mario が書いた記事もある。ところが Pi をアップグレードすると、MCP は今やサポートされている機能のひとつに入っている。何があったのか?
物事は変わる
まず思い出すべきは、世界は止まっていないということだ。この一年ずっと MCP を追いかけてきたが、今の MCP は当時の MCP ではない。とはいえ、それだけではコアに入れる理由にはならない。知ってのとおり Pi には優れた拡張エコシステムがあり、MCP は拡張として作れるはずだ。Earendil が公式に推す拡張にしたって構わない。そのとおりで、MCP は拡張として作れるし、実際にそうしていた。今回 MCP がコアに入ったのは、一緒に考え直した結果だ。
いったい何が変わったのか
MCP をコアに入れた理由は、MCP 自体が変わったからだけではない。そのために必要な変更に、それ自体の価値があったからだ。たとえば MCP 向けに加えた変更は、Pi で Jev を使いやすくもした。突き詰めれば、Pi が必要としているものと MCP が必要としているものはよく似ている。いじり倒せるサンドボックス、その形がインタプリタだ。
MCP は良くなった点が多いが、変わっていない点も少なくない。最大の問題は今も組み合わせにくさにある。codemode——ツール呼び出しを組み合わせるための小さなサンドボックスにすぎない——が加わっても、MCP はこれを完全には解決できなかった。ただ、ここまで来ると問題は MCP そのものよりも、外にある MCP サーバーと、それらが各種 harness と協働するやり方の違いにある。
多くの MCP サーバーは今も、ツールをそのままコンテキストに流し込む harness を前提に作られており、テキストを返すことでトークン効率を稼ごうと自陣で工夫している。今の我々の MCP 観はこうだ。MCP は OpenAPI に近づき、賢いツール発見を備えるべきである。つまりツールは構造化データを返すべきで、ツールはそのドキュメントと説明を通じて発見できるべきだ。
CLI がこれほど使い勝手がいいのは、agent とモデルが効率的な bash の手口でつなぎ合わせれば済むからだ。だが根本的に言えば、MCP でも同じことができる。Pi の MCP は、これらのツールを JavaScript サンドボックスに公開する。Codex など他の harness と同じやり方だ。
現代の LLM における MCP
ここから当然の疑問が浮かぶ。なぜ MCP を捨てて Codemode だけにしないのか。答えの一部は、Pi が今ツールを表現する方法に関係している。ここ数か月、ツールの遅延読み込み、会話途中での system メッセージ挿入、推論レベルの調整に対応する新しいモデルに Pi を合わせるため、大量の作業をしてきた。だがツール設定の側はまだ更新されておらず、こうした新しい能力へ十分にスケールできていない。
Codemode の世界では、あるツールを LLM が使えるようにするのか、LLM のうち Codemode を担う部分だけが使えるようにするのかを決める必要がある。普通の MCP 拡張は、Pi のツール設定から十分なメタデータを得られず、これをうまくやれない。だからツールを遅延読み込み専用に、あるいは Codemode 専用に設定できるようにしなければならなかった。
メタデータをつなぎ込んで MCP 拡張を便利にするだけでもよかった。だが MCP と Codemode を組み合わせれば、MCP が長く抱えてきた問題の多くも解決できると考えている。あるものに良い影響を与える最善の方法は、それを受け入れることだ。今の MCP はかつてないほど良いが、サーバーとスキーマにはまだ改善の余地がある。だから傍観するのではなく、この議論に加わり、小さな harness でもうまく動くようにしたい。
Codemode とは何か
ここまで Codemode の話をしてきたのだから、それが何なのか説明すべきだろう。harness がツールを実行するとき、大きく二つの側がある。bash が動く側と、harness の agent loop が動く側だ。両者の信頼レベルは大きく違う。harness loop はたいてい信頼された環境で動き、そこで実行されるツールはたいていそれほど信頼されていないサンドボックスで動く。
Codemode が特殊なのは、harness のある側で動くことだ。ツール呼び出しを編成し調整する仕組みと考えるのが一番いい。agent がこれらのツール呼び出しをより柔軟に発行できるサンドボックスであり、呼び出し順は自分で決め、JavaScript で組み合わせる。Codemode も harness 側で動くため、その状態はファイルシステムではなくセッション記録の一部として保存される。
理論上はどの言語でもいいが、JavaScript はかなり魅力的だ。小さな JavaScript は WASM バイナリにまとめられ、ある程度の保護も得られる。
Pi では MCP を設定すると Codemode が自動で読み込まれるし、デフォルトのツールとして設定に加えることもできる。pi に自分を再設定させて codemode を有効にすればいい! そうすれば MCP に限らず、いろいろ面白いことができる。たとえば、ログインしている provider が「Jev」を提供しているなら、こう書ける:
codemode で typesafe/jev を呼び出し、issue tracker で一番キレているコメント者トップ20を洗い出す
Linear MCP と Jev などを賢く組み合わせてこの分析をこなし、Pi の中で直接実行。コンテキストは一切無駄にしない。
> Use typesafe/jev via codemode to find the most frustrated people on our issue tracker
I'll pull the open issues from Linear and let Jev rate the tone of each thread, four at a time.
codemode
const { issues } = await tools.mcp__linear__list_issues({
team: "Pi", state: "open", limit: 250,
});
const jev = await models.getModelOfType(
"classifier", "cloudflare-workers-ai", "typesafe/jev",
);
const questions = {
frustration: {
type: "choice",
instructions: "Judge ONLY the emotional tone of the people writing. " +
"Ignore how severe the bug is.",
criteria: {
none: "Neutral, factual, or friendly, even about a serious bug",
mild: "Explicit annoyance, impatience, or disappointment",
high: "Clearly angry, exasperated, sarcastic, or fed up",
},
},
};
const results = [];
let next = 0;
async function worker() {
while (next < issues.length) {
const issue = issues[next++];
const { comments } = await tools.mcp__linear__list_comments({
issueId: issue.identifier,
});
const c = await models.classify(jev, { state: { ...issue, comments }, questions });
results.push({ id: issue.identifier, title: issue.title, ...c.answers.frustration });
}
}
await Promise.all([worker(), worker(), worker(), worker()]);
store("frustration", results);
const score = (r) => r.probabilities.mild * 0.5 + r.probabilities.high;
const counts = {};
for (const r of results) counts[r.choice] = (counts[r.choice] ?? 0) + 1;
const flagged = results.filter((r) => r.choice !== "none");
flagged.sort((a, b) => score(b) - score(a));
return {
total: results.length,
counts,
flagged: flagged.map((r) => `${r.id} ${r.title}`),
};
... (331 earlier calls)
✓ mcp__linear__list_comments {"issueId":"PI-4714"} 255ms
✓ mcp__linear__list_comments {"issueId":"PI-3200"} 354ms
✓ models.classify cloudflare-workers-ai/typesafe/jev 756ms
✓ models.classify cloudflare-workers-ai/typesafe/jev 750ms
{
"total": 167,
"counts": {
"none": 156,
"mild": 11
},
"flagged": [
"PI-7740 TUI after /reload does not follows custom tool's re…",
"PI-9566 context size defaults to 128k despite the real size…",
"PI-10031 Pi sporadically stuck in \"Working...\" when thinking…",
"PI-4714 [feature] /update TUI command instead of pi update",
"PI-5916 Support provider extensions with model aliases and…",
"PI-7547 [Windows] [sink-thread] How do you use Pi on window…",
"PI-6907 Add an Installation section to the README",
"PI-6212 Proposal: Bedrock path should honor `compat.forceAd…",
"PI-9932 before_agent_start: forced system prompt keeps tool…",
"PI-7730 High CPU usage on Mac OS with long session",
"PI-6600 pi update --extensions and npm scripts: npm 11.16.0…"
]
}
Most of the tracker is calm. Jev rated 156 of the 167 open issues as neutral, 11
as mildly frustrated and none as highly frustrated.
The clearest cases:
- PI-6907 no installation section in the README (“It's frustrating”) - PI-10031
Pi gets stuck on “Working...” after pressing Esc during thinking - PI-4714 a
/update command request (“a pain in the butt”) - PI-7730 high CPU usage on macOS
in long sessions
The per-issue verdicts are stored in codemode under frustration, so I can dig
into any of them without fetching the issues again.
Pi でのセッションを簡略化して再生したもの。
Jev や Codemode については後で詳しく書く。この記事が、世界が進化し続ける中で Pi をどう慎重に調整し更新していくかの一例になればいいと思っている。