Zedでは詰まり、VSCode Remote SSHでは通ったMCP運用の整理
remote SSH 環境で MCP を動かす際、Zed がローカル起点で解決するのに対し VSCode Remote SSH は拡張ホストを remote 側で動かすため MCP コマンドが compute server の PATH に従う。実行主体の違いと podman run –rm を使った最終設計を整理した記録。
はじめに
当初は compute server 側に VSCode Server もしくは Zed Remote Server を立て、マシンパワーを全面的に使う構成を考えていた。しかし M1 Mac が遊んでしまうので、結局エディタは Mac 側に置くことにした。現状は Zed を remote 編集用に使い、拡張と LSP は切った状態で運用している。
この構成変更の過程で、remote SSH 環境での MCP の扱いが VSCode と Zed で根本的に違うことに気づいた。今回のメモは、なぜ Zed では MCP が通らなかったのか、なぜ VSCode Remote SSH では通ったのか、その差分を実行モデルの観点で整理した記録だ。
背景・目的
当初の計画
compute server(EPYC + Blackwell)側にエディタサーバーを立て、Mac はシンクライアントとして使う計画だった。
- VSCode Server または Zed Remote Server を compute 側で動かす
- MCP サーバーも compute 側で起動し、ローカルの runtime をそのまま使う
- Mac はターミナルとブラウザだけの薄い存在にする
現実の構成
M1 Mac を遊ばせるのがもったいないので、エディタは Mac 側に置いた。
- Zed: remote 編集用。拡張(ext)と LSP は切って、純粋なエディタとして使う
- VSCode Remote SSH: MCP が必要なときはこちらを使う
- compute server は推論サーバーと MCP サーバーの実行環境として使う
この構成で、MCP サーバーを compute server 側の runtime で動かしたかった。local 側で補助的に仲介するのではなく、remote 側の uvx、python、コンテナ runtime をそのまま利用したかった。
欲しかった条件は次の通りだった。
- remote 側の PATH とバイナリをそのまま参照できること
- セッション終了時にプロセスがきれいに消えること
- 常駐プロセスを増やしすぎないこと
- 必要なら
podman run --rmで都度起動できること
実行モデルの違いが問題の核心
今回の論点は、設定ファイルの書き方よりも、MCP サーバーをどこで起動する設計なのかにあった。同じ command 指定でも、client 側の実装思想が違えば結果はまったく変わる。
関連ファイル vscode-serverのセットアップ.md でも、VSCode Server は 1 接続構成で扱い、runtime を self-contained に保つ方針になっていた。この前提があると、MCP も remote 側で閉じた subprocess として扱うほうが整合しやすい。
Zed がダメだった主因
最初に詰まったのは、Zed の MCP サーバー設定が基本的に command をローカル側、つまり mac 側で解決する設計だったことだ。remote SSH で作業していても、.zed/settings.json を remote プロジェクト側に置けば remote 側の uvx や python が使われる、とはならなかった。
実際には、Zed 本体がローカル環境からコマンドを起動しようとする。そのため、remote 側にインストールしてある runtime を使いたくても、起点がローカルである以上、PATH 解決の時点でずれてしまう。ここが根本原因だった。
さらに、Zed の MCP エントリは stdio ベースでローカル起点として扱われるため、remote 上の MCP サーバーをそのまま自然にぶら下げる構造に向いていなかった。mcp-remote のような proxy 案も考えたが、SSE 非対応という制約があり、回り道が増えるわりに運用が素直にならない。
Zed で遭遇した具体的な問題
実際に起きた問題はかなり明確だった。
- remote 側に入っている
uvxやserenaが参照されない - ローカルの
uvx経由でserenaを起動しても、compute server 側の PATH を使えない - port forward を張っても、MCP の起動主体がローカルのままなので効果が薄い
- remote 側で LISTEN するプロセスが増えず、観測上も remote MCP が生えていない
つまり、個別の設定ミスというより、実行主体そのものが自分の要件と合っていなかった。ここを無理に調整しても、例外処理ばかり増えていく。
VSCode がうまくいった決定的な理由
VSCode Remote SSH では事情がまったく違った。拡張ホスト自体が remote 側で動くため、mcp.servers の command と args がそのまま compute server 内の PATH と環境に従う。ここではじめて、remote 側に用意していた runtime を自然に使えるようになった。
この差はかなり大きい。MCP サーバーが remote 側のサブプロセスとして立ち上がるなら、Python でも uvx でも、その場の環境をそのまま使えばよい。しかも VSCode セッションが終わればサブプロセスも自動で落ちるので、掃除の仕組みを別に考えなくていい。
この性質は podman run --rm と非常に相性が良かった。セッション中だけ使う MCP をコンテナで都度起動し、終わったら消す、という運用に無理がない。
実際の挙動(ss -ltnp で確認)
挙動確認では ss -ltnp を見た。VSCode Remote で MCP を使うと、compute server 側に users:(("python",pid=xxxx)) のような LISTEN が現れ、接続終了と同時に消えた。これは「remote 側 subprocess として MCP が生え、親セッションが終われば一緒に死ぬ」ことをそのまま示している。
一方で Zed を使っていたときは、この remote 側 LISTEN がまったく生えなかった。local 起動前提なら当然の結果だが、ここで観測結果と実装上の推測が一致した。以後は Zed 側で細かく粘るより、client を変えたほうが早いと判断できた。
最終設計の方向性
最終的には、MCP サーバーを /opt/containers/runtime/mcps/run.sh で統一起動する方針にした。ここから podman run --rm を呼ぶ形にしておけば、依存関係の隔離、volume のスコープ管理、後始末の単純化をまとめて扱える。
このラッパーを VSCode の MCP command から呼べば、remote SSH のサブプロセスとして起動し、セッション終了で自動終了し、コンテナも --rm で残らない。今の段階ではこれで十分実用的だった。
将来的には、頻繁に使う MCP だけ SSE 化して quadlet で常駐させる選択肢もある。ただし現時点では、まず subprocess ベースで安定運用できることのほうが重要だった。常駐化は、利用頻度と起動コストが見合ったところで切り出せばよい。
結論
今回の結論は単純で、Zed は MCP をローカル起点で扱う設計のため、remote 側 runtime をそのまま使いたい用途には根本的に合わなかった。一方で VSCode Remote SSH は MCP command が remote 側サブプロセスとして動くため、compute server の runtime を直接使う構成と自然に噛み合った。
その結果、MCP が必要な作業は VSCode Remote SSH、純粋な編集作業は Zed(ext/LSP 切り) という使い分けに落ち着いた。MCP の運用は VSCode + Remote SSH + subprocess 方式を基本にし、隔離や移植性の問題は podman run --rm と /opt/containers/runtime/mcps/run.sh で吸収する。
当初の「compute 側にエディタサーバーを立てる」計画からは外れたが、M1 Mac を遊ばせないという実利を取った結果、エディタは Mac 側・MCP 実行は compute 側という分業になった。遠回りに見えても、実行主体を正しく揃えるほうが、最終的には一番シンプルだった。
今後の作業
次に補うべきなのは、実際の run.sh インターフェースと、VSCode 側 mcp.servers 設定例の固定化だ。比較メモとしては十分でも、運用テンプレートとして再利用するには、引数、環境変数、volume マウント規約を明文化したほうが良い。
また、常駐型 SSE MCP へ移る基準も今のうちに切り分けておきたい。起動回数、初期化コスト、共有キャッシュの必要性が高いものだけを常駐化し、それ以外は subprocess のままにする、というルールを作れば運用がぶれにくい。
