このドキュメントについて
このドキュメントは、Codex CLI のヘッドレスモード codex exec から Model Context Protocol (MCP) サーバーを利用する際に発生するエラーの原因と対処法をまとめたものです。
実際にソースコードを追跡して得た検証結果をもとに、初心者でも理解できるよう丁寧に解説しています。
目次
- 前提知識
codex execとは何か- よく見かけるエラーログ
- エラーログの正体:実は成功している場合がある
- 本当に失敗するケースとその原因
- 原因の詳細な仕組み
- 解決策
- コマンド例の詳細解説
- なぜこれで解決するのか
- 切り分けチェックリスト
- 運用のコツ
- まとめ
前提知識
このドキュメントを読む前に、以下の用語を知っておくとスムーズです。
| 用語 | 説明 |
|---|---|
| Codex CLI | OpenAI が提供するコマンドラインの AI エージェント。コードの生成・編集・実行を自動で行う |
codex exec |
Codex CLI のヘッドレス(画面なし)実行モード。CI/CD やバッチ処理向け |
| MCP (Model Context Protocol) | AI エージェントが外部ツールやデータソースと通信するための標準プロトコル |
| MCP サーバー | MCP プロトコルでツール機能を提供するサーバープロセス(例: serena, context7 など) |
| uvx | Python パッケージマネージャ uv のコマンド実行ツール。MCP サーバーの起動に使われることが多い |
| npx | Node.js のパッケージ実行ツール。同様に MCP サーバーの起動に使われる |
| サンドボックス | プログラムのアクセス範囲を制限するセキュリティ機構 |
codex exec とは何か
codex exec は TUI(テキストユーザーインターフェース)を持たないバッチ実行向けのコマンド です。
使われる場面
- CI/CD パイプライン — プルリクエスト時に自動でコードレビューやテスト生成を行う
- 外部スケジューラ — cron ジョブなどで定期的に AI にタスクを実行させる
- ChatOps — Slack などのチャットから Codex にタスクを投げる
通常モードとの違い
通常の codex コマンドは対話型で、ファイルの変更やコマンドの実行前にユーザーの承認を求めます。しかし codex exec はヘッドレスで動くため、承認を求めることができません。
そのため、デフォルトで承認ポリシーが AskForApproval::Never(承認を求めない)に固定されています。これはバグではなく 意図された設計 です。画面がない以上、承認ダイアログを出しようがないため、Never が唯一の合理的な選択肢です。ソースコードを修正する必要はありません。
初心者向けポイント: 「承認を挟めないなら危険では?」と思うかもしれませんが、その分 サンドボックス がセキュリティを担保しています。承認の代わりに、ネットワークやファイルアクセスの範囲を制限することで安全性を確保する設計です。ユーザーが対処すべきは ソースコードの変更ではなく、設定の調整(
-cフラグやconfig.toml)です。
参考として、この実装は codex-rs/exec/src/lib.rs:82 付近で確認できます(Codex CLI 本体のリポジトリ)。
// codex-rs/exec/src/lib.rs
let overrides = ConfigOverrides {
model,
review_model: None,
config_profile,
// この CLI はヘッドレスなので承認を挟めない
approval_policy: Some(AskForApproval::Never),
sandbox_mode,
cwd: cwd.map(|p| p.canonicalize().unwrap_or(p)),
model_provider,
codex_linux_sandbox_exe,
base_instructions: None,
include_plan_tool: Some(include_plan_tool),
include_apply_patch_tool: None,
include_view_image_tool: None,
show_raw_agent_reasoning: oss.then_some(true),
tools_web_search_request: None,
};
初心者向けポイント: 「承認ポリシーが
Neverに固定」とは、AI が判断したファイル変更やコマンド実行を、人間に確認せずそのまま実行するということです。CI/CD のように無人で動かす場面では必要な設定ですが、その分サンドボックスでセキュリティを担保しています。
よく見かけるエラーログ
codex exec から MCP サーバーを起動すると、ログに以下のようなメッセージが連続して表示されることがあります。
ERROR codex_core::mcp_connection_manager: new_stdio_client use_rmcp_client: false program: "uvx" ...
ERROR codex_core::mcp_connection_manager: new_stdio_client use_rmcp_client: false program: "npx" ...
一見すると 重大なエラーが起きている ように見えますが、実はこれは必ずしもエラーではありません。
エラーログの正体:実は成功している場合がある
ソースコードでの確認
このログの出力元は codex-rs/core/src/mcp_connection_manager.rs:112 にある info! マクロです。
// codex-rs/core/src/mcp_connection_manager.rs
info!(
"new_stdio_client use_rmcp_client: {use_rmcp_client} program: {program:?} \
args: {args:?} env: {env:?} params: {params:?} startup_timeout: {startup_timeout:?}"
);
なぜ ERROR に見えるのか
- Rust の
info!マクロは、本来 情報レベル(INFO) のログです - しかし
codex execのヘッドレス環境では、すべてのログが 標準エラー出力(stderr) に流れます - ターミナルやログ収集ツールによっては、stderr のすべてを ERROR として表示するため、実際には INFO レベルのメッセージが ERROR 表記になってしまいます
見分け方
| 状況 | ログの特徴 |
|---|---|
| MCP 起動成功(ログだけが紛らわしい) |
new_stdio_client use_rmcp_client: で始まる。その後にタイムアウトやスタックトレースが出ない |
| MCP 起動失敗(本当のエラー) |
request timed out やネットワーク/ファイルアクセス関連のエラーが続く |
初心者向けポイント: 「ERROR と書いてあるから失敗した」と即断しないでください。ログの中身を読んで、
request timed outのような具体的な失敗メッセージがあるかどうかで判断しましょう。
本当に失敗するケースとその原因
codex exec + MCP の組み合わせで 本当に失敗する 代表的な要因は以下の 2 つです。
原因 1: ネットワークが遮断されている
codex exec のサンドボックスがネットワークをブロックした状態で uvx や npx を起動すると、必要なパッケージのダウンロードに失敗します。
具体例: MCP サーバー serena を初めて起動する際、uvx は PyPI からパッケージをダウンロードする必要があります。ネットワークが閉じているとこのダウンロードが失敗します。
原因 2: キャッシュディレクトリへの書き込みが禁止されている
uvx や npx は初回起動時にキャッシュディレクトリにファイルを書き込みます。サンドボックスがこの書き込みを禁止していると、セットアップが完了できずタイムアウトします。
よく使われるキャッシュディレクトリ:
| ツール | キャッシュパス |
|---|---|
| uvx / uv |
~/.cache/uv, ~/.uv
|
| npx / npm |
~/.npm, ~/.cache
|
タイムアウトエラーの実装
上記の原因で MCP サーバーの初期化が間に合わないと、codex-rs/mcp-client/src/mcp_client.rs:224 で以下のタイムアウトエラーが発生します。
// codex-rs/mcp-client/src/mcp_client.rs
Err(_) => {
let mut guard = self.pending.lock().await;
guard.remove(&id);
return Err(anyhow!("request timed out"));
}
初心者向けポイント: MCP サーバーの起動は「プログラム起動 → 初期化リクエスト → 応答待ち」という流れです。途中でネットワークやファイルアクセスが止まると、応答が返らないまま制限時間を超えて「タイムアウト」になります。
原因の詳細な仕組み
なぜ codex exec ではデフォルトでネットワークやファイルアクセスが制限されるのか、内部の仕組みを解説します。
ステップ 1: 承認ポリシーの固定
前述の通り、codex exec はヘッドレスなので AskForApproval::Never が固定です。ユーザーに「ネットワークを使っていいですか?」と聞くことができません。
ステップ 2: workspace-write サンドボックスのデフォルト設定
--sandbox workspace-write を指定すると、ワークスペース内への書き込みは許可されますが、ネットワークはデフォルトで無効 です。
codex-rs/protocol/src/protocol.rs:302 のソースコードを見ると、この設定が確認できます。
// codex-rs/protocol/src/protocol.rs
pub fn new_workspace_write_policy() -> Self {
SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
network_access: false, // ← ネットワークはデフォルトOFF
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
}
}
ステップ 3: 書き込み可能な範囲の制限
workspace-write モードで書き込みできるのは以下の範囲のみです。
-
--cdで指定したワークスペースディレクトリ - システムの一時ディレクトリ(
/tmpなど)
つまり ~/.cache や ~/.uv は 書き込み対象外 です。
ステップ 4: 結果として起きること
1. codex exec が MCP サーバーを起動しようとする
2. uvx/npx が依存パッケージのダウンロードを試みる
3. ネットワークがブロックされているため、ダウンロード失敗
4. ローカルキャッシュに書き込もうとするが、権限がないため失敗
5. MCP の initialize リクエストに対する応答が返らない
6. タイムアウト → "request timed out" エラー
初心者向けポイント: 「セキュリティのために制限しているのに、そのせいで動かない」というジレンマです。解決策は「必要最小限の権限だけを明示的に許可する」ことです。
解決策
対処のポイントは以下の 3 つ ですが、すべてが必須というわけではありません。重要度に応じて段階的に適用してください。
1. ネットワークと書き込み先を明示的に許可する【必須】
サンドボックス設定で、MCP サーバーが必要とするネットワークアクセスとキャッシュディレクトリへの書き込みを許可します。これが本当の失敗原因を解消する唯一の対処であり、必ず行う必要があります。
対応するコマンドオプション(詳細は コマンド例の詳細解説 を参照):
| コマンドオプション | 役割 |
|---|---|
--sandbox workspace-write |
サンドボックスモードの選択(土台) |
-c 'sandbox_workspace_write={network_access=true,writable_roots=[...]}' |
ネットワーク許可 + 書き込み先の追加(これが本体) |
注意:
--sandbox workspace-writeだけではネットワークは遮断されたままです。必ず-cオプションでnetwork_access=trueとwritable_rootsを上書きしてください。この 2 つはセットで指定する必要があります。
2. MCP サーバーの初期化タイムアウトを延長する【状況次第】
デフォルトのタイムアウトでは、初回のパッケージダウンロード+セットアップに間に合わないことがあります。余裕を持った値に延長します。
- 必要な場合: 初回起動時(キャッシュが空)、ネットワークが遅い環境
- 不要な場合: 2 回目以降の起動でキャッシュが温まっている場合、高速なネットワーク環境
迷ったら設定しておくのが安全です。設定しても副作用はありません。
3. ログのノイズをフィルタリングする【任意(推奨)】
成功時でも ERROR 表示が残るため、RUST_LOG 環境変数で不要なログを抑制し、本当の問題を見つけやすくします。
- 動作には一切影響しません。設定しなくても MCP サーバーは正常に動きます
- ログを監視する運用(CI/CD でのアラート設定など)では、誤検知を防ぐために設定を推奨します
コマンド例の詳細解説
以下のコマンドで、3 つの対処をすべて適用できます。パスは自分の環境に合わせて読み替えてください。
RUST_LOG=warn,codex_core::mcp_connection_manager=off \
codex exec \
--json \
--sandbox workspace-write \
-c 'sandbox_workspace_write={network_access=true,writable_roots=["/home/youruser/.cache","/home/youruser/.uv"]}' \
-c mcp_servers.serena.startup_timeout_sec=30 \
--cd "/path/to/your/workspace" \
"MCP 経由でタスクを実行してください"
各オプションの解説
RUST_LOG=warn,codex_core::mcp_connection_manager=off(→ 解決策 3【任意】 に対応)
| 部分 | 意味 |
|---|---|
warn |
デフォルトのログレベルを WARN 以上に設定(INFO や DEBUG を非表示にする) |
codex_core::mcp_connection_manager=off |
MCP 管理モジュールからの冗長なログを完全に抑止する |
これにより、紛らわしい ERROR 風のログが消え、本当の警告やエラーだけが表示されます。
--json(--experimental-json でも同じ)
出力を JSONL(1 行 1 JSON)形式 にします。プログラムからの解析が容易になります。
--json と --experimental-json は 完全に同じ機能 です。CLI 定義(codex-rs/exec/src/cli.rs:67)で確認できます。
// codex-rs/exec/src/cli.rs
#[arg(long = "json", alias = "experimental-json", default_value_t = false)]
pub json: bool,
初心者向けポイント: 古いドキュメントや記事では
--experimental-jsonと書かれていることがありますが、現在は--jsonが正式名で、--experimental-jsonはその別名(エイリアス)です。どちらを使っても同じ動作をします。
--sandbox workspace-write + -c 'sandbox_workspace_write={...}'(→ 解決策 1【必須】 に対応)
この 2 つのオプションがセットで 解決策 1(ネットワークと書き込み先の許可) を実現します。
--sandbox workspace-write はサンドボックスモードを「ワークスペース書き込み許可」に設定します。
- ワークスペース(
--cdで指定したディレクトリ)と一時ディレクトリへの書き込みを許可 - ただしデフォルトではネットワークは遮断されたまま(次の
-cオプションで解放する)
-c 'sandbox_workspace_write={network_access=true,writable_roots=[...]}' で workspace-write サンドボックスの設定を上書きします。
| 設定項目 | 値 | 意味 |
|---|---|---|
network_access |
true |
ネットワークアクセスを許可する |
writable_roots |
["/home/youruser/.cache", "/home/youruser/.uv"] |
指定したディレクトリへの書き込みを追加で許可する |
writable_roots には、利用する MCP サーバーが必要とするキャッシュディレクトリを登録します。
よくある設定例:
| MCP サーバーの起動方法 | 追加すべき writable_roots
|
|---|---|
| uvx 経由 |
~/.cache, ~/.uv
|
| npx 経由 |
~/.npm, ~/.cache
|
| 両方使う場合 |
~/.cache, ~/.uv, ~/.npm
|
-c mcp_servers.serena.startup_timeout_sec=30(→ 解決策 2【状況次第】 に対応)
serena という名前の MCP サーバーの初期化タイムアウトを 30 秒 に延長します。
- デフォルトのタイムアウトでは、初回のパッケージダウンロードに間に合わないことがある
- 別の MCP サーバーを使う場合は
serenaの部分を変更する(例:mcp_servers.context7.startup_timeout_sec=30)
--cd "/path/to/your/workspace"
Codex の作業ディレクトリを明示的に指定します。サンドボックスの書き込み範囲もこのディレクトリを基準に決まります。
セッション再開の場合
既存のセッションを再開する場合は、最後の引数を resume に変えます。
RUST_LOG=warn,codex_core::mcp_connection_manager=off \
codex exec \
--json \
--sandbox workspace-write \
-c 'sandbox_workspace_write={network_access=true,writable_roots=["/home/youruser/.cache","/home/youruser/.uv"]}' \
-c mcp_servers.serena.startup_timeout_sec=30 \
--cd "/path/to/your/workspace" \
resume <SESSION_ID> "こんにちは"
| 引数 | 意味 |
|---|---|
resume |
セッション再開モード |
<SESSION_ID> |
再開するセッションの ID |
"こんにちは" |
再開時に送信する追加プロンプト |
なぜこれで解決するのか
修正前と修正後の状態を比較すると、解決の仕組みが分かります。
【修正前】
uvx が PyPI にアクセス → ネットワーク遮断 → 失敗
uvx がキャッシュに書き込み → 権限なし → 失敗
MCP サーバーが起動できない → タイムアウト → "request timed out"
【修正後】
uvx が PyPI にアクセス → network_access=true → 成功
uvx がキャッシュに書き込み → writable_roots に登録済み → 成功
MCP サーバーが起動 → startup_timeout_sec=30 で余裕あり → 成功
つまり:
- ネットワーク許可 で依存パッケージのダウンロードが可能になる
- キャッシュディレクトリの書き込み許可 でダウンロードしたパッケージの保存が可能になる
- タイムアウト延長 でネットワーク遅延や初回セットアップの時間を吸収できる
- ログフィルター で成功しているのに ERROR 表示が残る混乱を防げる
切り分けチェックリスト
MCP サーバーがうまく動かないときは、以下の順番で確認してください。
ステップ 1: ログの確認
-
ログの ERROR 表示は本当にエラーか?(
new_stdio_client use_rmcp_client:で始まるなら単なる起動情報ログの可能性大) -
request timed outというメッセージが出ているか?(出ていれば本当の失敗)
ステップ 2: サンドボックス設定の確認
-
-c sandbox_workspace_writeでnetwork_access=trueを指定しているか? -
MCP ランタイムが使うキャッシュディレクトリが
writable_rootsに含まれているか? -
writable_rootsのパスは 絶対パスで、実際に存在する か?
ステップ 3: タイムアウトの確認
-
startup_timeout_secを設定しているか?(まず 30 秒で試す) - 初回起動かどうか?(初回はパッケージダウンロードがあるため特に時間がかかる)
ステップ 4: 環境の確認
-
--cdが意図したワークスペースを指しているか? -
uvx/npxコマンドが PATH 上で見つかるか?(which uvx,which npxで確認) -
MCP サーバー名が
config.tomlや-cオプションの設定と一致しているか?
運用のコツ
段階的に設定を固める
- まず CLI の
-cフラグで一時的に上書きして動作確認 する - 動作が安定したら、その設定を
config.tomlに反映する -
config.tomlに書くことで、毎回-cを指定しなくてよくなる
# config.toml の例
[sandbox_workspace_write]
network_access = true
writable_roots = ["/home/youruser/.cache", "/home/youruser/.uv"]
[mcp_servers.serena]
startup_timeout_sec = 30
CI/CD での運用
- タイムアウト値と
writable_rootsを環境変数やパイプライン設定で明示し、再現性を確保する - 初回実行時はキャッシュが空なのでタイムアウトを長めにする(60 秒など)
- 2 回目以降はキャッシュがあるので短くても動く
- CI キャッシュ機構(GitHub Actions の
actions/cacheなど)で~/.cache/uvを保存すると起動が高速化する
複数の MCP サーバーを使う場合
MCP サーバーごとにタイムアウトを設定できます。
-c mcp_servers.serena.startup_timeout_sec=30 \
-c mcp_servers.context7.startup_timeout_sec=30 \
-c mcp_servers.another_server.startup_timeout_sec=45
まとめ
| ポイント | 内容 |
|---|---|
codex exec の特性 |
ヘッドレス(無人)実行のため、承認なし・サンドボックスによる制限が厳しい |
| ERROR ログの誤解 |
mcp_connection_manager の INFO ログが ERROR 風に見えるだけの場合がある |
| 本当の失敗原因 | ネットワーク遮断とキャッシュディレクトリの書き込み禁止 |
| 解決の 3 本柱 | ネットワーク許可 + キャッシュ書き込み許可 + タイムアウト延長 |
--json と --experimental-json
|
完全に同じ機能(エイリアス関係) |
| 運用の流れ |
-c で一時テスト → 安定したら config.toml に反映 |
codex exec から MCP 連携は 可能 です。安定化の鍵は「必要最小限の権限を明示的に許可する」ことです。ログの見た目だけで失敗と判定せず、チェックリストで実際の原因を切り分けてください。