GitHubのトレンドを眺めていたら、Desktop Commander MCP というのが一番上に来ていました。説明には「Claude にターミナル操作・ファイル検索・差分編集の能力を与える MCP サーバー」とあります。要するに、AIエージェントにマシンの操作権を渡すためのツール側の実装ですね。
こういうのは普段、Claude Desktop なり対応クライアントなりの設定に一行足して、あとはチャットから「このファイルを直して」とお願いして使うものです。ただ、私はMCPサーバーを見ると、つい「クライアント越しに便利に見えているだけで、中では結局どんなツールを、どんな既定値で公開しているんだ?」というのが気になる性分でして。
今回は普段の使い方をあえて外して、クライアントを一切介さず、自分で書いた小さなクライアントから生のJSON-RPCで直接叩くというやり方で中身を確かめてみました。エージェントに任せると見えてこない部分が、直接叩くとけっこう見えてきます。
試した環境
いつもどおり、ホスト(Windows)は汚したくないので使い捨てのDockerコンテナの中だけで完結させています。立てて → 動かして → 壊す、の流れです。
| 項目 | 値 |
|---|---|
| ベースイメージ | node:22-bookworm |
| Node.js | v22.23.1 |
| desktop-commander | v0.2.44(npm @wonderwhy-er/desktop-commander) |
| プロトコル | MCP 2024-11-05(stdio / JSON-RPC) |
インストールは npm install @wonderwhy-er/desktop-commander の一発で、535パッケージが21秒で入りました。MCPサーバーの本体は dist/index.js で、これを標準入出力(stdio)でつなぐと、行区切りのJSONでリクエストとレスポンスをやり取りできます。
クライアントといっても大したものは要りません。child_process.spawn でサーバーを起動して、initialize → notifications/initialized → tools/list を投げるだけの、40行ほどのNodeスクリプトを書きました。まずは握手して、何が使えるのかを一覧します。
INIT protocolVersion: 2024-11-05
INIT serverInfo: {"name":"desktop-commander","version":"0.2.44"}
INIT capabilities keys: tools,resources,prompts,logging
TOOLS count: 26
- get_config
- read_file
- write_file
- edit_block
- start_process
- read_process_output
- interact_with_process
- list_directory
- ...(全26個)
ツールが26個。ファイル読み書き、ディレクトリ操作、検索、そして start_process / read_process_output / interact_with_process というプロセス操作系が一式そろっています。この「プロセスと対話する」あたりが後で一番おもしろかったので、先に全体像だけ。
まず設定を覗く——ブロックリストとtelemetryの既定値
ツール一覧に get_config があったので、真っ先にこれを叩きました。導入する前に既定値を確認するのは癖みたいなものです。返ってきた設定の要点がこれです。
$ get_config
{
"blockedCommands": [
"mkfs","format","mount","umount","fdisk","dd","parted","diskpart",
"sudo","su","passwd","adduser","useradd","usermod",
"shutdown","reboot","halt","poweroff","init",
"iptables","firewall","netsh","reg","net","sc","runas","takeown"
],
"defaultShell": "/bin/sh",
"allowedDirectories": [],
"telemetryEnabled": true,
"fileWriteLineLimit": 50,
"fileReadLineLimit": 1000,
"version": "0.2.44"
}
なるほど、と思うところがいくつかありました。
blockedCommands に dd や mkfs、sudo、reboot、iptables あたりがずらっと並んでいます。ディスクを壊す・権限を奪う・ネットワークをいじる系はデフォルトで禁止、という方針は堅実ですね。あとで実際に弾かれるところも見ます。
一方で allowedDirectories が []、つまり空です。これは「どこも許可していない」ではなく実質「どこでも触れる」の意味で、コマンドはブロックリストで縛るけれど、ファイルパスの範囲はデフォルトでは絞っていない、という非対称な作りになっています。エージェントに丸ごと渡す前提だと、ここは運用で最初に絞っておきたいポイントだな、と感じました。telemetryEnabled が既定で true なのも、社内利用を考えるなら把握しておきたいところです。
こういう「既定値の思想」は、クライアント越しにチャットしているだけだとまず目に入りません。直接叩くと最初の一手で見えるので、やっぱり覗いておいてよかったです。
start_processで一発コマンド、からのwrite_fileでいきなりコケる
次に、ツールを実際に呼びます。JSON-RPC の tools/call に名前と引数を渡すだけです。まずは start_process でシェルコマンドを一発。
$ start_process { command: "uname -sm; echo NODE=$(node -v); whoami; pwd" }
Process started with PID 255 (shell: /bin/sh)
Initial output:
Linux x86_64
NODE=v22.23.1
USER=root
/work
PIDを振って /bin/sh 経由で実行し、初期出力まで返してくれます。ここまでは素直。
気をよくして、今度はファイルを書こうと write_file を呼んだら、いきなりコケました。これが最初のアプローチの失敗です。
$ write_file { path: "/work/demo/greeting.txt", content: "..." }
Error: ENOENT: no such file or directory, open '/work/demo/greeting.txt'
一瞬「権限か? allowedDirectories が空なのが効いてるのか?」と疑ったんですが、よく見ると ENOENT で、単に親ディレクトリ /work/demo が無いだけでした。write_file は mkdir -p 相当を勝手にはやってくれない、という素直な挙動です。エッジケースというほどでもないですが、open 系のエラーを権限エラーと早合点しかけたのは反省点でした。
改善後のアプローチは単純で、先に create_directory を通してから書けば済みます。
$ create_directory { path: "/work/demo" }
Successfully created directory /work/demo
$ write_file { path: "/work/demo/greeting.txt", content: "hello desktop-commander\nline two\nTODO: replace me\n" }
Successfully wrote to /work/demo/greeting.txt (4 lines)
$ read_file { path: "/work/demo/greeting.txt" }
[Reading 3 lines from start (total: 3 lines, 0 remaining)]
hello desktop-commander
line two
TODO: replace me
read_file が [Reading 3 lines from start (total: 3 lines, 0 remaining)] というヘッダを付けて返すのが目を引きました。大きなファイルをうっかり全部読んでコンテキストを溢れさせないように、行数と残りを常に添えてくる設計です。エージェントに読ませる前提だと、この手のガードは効いてくるだろうな、と思います。
差分編集の看板ツール edit_block も試します。狙った一文だけを外科手術的に差し替える、というものです。
$ edit_block { file_path: ".../greeting.txt", old_string: "TODO: replace me", new_string: "edited via edit_block" }
$ read_file { path: ".../greeting.txt" }
hello desktop-commander
line two
edited via edit_block
TODO: replace me の行だけが edited via edit_block に置き換わりました。ファイル全体を書き戻すのではなく該当箇所だけを触るので、差分が小さく済むのは実務的にありがたい作りです。
一番おもしろかったのはプロセスとの対話——node REPLに式を投げると42が返る
interact_with_process というツールが気になっていました。説明に「SSH・データベース・開発サーバーのような、動いているプロセスと対話できる」とあります。要するに、起動しっぱなしのプロセスに後から入力を流し込めるということです。
試しに、コンテナの中でNodeのREPL(node -i)を裏で起動させて、そこに式を送り込んでみました。
$ start_process { command: "node -i", timeout_ms: 8000 }
Process started with PID 287 (shell: /bin/sh)
Initial output:
Welcome to Node.js v22.23.1.
Type ".help" for more information.
>
🔄 Process 287 is waiting for input (detected: ">")
ここがうまくできているところで、出力の末尾に > を見つけると「このプロセスは入力待ちだ(プロンプト > を検出)」と判断して返してきます。プロンプトの形でREPLの手番を認識しているわけです。
で、この待っているプロセスに向かって式を一つ流し込みます。
$ interact_with_process { pid: 287, input: "const answer = 6 * 7; answer" }
✅ Input executed in process 287:
📤 Output:
42
🔄 Process 287 is waiting for input (detected: ">")
42 が返ってきました。別プロセスとして生きているNode REPLに、MCP経由で式を送り、評価結果を受け取れたわけです。一発コマンドを叩くだけなら他にもやりようはありますが、「状態を持ったまま動き続けるプロセスと何度もやり取りする」——たとえばDBのセッションを張りっぱなしにしてクエリを重ねる、みたいな使い方——を素直に扱えるのは、地味ですがけっこう効く設計だと思いました。ここが今回一番「へえ」と声が出たところです。
ブロックリストは効く、行数リミットは効かない
最後に、get_config で見えていた2つの既定値が実際どう振る舞うのかを確かめました。
まずブロックリスト。禁止されている sudo を含むコマンドをわざと投げてみます。
$ start_process { command: "sudo rm -rf /" }
Error: Command not allowed: sudo rm -rf /
きちんと実行前に弾かれました。Command not allowed で止まるので、ブロックリストは飾りではなく本当に効いています。
一方、fileWriteLineLimit が 50 になっていたので、これはハードリミットで50行を超えたら拒否されるのかと思い、60行のファイルを書いてみました。
$ write_file { path: "/work/demo/big.txt", content: "row 1\n...\nrow 60\n" }
Successfully wrote to /work/demo/big.txt (61 lines) ✅ File written successfully! (61 lines)
💡 Performance tip: For optimal speed, consider chunking files into
≤30 line pieces in future operations.
こちらは拒否ではなく、そのまま書けた上で「速度のために30行以下に分けるといいよ」という助言が返ってきました。fileWriteLineLimit は禁止のための壁ではなく、あくまでチャンク分割をうながすためのソフトな目安、という位置づけのようです。名前から早合点して「50行で弾かれる」と思い込んでいたので、ここも実際に動かして初めて分かった部類でした。
触ってみての所感
MCPサーバーを、普段のようにクライアント越しに使うのではなく、自分の小さなクライアントから直接叩いてみて分かったことを、自分用に整理しておきます。
initialize→tools/list→tools/callの3手で、公開ツール(今回は26個)と挙動を素の状態で確認できるget_configの既定値がいちばん情報量が多い。sudoなどはブロックする一方でallowedDirectoriesは空(=パス無制限)、telemetryは既定ON、という思想が読めるwrite_fileは親ディレクトリを作らない。ENOENTを権限エラーと早合点しないことedit_blockの外科的置換と、read_fileの行数ヘッダは、エージェントに読み書きさせる前提の配慮interact_with_processは状態を持つプロセスとの対話に効く。プロンプト検出で入力待ちを判断しているfileWriteLineLimitは壁ではなく助言。名前で挙動を決めつけず、実際に投げて確かめる
なぜクライアント経由で普通に使わなかったのか、と言われれば、その普通の使い方こそ「サーバーが何を許して何を禁じているか」を隠してしまうからです。エージェントに操作権を渡すツールほど、導入前に一度、素で叩いて既定値を自分の目で確認しておく価値があると考えています。少なくとも私の環境では、allowedDirectories を空のまま本番のエージェントに渡すことはしないと思います。ここは好みが分かれるところですが。
もっと良い確かめ方があったら教えてください。