🗓️ この記事の要点(2026-07-30・Windows 実機検証)
  • 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_projectflash_project を呼ぶと実処理そのものは成功するのに、MCP のタスクが完了扱いにならず、応答がクライアントに返ってこない——という症状です(Windows での再現。詳しくは前記事へ)。

つながらないわけでも、ビルドが失敗するわけでもありません。「動いているのに、終わったと言ってくれない」。これでは AI クライアントから安定して回せません。

そこで発想を変えます。MCP サーバを経由せず、AI に idf.py を直接叩かせればいい。 ちょうど Claude Code には、こういう「決まった手順」を教え込む スキル という仕組みがあります。この記事は、その スキル を実際に作って ESP32-S3 で検証した記録です。検証はすべて 2026-07-30・Windows 11 で実機で行いました。


🛠️ Claude Code の「スキル」とは(3分で分かる基礎)

💡 ワード解説:Claude Code のスキル

スキル は、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 runidf.py を直接実行する」というスキルを、ユーザーレベルに作りました。全体の流れは、次のようにとてもシンプルです。

flowchart LR U["あなた(自然言語で依頼)"] --> S["スキル発動(description が一致)"] S --> R["eim run で idf.py を直接実行"] R --> I["set-target → build → flash"] I --> C["成果物とログで完了確認"]

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回失敗していますが、そこも含めて正直に載せます。

flowchart TD A["自然言語1文:S3 向けにビルドして COM5 に書き込んで"] --> B["COM ポート列挙(COM5 を確認)"] B --> C["set-target esp32s3"] C --> D["フルビルド(成果物で完了確認)"] D --> E["flash(1回目)"] E -->|"COM5 を Tera Term が占有"| F["失敗:Could not open COM5"] F --> G["kill せずユーザーに解放を依頼"] G --> H["flash(2回目)"] H --> I["成功:Hash of data verified・実機起動"]
  1. COM ポートの列挙 — つながっているポートを確認し、指定の COM5 が実在することを照合してから進みました(候補が複数で曖昧なら、ここでユーザーに確認する設計です)
  2. set-target esp32s3 — ターゲットを設定
  3. フルビルド — バックグラウンドで実行し、通知ではなく成果物.bin とログ)で完了を確認
  4. flash(1回目)は失敗A fatal error occurred: Could not open COM5 が出ました。原因は、COM5 を Tera Term が握っていたこと。このとき eim run の終了コードは 0 でしたが、出力に成功ログが無いので失敗と正しく判定できました。スキルどおり、占有プロセスを特定するにとどめ、kill はせずユーザーに解放を依頼
  5. 解放 → 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 側の設定でうまく回避する方法や、もっと筋のいいワークフローをご存じの方がいれば、ぜひ教えてください。環境が変わればベストな形も変わるはずで、この記事もそのときにはアップデートしたいと思っています。


関連記事


参考