PaPoo
cover

MCPサーバーにも「lockfile」が必要だという話

AIエージェント周りの話は、正直まだかなり“流動的”です。昨日までうまく動いていたのに、今日はなんだか挙動が変。そんな経験がある人なら、この記事の問題意識はかなり刺さるはずです。

記事の主張をひとことで言うと、​MCP serverの契約も、npm dependencyと同じように固定して差分を監視すべきだ、です。
ここでいう「契約」は、APIの名前や引数だけではありません。tools/list で返ってくる tool の名前、description、JSON schema、annotation まで含みます。

このあたり、かなり面白い視点です。ふつう dependency というと、ライブラリのバージョンや lockfile を思い浮かべますよね。でも MCP の世界では、AI が読む「説明文」まで実質的な仕様になっている。つまり、ドキュメントのつもりで書いた文が、そのままモデルの行動指示になっているわけです。怖いけど、たしかにそうです。

何がそんなに危ないのか

image_0003.svg

記事では、MCP dependency が静かに壊れるパターンを4つ挙げています。

まず一番厄介なのが、​description の書き換えです。tool 名も schema も同じなのに、説明文だけ変わる。人間の目には小さな修正に見えても、モデルはその文言を読んで「この tool は何をするのか」「いつ呼ぶべきか」を判断するので、振る舞いが変わる可能性があります。しかも schema diff では見逃します。ここが肝です。

次に、​required parameter の追加。これはわかりやすく壊れます。今まで通っていた呼び出しが invalid params で失敗する。しかも本番トラフィックで初めて気づく、という嫌なやつです。

3つ目は、​**readOnlyHint が true から false に変わる**こと。readOnlyHint は「この tool は安全そうか」を示すようなヒントです。これが変わると、今まで自由に呼ばせてよかったものが、実は状態を書き換えるかもしれない。安全だと思っていた道具が、急に危険物になる。かなり実務的な恐怖です。

image_0004.svg

4つ目は、​tool の消失。これは派手に壊れるのでまだマシで、少なくとも気づけます。最悪なのは、消えた tool の代わりにエージェントが別の手段で勝手に迂回することだと思います。そうなると、エラーではなく“想定外の成功”が起きるので、もっと見つけにくいです。

著者は何を作ったのか

この記事の著者は、こうした変更を検出するために mcpward というツールを作っています。役割はかなりシンプルで、

npx mcpward baseline
npx mcpward diff

image_0005.svg

この2つです。

baseline で、server の contract を lockfile のように保存する。
diff で、CI 上でその契約が変わっていたら build を落とす。

ここがうまいなと思いました。発想としてはとても素直です。npm の依存関係なら lockfile があるし、変更があれば diff を見る。なら MCP の server にも同じ発想を持ち込めばいいじゃないか、というわけです。AI 周辺の議論はどうしても抽象的になりがちですが、この記事はかなり実装寄りで、そこが好感触でした。

baseline に含めるのは、各 tool の名前、description の hash、input/output schema、annotations です。
そして更新後の server に対して diff を取ると、たとえばこんな感じで差分が出ます。

image_0006.svg

しかも build は exit code 1 で止まります。つまり、「壊れてから気づく」のではなく、「壊れそうな変更を先に止める」方向に寄せている。これはかなり DevOps っぽい考え方です。

でも、全部を壊れた扱いにすると困る

ここで大事なのが、​すべての変更を failure にしないことです。
著者は breaking / non-breaking の線引きをしています。

たとえば、​optional parameter の追加は基本的に問題なし。既存の呼び出しはそのまま通るので、普通は non-breaking と見なします。
逆に、​required field の追加や、​type を狭める変更、​enum を厳しくする変更は breaking です。既存クライアントが失敗しやすいからです。

image_0007.svg

さらに、​description の変更は「rug-pull」として扱い、デフォルトで failure にしています。
この “rug-pull” という言い方はかなり印象的でした。絨毯を引き抜く、つまり見た目は同じでも足元を崩す変更という意味です。技術用語というより、かなり率直な比喩です。私はこの感覚、わりと大事だと思います。MCP の description は単なる説明文じゃなくて、実質的な instruction だからです。

