- ESP-IDF 6.0 の MCP サーバは、Windows で「処理は成功するのに完了応答が返らない」ことがある。その現実解として、Claude Code のスキルに
idf.pyを直接叩かせました - 設計原則は5つ。すべて「実際に起きた事故」から導いたルールで、それぞれに理由があります
- 実戦テストは失敗込みで公開。自然言語1文から、COM ポート列挙 → ビルド → 書き込み(1回目は失敗、原因を切り分けて成功)まで一気通貫で通りました
🧭 はじめに:完了応答が返らないなら、直接叩けばいい
前記事の Claude Code × ESP32|ESP-IDF 6.0 の MCP サーバ活用ガイド で、ひとつ大きな問題にぶつかりました。ESP-IDF 6.0 内蔵の MCP サーバは、build_project や flash_project を呼ぶと実処理そのものは成功するのに、MCP のタスクが完了扱いにならず、応答がクライアントに返ってこない——という症状です(Windows での再現。詳しくは前記事へ)。
つながらないわけでも、ビルドが失敗するわけでもありません。「動いているのに、終わったと言ってくれない」。これでは AI クライアントから安定して回せません。
そこで発想を変えます。MCP サーバを経由せず、AI に idf.py を直接叩かせればいい。 ちょうど Claude Code には、こういう「決まった手順」を教え込む スキル という仕組みがあります。この記事は、その スキル を実際に作って ESP32-S3 で検証した記録です。検証はすべて 2026-07-30・Windows 11 で実機で行いました。
🛠️ Claude Code の「スキル」とは(3分で分かる基礎)
スキル は、Claude Code に「特定の作業のやり方」をあらかじめ教えておく仕組みです。.claude/skills/<スキル名>/SKILL.md というファイルに、Markdown で手順を書いておきます。
- frontmatter の
descriptionがトリガー — 依頼内容がこの説明に合致すると、スキルが自動的に発動します - 本文が手順書 — 使うコマンド、注意点、やってはいけないことを書いておくと、Claude Code はそれに従って動きます
- 置き場所は2種類 — プロジェクト単位なら
.claude/skills/、どのプロジェクトでも効かせたいならユーザーレベルの~/.claude/skills/(Windows では%USERPROFILE%\.claude\skills\)
ポイントは、スキルは「環境の癖」を1回だけ教える場所だということです。「このマシンでは idf.py は素のシェルから呼べない」「eim run を通す」「COM ポートはこう確認する」——こうしたそのマシン固有の作法を毎回説明する代わりに、スキルに書いておけば、以後は自然言語で頼むだけで済みます。
今回は「ESP32 のビルド・書き込みを頼まれたら、MCP を使わず eim run で idf.py を直接実行する」というスキルを、ユーザーレベルに作りました。全体の流れは、次のようにとてもシンプルです。
MCP サーバのような常駐プロセスも、専用の登録も要りません。依頼 → スキル発動 → シェル実行 → 成果物確認という一直線の流れだけです。
ここで、大事な点を先にはっきりさせておきます。この方法は、MCP を一切使っていません。スキルは Claude Code に渡す「指示書」にすぎず、実際に走っているのは eim run(=ふつうのシェル)です。MCP サーバの起動も、claude mcp add での登録も要りません。
前記事で扱った「MCP 経由」と、本記事の「スキル+シェル直行」は、経路そのものが違います。並べると、違いが一目で分かります。
| MCP 経由 | スキル+シェル直行(本記事) | |
|---|---|---|
| 経路 | Claude Code → MCP サーバ(idf.py mcp-server)→ idf.py |
Claude Code → eim run → idf.py |
| 仕組み | 型付きのツール呼び出し(JSON-RPC / stdio) | スキル(指示書)に沿ってシェルコマンドを実行 |
| メリット | 標準化・型付き・どのクライアントからも同じ使い方 | 常駐プロセス不要・詰まる経路を回避・すぐ使える |
| 現状の安定性(Windows・2026-07-30) | 重い処理で完了応答が返らないことがある | 一気通貫で安定(実戦テストで確認) |
つまり本記事は、MCP の代わりに、AI へ「シェルの叩き方」を教えておくアプローチです。次の節では、そのルールの中身を見ていきます。
🧩 スキル設計の5原則(すべて「実際に起きた事故」から)
スキルは、最初から完璧には書けません。今回の5つのルールは、検証中に実際につまずいた出来事を、そのままルールに落としたものです。テンプレートの一般論ではなく、事故の再発防止策として読んでください。
| # | 実際に起きたこと | ルール化した対策 | なぜ |
|---|---|---|---|
| 1 | MCP 経由だと完了応答が返らずハングした | eim run "idf.py -C <proj> …" でシェル直行 |
詰まる経路(MCP)を最初から使わない |
| 2 | 完了通知が実完了より先に届いた | 完了は成果物とログで確認する | 通知を信じると「未完成なのに成功」と誤報告する |
| 3 | flash 失敗なのに終了コードが 0 だった | 成否は出力文字列で判定する | eim run は内側の失敗を握りつぶす |
| 4 | COM ポートを別アプリが占有していた | 占有プロセスを特定し、勝手に kill しない | 無断で他人のアプリを落とすのは危険 |
| 5 | idf.py monitor は対話型で止まる |
monitor はAI から実行しない | 対話型コマンドは AI 実行に向かない |
以下、特に効いた3つを掘り下げます。
② 「完了通知」より、成果物を信じる
いちばん効いたルールがこれです。フルビルドは3〜5分かかるのでバックグラウンドで走らせるのですが、その「完了通知」が、実際のビルド完了より早く届くことがありました。通知だけで「できました」と報告すると、まだ .bin が生成されていないのに成功扱いになってしまいます。
そこで、完了の判定を通知ではなく実体に寄せました。具体的には、build/<プロジェクト名>.bin の存在とタイムスタンプ、そしてログ末尾の Successfully created ESP32-S3 image. を確認できて初めて「ビルド完了」とみなします。AI の「終わりました」は、証拠と突き合わせるまで信じない——というルールです。
③ 終了コード 0 を信用しない
もうひとつの落とし穴が、eim run は内側のコマンドが失敗しても終了コード 0 を返すことです。実際、flash が失敗した回でも eim run 自体は 0 で返ってきました。終了コードだけ見ていたら「成功」と誤判定します。
対策は単純で、出力の文字列で判定すること。書き込み成功なら全イメージに Hash of data verified が出て、最後に Hard resetting via RTS pin が出ます。失敗なら A fatal error occurred: Could not open COM5 … のようなエラー行が出ます。見るべきは exit code ではなく、ログの中身です。
④ COM ポートが busy でも、勝手に殺さない
書き込み先の COM ポートを別のアプリ(シリアルモニタなど)が握っていると、flash は失敗します。ここで「占有プロセスを見つけて自動で kill する」こともできますが、それはやらないルールにしました。ユーザーが意図して開いているターミナルかもしれないからです。
スキルには「占有プロセスを特定するところまではやり、kill はせずユーザーに解放を依頼する」と書いてあります。自動化の便利さと、勝手に他のアプリを落とさない安全性の、線引きです。
📄 SKILL.md の実物
実際に使ったスキルの中身です。パス(C:\esp\... など)はこの検証マシンの例なので、読者は自分の環境に置き換えてください。ルールの一つひとつが、上の「事故」に対応しているのが分かります。
---
name: esp-idf
description: ESP32/ESP-IDF プロジェクトのビルド・書き込み(flash)・set-target・クリーン等の操作全般。ESP32 のビルドやフラッシュを頼まれたら必ずこのスキルに従う(idf.py は素のシェルでは使えない・MCP サーバは使わない)
---
# ESP-IDF 操作スキル(Windows・EIM 環境)
## 環境の前提(このマシン固有・2026-07-30 実機検証済み)
- ESP-IDF v6.0.2 が EIM (0.17.1) で C:\esp\v6.0.2\esp-idf に導入済み
- 素の PowerShell/CMD に idf.py は存在しない。必ず eim run "idf.py <args>" の形で呼ぶ
- プロジェクトは -C <プロジェクトパス> で必ず明示指定する(cwd に依存させない)
## 基本コマンド
| 操作 | コマンド |
|:--|:--|
| ターゲット設定 | eim run "idf.py -C <proj> set-target esp32s3" |
| ビルド | eim run "idf.py -C <proj> build" |
| 書き込み | eim run "idf.py -C <proj> -p <COMポート> flash" |
| フルクリーン | eim run "idf.py -C <proj> fullclean" |
## 実行の作法
- フルビルドは 3〜5分。バックグラウンド実行にし、完了は成果物で確認する
- build\<プロジェクト名>.bin の存在とタイムスタンプ/ログの Successfully created ESP32-S3 image.
- ⚠️ 「完了通知」は実完了より早く届くことがある。通知だけで成否を判断しない
- ⚠️ eim run は内側コマンドが失敗しても exit code 0 を返す。出力内容で判定する
- flash 成功 = Hash of data verified(全イメージ)+ Hard resetting via RTS pin
- 失敗の典型 = A fatal error occurred: Could not open COMx, the port is busy
- COM ポートが busy のとき: 占有プロセスを特定し、勝手に kill せず必ずユーザーに確認
- idf.py monitor は対話型なので実行しない(ユーザー端末で実行してもらう)
## やってはいけないこと
- MCP サーバのツールでビルド・フラッシュしない
(実処理は成功するが完了応答を返さずタスクが running のままになるため)
- venv の mcp を 2.x に上げない(idf.py mcp-server は mcp 1.x API 前提。mcp[cli]<2 を維持)
description に「ESP32 のビルドやフラッシュを頼まれたら必ずこのスキルに従う」と書いてあるのが効きどころです。この一文があるおかげで、「ビルドして」「書き込んで」といった依頼で自動的にこの手順が呼ばれます。あとは本文の作法に沿って、Claude Code が eim run を組み立ててくれます。
🔬 実戦テスト:自然言語1文で set-target → build → flash
新規プロジェクト(MCP 登録なし・スキルのみ)で、実際に流してみました。依頼したのは、たった1文です。
「ESP32-S3 向けにビルドして、COM5 に書き込んで」
ここから先は、スキルに従って Claude Code が自動で進めます。途中で1回失敗していますが、そこも含めて正直に載せます。
- COM ポートの列挙 — つながっているポートを確認し、指定の COM5 が実在することを照合してから進みました(候補が複数で曖昧なら、ここでユーザーに確認する設計です)
set-target esp32s3— ターゲットを設定- フルビルド — バックグラウンドで実行し、通知ではなく成果物(
.binとログ)で完了を確認 - flash(1回目)は失敗 —
A fatal error occurred: Could not open COM5が出ました。原因は、COM5 を Tera Term が握っていたこと。このときeim runの終了コードは 0 でしたが、出力に成功ログが無いので失敗と正しく判定できました。スキルどおり、占有プロセスを特定するにとどめ、kill はせずユーザーに解放を依頼 - 解放 → flash(2回目)は成功 — bootloader・partition-table・アプリの3イメージすべてが
Hash of data verified、最後にHard resetting via RTS pinが出て、ESP32-S3 上でアプリが起動しました
- 自然言語1文だけで、COM 確認 → set-target → build → flash まで一気通貫で回った
- 1回目の失敗(COM 占有)を、終了コードではなく出力で正しく検知できた
- 失敗しても勝手に他アプリを落とさず、ユーザー確認を挟んで安全にリトライできた
- MCP のようなハングはゼロ。詰まる経路を最初から避けた設計が効いた
🔀 誤解しないでほしい:本線は MCP、スキルは現段階の現実解
ここまで「MCP を使わない」と書いてきましたが、MCP そのものを否定しているわけではありません。設計としては、むしろ MCP が本線です。MCP には、シェル直叩きにはない次のような強みがあります。
- ツールとリソースの標準化 — 「このサーバはこういう操作ができる」という型付きのインターフェースを公開します。シェルの文字列を毎回組み立ててパースする、という危うさがありません
- 一度サーバを作れば、どのクライアントからも同じように使える — Claude Code でも Cursor でも VS Code でも、同じサーバに同じ作法でつながります。今回のスキルは Claude Code 固有の書き方ですが、MCP はクライアントをまたいで再利用できます
- 公式サポートの本線 — ESP-IDF 側も、手元を操作する
idf.py mcp-serverと、公式ドキュメントを検索するmcp.espressif.comのサーバという形で、MCP を正式に用意しています
つまり、条件がそろえば MCP に寄せるのが素直です。今回スキルへ逃がしたのは、あくまで前編の記事で見つけた「Windows で重い処理の完了応答が返らない」という一点が理由で、ここが直れば話は変わります。
現実的な線引きとしては、状態の読み取りのような軽いやり取りは MCP、ビルド・書き込みのような重い実行は当面スキル、と役割を分けるのが無理のないところです。MCP 側が安定したら、実行もまとめて MCP へ戻す——という前提で捉えておくと、判断を切り替えやすくなります。
🧭 まとめ:スキルは「環境の癖を1回だけ教える」道具
- MCP が完了応答を返さないあいだの現実解は、スキルで
idf.pyを直接叩くことでした。詰まる経路を避けるので、ハングに悩まされません - スキル設計の勘所は、「実際に起きた事故」をそのままルールにすること。完了は成果物で確認する・終了コードを信用しない・勝手に kill しない——どれも、一度踏んだからこそ書けるルールです
- スキルの本質は、そのマシン固有の作法を1回だけ書いておくこと。以後は自然言語で頼むだけで、同じ手順が安定して再現します
- MCP が直ったら、使い分ければいい。読み取り系(状態確認)は MCP、重い実行(ビルド・書き込み)はスキル、というように。今回の内容は 2026-07-30 時点・Windows での実測にもとづくもので、MCP サーバ側が更新されれば状況は変わります
MCP サーバ自体が何をしてくれて、どこで詰まるのかは、前記事の Claude Code × ESP32|ESP-IDF 6.0 の MCP サーバ活用ガイド にまとめてあります。あわせてどうぞ。
最後にひとつ添えておくと、ここに書いたのはあくまで現段階(2026-07-30 時点・Windows 環境)での筆者の経験談です。MCP 側の設定でうまく回避する方法や、もっと筋のいいワークフローをご存じの方がいれば、ぜひ教えてください。環境が変わればベストな形も変わるはずで、この記事もそのときにはアップデートしたいと思っています。
関連記事
- Claude Code × ESP32|ESP-IDF 6.0 の MCP サーバ活用ガイド:本記事の前編。MCP サーバで何ができ、どこで詰まるのか(完了応答の問題の詳細)
- VS Codeで始めるESP-IDF環境構築ガイド|最小構成でHello Worldまで:スキルが叩く ESP-IDF 環境そのものの作り方(v5.5.2 前提だが考え方は共通)
- ESP32 シリーズの選び方|S3・C3・C6・H2・P4 の違いと使い分け:
set-targetで指定するチップを、用途から選ぶためのガイド