TL;DR
- 課題: サーバーとテストクライアントで型定義を共有する「内閉的な検証」は、規約(JSON等)の微細な不整合を見逃す死角を生む
- 解決策: 実装(Rust)から物理的に遮断された別言語(Python/Node.js等)による、「冷酷な契約検証(Cold Contract Validation)」 を導入する
- 結論: 単一エコシステムへの過信を捨て、外部規約(Spec)を唯一の正典として叩き続ける「意図的な不便さ」こそが、真に堅牢な境界防御を実現する
第1章:設計仮定の明示 — 「型の安全性」が招く思わぬ死角
プロセス間通信を伴うアプリケーションの設計において、Rust のような強力な型システムを持つ言語を採用することは、論理的な一貫性を保つ上で非常に有効な手段となります。しかし、外部インターフェース(特に stdio を介した MCP や JSON-RPC 等のプロトコル)を扱う際、この「型の強固さ」が、思わぬ死角を作り出すことがあります。
私たちは、内部のユニットテストがパスし、cargo test が正常に終了していれば、外部からの呼び出しも正しく動作すると信じがちです。しかし、実は「内部の整合性」と「外部との契約」は、全く別のレイヤーに属する問題です。
第2章:観測された不整合 — 「型は通るが規約に違反する」事象
この死角が最も顕著に現れるのが、JSON 上での「値の不在」の表現です。例えば、Rust の型定義で Option<T> を用いるケースを考えます。多くの MCP 規約では、オプションのフィールドについて「null を許容するのではなく、キー自体が存在しないこと」を厳密に要求する場合があります。
// 規約が求める形式 (フィールド欠落)
{ "id": 1, "method": "foo" }
// 実装が誤って出力する形式 (null)
{ "id": 1, "method": "foo", "params": null }
Rust 内部で Serde を用いてシリアライズする際、アトリビュートの設定(skip_serializing_if 等)を誤れば、上記のように null が出力されます。この時、テストコード側でも同じ型定義を用いてデシリアライズしていれば、どちらも None として正しく処理されてしまうため、この微細な規約違反を検知することはできません。
第3章:仮定 de 破綻 — 内部剛性と外部流動性の相克
多くの開発現場では、開発の効率化(DRY 原則)のために、サーバー実装とテストクライアントの間で 型定義(Struct や Enum 等)を共有 します。一見、これは生産性の高い手法に思えますが、実は「境界検証」という観点からは重大な脆さを孕んでいます。
内部テストは「自分たちの実装」を正解として参照してしまう。これが、単一言語・単一プロジェクトに閉じた検証の限界です。
型定義を共有すると、サーバーとテストが「同じ誤り」を共有してしまう。これが境界検証における最大の構造的リスクだ。
第4章:再定義 — 「冷酷な契約検証(Cold Contract Validation)」
この「型の甘え」を構造的に排除するためには、内部の実装から物理的に遮断された 「独立プローブ」 による検証が必要です。これを、私たちは 「冷酷な契約検証(Cold Contract Validation)」 と呼んでいます。
「モノカルチャー」の引力に抗う
現代の開発環境(Web サービスネイティブな環境)では、開発言語の統一による生産性の向上が重視されます。しかし、境界検証において言語の同一性は「論理の一致(Single Point of Logic Failure)」のリスクを高めます。異なるエコシステム(例:Python の Pydantic 等)を用いて、異なる視点からプロトコルの境界線を物理的に叩き、検証し続ける。この「不便さ」を選択することこそが、堅牢なシステムを維持するための構造的な必然性となります。
第5章:実装原理(核心のみ) — 独立プローブによる「物理的な遮断」
あえて Python や Node.js 等の別言語を用いて検証プローブを構築するのには、明確な構造上の理由があります。
- 他言語であれば、Rust の内部型定義を import することは物理的に不可能である
- 検証コードは、プロトコル規約(JSON Schema 等)を出発点として、ゼロから期待値を定義することを強制される
- エコシステムを分断することで、暗黙の前提(Serde の既定値等)を共有するリスクを排除する
DoD:境界防御における「完了」の定義
私たちは、以下の 3 条件をすべて満たした時のみ、その境界は「守られている」と定義(Definition of Done)します。
- 物理遮断の完遂: 検証コードが, サーバー実装の内部型定義(Serde 等)を 1 行も import していないこと
- 異言語プローブパス: 実装とは異なるランタイム(Python/Node.js 等)からの要求に対し、プロトコル規約通りの挙動が確認されていること
- ゼロ・トラスト検証: オプションフィールドの不在や null の扱いなど、実装側の「既定値」に依存しない厳格な契約検証がパスしていること
第6章:結果と帰結 — 「泥臭さ」という名の高級な設計
今の時代、AIがコードを書き、強力な型システムがバグを未然に防ぐのが「当たり前」とされる中で、このアプローチは間違いなく**「時代に逆行した、かつ意図的に泥臭い」**手法です。
なぜこれが「今時」の逆を行く泥臭さなのか、そしてなぜ今それが価値を持つのか。
ここからは、効率化の波に飲まれがちな現代の開発現場に対して、あえて逆張りする “老エンジニアの視点” を整理してみます。
1. 「効率」という甘い罠への反逆
今のトレンドは「生産性」です。
今時の正論: 「同じ型定義を共有(DRY)して、サーバーもクライアントも自動生成しよう。そうすれば修正は一箇所で済む。速いし, ミスもない。」
この記事の泥臭さ: 「いや、あえて共有しない。同じことを二度書け。別々の言語で、別々の苦労をしろ。」
これは、効率化のために「検証の独立性」を犠牲にしている現代への警告です。同じ金型(型定義)から作った製品同士をぶつけて「ほら、ぴったり合う(テスト成功)」と喜んでいる若手に、「金型そのものが歪んでいたらどうする?」と問いかける泥臭さです。
2. 「型システム」という聖域からの脱却
Rustのような言語を使っていると、「コンパイラは神」になります。
今時の正論: 「型さえ合っていれば、論理的に正しいことが保証される。」
この記事の泥臭さ: 「型なんてものは, バイナリの外に出ればただの言い訳だ。Pythonという『型に無論着な余所者』に, 生データ(JSON)を冷酷に突き刺させろ。」
最強の武器(Rust)を持ちながら、あえてそれを使わずに「素手(生データ検証)」で挑む。この「過信を捨てるときのストイックさ」が、最高に泥臭いんです。
3. 「境界防御」という名の保険料
今時の正論: 「テストコードは最小限に。保守コストを下げよう。」
この記事の泥臭さ: 「二重管理のコスト? 構造的な不便さ? ああ、全部受け入れよう。それが『境界を守る』ための保険料だ。」
動けばいい、納期が、効率が……。そんな言い訳を「リスクの非対称性」という言葉で一蹴し、あえて面倒な道を選ぶ。この姿勢は、スピード重視の現代では非常に「重たい(泥臭い)」決断です。
まとめ:内部は「型」で守り、境界は「独立した目」で疑う
この「泥臭さ」こそが「高級な設計」です。今の時代、誰でも「動くもの」は作れます。しかし、**「壊れないもの」**を作るには、こうした意図的な不便さが不可欠です。
自動生成されたテストは、鏡に映った自分とジャンケンをしているだけ。独立プローブは、本物の敵(外部仕様)を連れてくる作業。
この記事が提示しているのは、単なる古い手法への回帰ではなく、**「便利さに溺れて視力を失った現代のエンジニアに対する, 外科手術のようなアプローチ」**です。
「泥臭い」と言われたら、ニヤリと笑って「そうさ。泥の中でしか見えない真実(バグ)ってのがあってね」と返せるような, そんな深みのある設計を目指すべきではないでしょうか。
型システムの恩恵を享受しつつ、その限界を知り、境界線を冷酷に守り抜く。この二重の防御責任こそが、複雑な MCP エコシステムにおいて真に安定したサービスを提供するための鍵となるでしょう。