個人的には、この線引きがこの手のツールのいちばん難しい部分ではないかと思います。
「どこまでを破壊的変更とみなすか」は、仕様の話であると同時に運用の話でもあるからです。厳しすぎるとノイズだらけになるし、甘すぎると肝心な変更を見逃す。著者もこの判定ロジックを pure function にして、fixture 付きテストで固めていると書いています。ここはかなり真面目で、好感が持てます。

contract だけでなく、プロトコルの作法も見る

mcpward は、単に diff を見るだけではありません。実際の client として protocol compliance もチェックします。

image_0008.svg

たとえば、

こういうものを見ます。

中でも地味に重要なのが、​エラーの扱いが2層あるという話です。
MCP では、プロトコル自体のエラーと、tool 実行の失敗は別物です。前者は JSON-RPC error object、後者は isError: true を持つ成功レスポンスです。

image_0010.png

これ、最初はややこしいですが、実務ではかなり大事です。
たとえば「ファイルが見つからない」は tool の失敗であって、プロトコル全体の破綻ではありません。でも実装を雑にすると、これを JSON-RPC エラーとして返してしまう server がある。すると client 側のエラーハンドリングが全部ずれます。著者が「誰もここをチェックしていない」と言っているのは、かなり納得感があります。

security の匂いも拾う

このツールは、security っぽい変な気配も検出します。

説明文に injection 的な文言が混ざっていないか。
hidden unicode や zero-width 文字が入っていないか。
schema が API key や password を要求していないか。
readOnlyHint と見た目の挙動が矛盾していないか。

こういうのは、AI 時代ならではの地雷です。人間から見るとただのテキストでも、モデルにとっては行動を変える入力になり得る。しかも見た目では気づけない細工が混ざることもある。ここは本当にイヤな領域ですが、だからこそ機械的に見る価値があると思います。

image_0020.png

出力が SARIF 形式なのも実用的です。GitHub Security tab に流せるので、いかにも「セキュリティ検査しました」感がある。派手ではないけど、現場ではこういう接続の良さが効きます。

ちゃんと信用できるように、テストの作り方も慎重

こういう検査ツールでいちばん怖いのは、「何となく安心させるだけの置物」になることです。著者もそこをかなり警戒していて、fixture server を複数用意しています。

正しい server、意図的に壊した server、1種類ずつ変更した server、遅い server、poisoned な server。こういう controlled な環境で検査ロジックを育てているそうです。
さらに、各チェックには「ちゃんと赤くなる」negative test を用意している。これ、地味ですが大事です。常に green なテストは、ただの自己満足装置になりがちだからです。

image_0021.png

私はこの姿勢がかなり好きです。セキュリティや互換性の検査って、導入した瞬間に安心したくなるんですが、そこが一番危ない。信じたい気持ちをぐっとこらえて、壊れるケースを先に作る。こういう慎重さは、地味だけど強いです。

この記事の面白さは、「AI まわりの lockfile」という発想

この話の本質は、MCP の細かい仕様そのものより、​AI エージェントの依存先も、ふつうのソフトウェアと同じように固定・監査すべきだという視点だと思います。

私たちは npm dependency には慣れています。lockfile があって、diff を見て、怪しければ止める。
でも AI エージェントが参照する MCP server に対しては、まだその感覚が薄い。そこを「いや、同じでしょ」と持ち込んでいるのが気持ちいいんです。

image_0022.png

ただし、コメント欄でも指摘されているように、contract が同じでも実装が同じとは限りません。エンドポイントが同じ名前と schema を返していても、裏で動く binary や container が別物なら、依存関係としては別です。ここはこの記事の限界でもあるし、今後の拡張ポイントでもあるはずです。
個人的には、これはかなり本質的な指摘だと思います。contract の固定は必要。でもそれだけでは足りない。AI 周辺のサプライチェーンは、まだまだ見ないといけない層が多いです。

mcpward は MIT license で、ローカル完結、API call なし、telemetry なし。
このあたりも、内部 server を扱う現場にはありがたい設計です。外に説明文を送らずに済むのは、かなり安心材料でしょう。



参考: Pin your MCP server contracts the way you pin your dependencies

同じ著者の記事