はじめに

当初は 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 側の uvxpython、コンテナ 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 側の uvxpython が使われる、とはならなかった。

実際には、Zed 本体がローカル環境からコマンドを起動しようとする。そのため、remote 側にインストールしてある runtime を使いたくても、起点がローカルである以上、PATH 解決の時点でずれてしまう。ここが根本原因だった。

さらに、Zed の MCP エントリは stdio ベースでローカル起点として扱われるため、remote 上の MCP サーバーをそのまま自然にぶら下げる構造に向いていなかった。mcp-remote のような proxy 案も考えたが、SSE 非対応という制約があり、回り道が増えるわりに運用が素直にならない。

Zed で遭遇した具体的な問題

実際に起きた問題はかなり明確だった。

  • remote 側に入っている uvxserena が参照されない
  • ローカルの uvx 経由で serena を起動しても、compute server 側の PATH を使えない
  • port forward を張っても、MCP の起動主体がローカルのままなので効果が薄い
  • remote 側で LISTEN するプロセスが増えず、観測上も remote MCP が生えていない

つまり、個別の設定ミスというより、実行主体そのものが自分の要件と合っていなかった。ここを無理に調整しても、例外処理ばかり増えていく。

VSCode がうまくいった決定的な理由

VSCode Remote SSH では事情がまったく違った。拡張ホスト自体が remote 側で動くため、mcp.serverscommandargs がそのまま 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 のままにする、というルールを作れば運用がぶれにくい。