CLIをラップしたMCPサーバが上流の無料枠終了で死んだらREST直叩きへ寄せる
外部AIのCLIを子プロセスで起動するMCPサーバが認証終了・旧モデル固定・機能別の枠切れで死ぬ。原因を4層で切り分け、ツール名を変えずに実行部だけRESTへ置き換える手順と、接続緑・テスト緑では足りない検証方法。
約3.6万トークンの節約 (API料金換算で約54円分)。 要件定義・技術調査・試行錯誤ぶんのトークンがまるごと不要になります。※ 出品者申告とレビューに基づく推定値。モデル・タスク内容により変動します。
この巻物について
「CLIをラップしたMCPサーバが上流の無料枠終了で死んだらREST直叩きへ寄せる」は、開発プロセスカテゴリのAI指示書(MDファイル)です。外部AIのCLIを子プロセスで起動するMCPサーバが認証終了・旧モデル固定・機能別の枠切れで死ぬ。原因を4層で切り分け、ツール名を変えずに実行部だけRESTへ置き換える手順と、接続緑・テスト緑では足りない検証方法。この巻物をAIに読み込ませると、ゼロから設計・調査する場合に比べて 約3.6万トークン(API料金換算で約54円)・86%のトークンを節約できます。
- カテゴリ
- 開発プロセス
- 対応AI
- claude-code、cursor、codex-cli
- ライセンス
- 商用利用可 (再販不可)
- 価格
- 無料
- ゼロから開発時
- 約4.2万トークン
- この巻物使用時
- 約6,000トークン
- 節約量
- 約3.6万トークン (約54円)
- 更新日
- 2026-08-30
使い方 (AIに渡す3つの方法)
いちばん簡単なのはワンライナー。Claude Code のターミナルに貼るだけです。
claude "https://makimono-md.vercel.app/api/v1/files/cli-mcp-rest/raw を読み込んで、この指示書どおりに実装して"
中身
CLI をラップした MCP サーバが上流の無料枠終了で死んだら、REST 直叩きへ寄せる
対象: 外部 AI の CLI(xxx-cli)を子プロセスで起動する形の MCP サーバを運用していて、ある日から呼ぶたびに失敗するようになった状態。
結論: CLI を挟むのをやめて、同じ API キーで REST を直接叩く MCP サーバに置き換える。CLI は「自分では選べないモデル」と「自分では見えない認証経路」を勝手に決めてしまうため、上流の課金・モデル世代の変更を丸ごと被る。
1. まず切り分ける(ここを飛ばすと直したつもりで直っていない)
CLI 経由の失敗は、見た目が同じでも原因が層で分かれている。上から順に潰す。
| 層 | 症状の例 | 確認方法 |
|---|---|---|
| ① 認証方式 | IneligibleTierError / UNSUPPORTED_CLIENT / 「このクライアントはサポート対象外」 | CLI を素で1回叩く |
| ② モデル世代 | 404 ... no longer available to new users | REST に現行モデル名で1発投げる |
| ③ 無料枠の割当 | 429 ... free_tier_requests, limit: N, model: <古いモデル名> | 429 本文の model: を読む |
| ④ 機能単位の割当 | 素の生成は 200 なのに、特定機能を付けた時だけ 429 | 同じキー・同じモデルで A/B する |
決定的な判別は「CLI を外して REST を直接叩く」こと。 CLI が 429 で REST が 200 なら、枯れているのはアカウントの枠ではなく CLI が掴んでいるモデルである。
# CLI 経路(失敗する)
<cli> -p "hi" -m <現行モデル>
# REST 経路(同じキーで成功するなら CLI が犯人)
curl -s -X POST "https://<api-host>/v1beta/models/<現行モデル>:generateContent" \
-H "x-goog-api-key: $API_KEY" -H "content-type: application/json" \
-d '{"contents":[{"parts":[{"text":"hi"}]}]}' -o - -w '\n%{http_code}\n'
見落としやすい罠
- CLI がモデル指定を無視することがある。
-m <現行モデル>を渡しても 429 本文のmodel:は旧世代のまま、ということが起きる。429 の本文に出るモデル名を読むこと(自分が渡した名前だと思い込まない)。 - CLI をアップグレードしても直らないことがある。既定モデルがハードコードに近い形で古いままなら、バージョンを上げても同じ 429 になる。「最新版にしたのに直らない=別の原因」ではなく「最新版でも既定は古い」が正解のことがある。
- 無料枠は機能ごとに別バケツ。素の生成は通るのに検索グラウンディングだけ 429、という分かれ方をする。しかもこの 429 は
violationsが空で理由が出ないことがある。
2. 置き換え方(MCP のプロトコル部分は触らない)
既存サーバの ツール名・inputSchema・JSON-RPC の処理は一切変えない。変えるのは「実行部」だけ。ツール名を変えるとクライアント側の登録が壊れ、復旧がもう一段増える。
export async function runModel(prompt, model, options = {}) {
const env = options.env ?? process.env;
const key = env.<API_KEY_NAME> ?? readKeyFromDotenv(options.homeDir); // 無ければ ok:false を返す。throw しない
if (!key) return { ok: false, text: '<API_KEY_NAME> が見つかりません' };
const fetchImpl = options.fetchImpl ?? globalThis.fetch; // テストで差し替える口を必ず開ける
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs(env));
const body = { contents: [{ parts: [{ text: prompt }] }] };
if (options.grounding === true) body.tools = [{ <grounding_tool_name>: {} }];
try {
const res = await fetchImpl(`https://<api-host>/v1beta/models/${encodeURIComponent(model || DEFAULT_MODEL)}:generateContent`, {
method: 'POST',
headers: { 'x-goog-api-key': key, 'content-type': 'application/json' },
body: JSON.stringify(body),
signal: controller.signal,
});
const raw = await res.text();
if (!res.ok) return { ok: false, text: `REST ${res.status}: ${raw.slice(0, 800)}` }; // 本文を捨てない
const parsed = JSON.parse(raw);
// parts には text を持たない要素(署名・思考メタ)が混ざる。text だけ拾って連結する
const text = parsed.candidates?.[0]?.content?.parts
?.map((p) => p.text).filter((t) => typeof t === 'string' && t).join('');
return text ? { ok: true, text } : { ok: false, text: `REST: 応答に text がありません: ${raw.slice(0, 400)}` };
} catch (e) {
if (controller.signal.aborted) return { ok: false, text: 'timeout' };
return { ok: false, text: `REST failed: ${e instanceof Error ? e.message : String(e)}` };
} finally {
clearTimeout(timer);
}
}
この置き換えでついでに消える問題
- 起動が速くなり、MCP の接続タイムアウトが消える。CLI 起動は依存の読み込みで数十秒かかることがあり、
CONNECT_TIMEOUTの温床になる。 - 端末装飾(ANSI)や「色数が足りません」系の警告が回答本文に混入しなくなる。CLI 経由では出力を行単位で削る後処理が要る。
- OS ごとの起動差(Windows の
.cmdはCreateProcessで直接起動できずcmd.exe /cを挟む必要がある等)が丸ごと不要になる。 - 「信頼されていないディレクトリでは動かない」系のガードを外すフラグが不要になる。
3. 失敗は握り潰さない(特に検索系)
機能が使えないときに「使えないまま答えを返す」設計にしないこと。 検索グラウンディングが 429 のときに、黙って検索なしで生成して返すと、呼び出し側には検索した結果に見える。ハルシネーションを仕様として組み込むことになる。
// 429 のときだけ、なぜ落ちたかを本文に足して「失敗のまま」返す
const hint = res.status === 429 && options.grounding === true
? '\n(検索グラウンディングは無料枠では使えません。素の生成は使えます)'
: '';
return { ok: false, text: `REST ${res.status}: ${raw.slice(0, 800)}${hint}` };
安いモデル・無料枠は黙って死ぬのが最大の危険で、上位のエージェントが気付かずに自分で処理を巻き取り、コストが静かに戻る。枠切れは必ず声を出して落とす。
4. 検証(緑を3つ揃えても足りない)
| 見た目 | 何を証明するか |
|---|---|
MCP クライアントが connected と表示 | プロセスが起動して initialize に応答しただけ。ツールが動く証拠にはならない |
| 単体テストが全部緑 | スタブした fetch の契約を守っただけ。上流の実際の応答は1度も見ていない |
| ツールを1個叩いて成功 | その1個だけの証拠。機能ごとに枠が別なので他は落ちうる |
最低ライン: サーバを実際に起動し、stdio で initialize → tools/list → tools/call を全ツール分流して本文を見る。
// 検証ドライバの骨子(実プロセスを起動して JSON-RPC を1行ずつ流す)
const server = spawn(process.execPath, ['<server>.mjs'], { stdio: ['pipe', 'pipe', 'inherit'] });
const call = (method, params) => new Promise((resolve) => { /* id を採番して stdout の同 id を待つ */ });
await call('initialize', { protocolVersion: '<version>' });
const list = await call('tools/list', {});
for (const tool of list.result.tools) {
const r = await call('tools/call', { name: tool.name, arguments: sampleArgsFor(tool) });
console.log(tool.name, 'isError=', r.result?.isError === true, r.result?.content?.[0]?.text?.slice(0, 300));
}
実際にこれで、単体テスト 7/7 緑・接続も緑のまま、2つのツールのうち1つだけが 429 という状態が見つかる。
5. 運用側の注意
- 並行して別のセッション/別の作業が同じ作業ツリーを触る環境では、未追跡ファイルは消える。他所の
git cleanや巻き戻しの巻き添えで、書いたばかりの新規ファイルが commit の裏で丸ごと消えることがある。新規ファイルは書いたらすぐ追跡下に入れる(push は不要)。 - MCP サーバのプロセスは接続時にコードを読み込む。ファイルを直しても、動いているセッションは古いコードのまま。直後の呼び出しが失敗しても実装の失敗とは限らない。クライアントを再起動して確認する。
- 設定に残った古いモデル名は全部洗う。
<旧モデル名>を持つ設定・スクリプトは 404 で落ちる。
チェックリスト
- 429 の本文に出ているモデル名を読んだか(自分が渡した名前ではない)
- CLI と REST を同じキーで A/B したか
- ツール名・inputSchema を変えずに実行部だけ差し替えたか
-
fetchをテストで差し替えられるようにしたか(テストで実ネットワークを叩いていないか) - 枠切れを失敗のまま返しているか(黙って劣化した答えを返していないか)
- stdio で
tools/callを全ツール分流したか - 新規ファイルを追跡下に入れたか
よくある質問
+「CLIをラップしたMCPサーバが上流の無料枠終了で死んだらREST直叩きへ寄せる」とは何ですか?
外部AIのCLIを子プロセスで起動するMCPサーバが認証終了・旧モデル固定・機能別の枠切れで死ぬ。原因を4層で切り分け、ツール名を変えずに実行部だけRESTへ置き換える手順と、接続緑・テスト緑では足りない検証方法。
+どれくらいトークン(費用)を節約できますか?
ゼロから開発すると約4.2万トークンかかりますが、この巻物を使えば約6,000トークンで済みます。差し引き約3.6万トークン(API料金換算で約54円)・86%の節約です。
+どうやって使いますか?
無料です。MDファイルを Claude Code などのAIに読み込ませるだけ。ワンライナーをターミナルに貼れば実装が始まります。要件定義や技術調査を省いて実装だけにトークンを使えます。
+どのAIツールに対応していますか?
claude-code、cursor、codex-cli に対応しています。
+商用利用できますか?
ライセンスは「商用利用可 (再販不可)」です。
🤝 自分でAIを動かすのは、まだ不安…という方へ
この巻物の内容を、AIを使うプロに丸ごと任せることもできます。姉妹サービスAI代行堂なら「LINEで頼むだけで、仕事が完成」。
関連する巻物
ドキュメント駆動開発プロセス CLAUDE.md — 作るものを固めてから書かせる
「AIが暴走して意図と違うものを作る」を根絶する開発プロセス指示書。UI仕様→機能設計→実装の順をAIに強制し、1ファイルごとに承認ゲートを挟む。受託開発・チーム開発向け。
AIに指示書マーケットを自動参照させ、終了時に自動出品させるMD
開発依頼を受けた瞬間にマーケットの完成済み指示書を検索してAIに読ませ、セッション終了時には汎用ノウハウを自動出品させる仕組みの作り方。全台配布・秘密情報スキャン・実際に踏んだ配布バグ3つの回避込み。
「そのPCにしか直せない障害」をAIに自分で気付かせて着手させる
特定の1台にしかリポジトリが無い機能は、修正手順を書いても誰にも実行されず放置される。SessionStart hook で当該PCのAIだけに指示を出し、完了後は指示書へ状態を書き戻して再実装事故を防ぐ型。走査の時間予算とセッション跨ぎの再開、メール一致だけの自動承認がなりすまされる理由と署名キー方式、状態問い合わせAPI、鍵の自動配布、no-op通知の抑止まで、実際に94件の滞留を解消した実例に基づく手順。
この巻物、誰かのトークンも救えます
𝕏 で節約レシートをシェア