MCP(Model Context Protocol)はAIのツール接続標準になりつつある
1. 概要
大規模言語モデル(LLM)は、実データを読んだり、実際のシステムを呼び出したりできるときだけ本当に役立つ:注文を検索したり、チケットを読んだり、クエリを実行したり。以前は、各AIアプリケーションがそれぞれのツールに対して独自の統合方法を書いていました。Model Context Protocol(MCP)は、この接続部分を標準化するために生まれました。
この記事ではMCPとは何か、なぜ注目されているのかを説明します。その後、Spring BootでMCPサーバーを小規模で書き、クライアントを設定して呼び出し、いつ使うべきではないかを知ります。
2. MCPとは?
MCPはオープンプロトコルです。これは、AIアプリケーションが外部機能(ツール、データ、サンプルプロンプト)を発見して呼び出す方法を定義します。MCPはAIの「USBポート」と考えることができます。標準に従う任意のデバイスが、その標準をサポートする任意のホストに接続して動作します。
2.1. 3つの主要な役割
- ホスト:ユーザーが対話するアプリケーション。例えば、デスクトップ上のAIアシスタント、IDE、または社内チャットボット。
- クライアント:ホスト内に位置するコンポーネント。各クライアントは1つのサーバーとの1対1接続を保持します。
- サーバー:AIに機能を提供するプログラム。例えば、注文データベースを読むサーバーまたはファイルシステムにアクセスするサーバー。
2.2. サーバー側の3つの「プリミティブ」
- ツール:モデルが呼び出すことを決定できる関数。例えば、
findOrderやcancelOrderなど。 - リソース:ファイルの内容や設定レコードなどの読み取り専用データ。コンテキストに挿入されます。
- プロンプト:ユーザーが選択して使用できるあらかじめ用意されたプロンプトテンプレート。
2.3. 下層のプロトコル
MCPはJSON-RPC 2.0を使用します。最も一般的なトランスポートは2種類です:ホストがサーバーを子プロセスとして起動する場合のstdioと、サーバーがリモートで実行される場合のHTTP。ツール呼び出しは次のようになります:
{
"jsonrpc": "2.0",
"id": 7,
"method": "tools/call",
"params": {
"name": "findOrder",
"arguments": { "orderId": "ORD-1001" }
}
}
基本的なワークフローは3つのステップで構成されます:
- クライアントが
initializeを送信して、双方がプロトコルバージョンとサポート機能を交換します。 - クライアントが
tools/listを呼び出して、ツールのリスト、その説明、およびパラメータのJSONスキーマを取得します。 - モデルがツールを使用することを決定したら、クライアントが上記の例のように
tools/callを送信し、その結果をモデルに返します。
重要な点:モデルはサーバーを直接呼び出しません。ホストが仲介役となるため、ホストは機密性の高いツールを実行する前にユーザーに確認を求めることができます。
3. なぜMCPが注目されているのか?
3.1. N × M問題
N個のAIアプリケーションとM個のシステムを接続する必要があるとします。共通の標準がなければ、N × Mの統合を書く必要があります。共通のプロトコルがあれば、各アプリケーションはMCPを一度だけサポートすればよく、各システムはMCPサーバーを1つだけ必要とします。問題はN + Mに減ります。
3.2. 責任の分離
注文システムを所有するチームが独自のMCPサーバーを書いて運用します。チャットボットを作るチームはそのサーバーをポイントするだけです。ビジネスロジック、アクセス権、監査ログは正しい場所にあり、各プロンプトや各アプリケーションに複製されません。
3.3. モデルプロバイダーにロックインされない
各LLMプロバイダーの関数呼び出しには独自の形式があります。MCPはその形式の上の層に位置します。今日書いたサーバーは、モデルを切り替えるか、ホストを切り替えるときでも使用できます。
3.4. エコシステムが成長している
ますます多くのAIアシスタント、IDE、フレームワークがMCPをサポートしています。多くの言語のSDKがあり、Javaは公式なSDKを持ち、Spring AIは組み込みサポートがあります。これが、多くの人がMCPをツール接続標準の有力候補と見なす理由です。ただし、プロトコルはまだ開発中なので、長期的な展開時には仕様を追跡する必要があります。
4. Spring BootでMCPサーバーを書く
注文を検索してキャンセルするための2つのツールを提供するサーバーを構築します。stdioで実行されます。
4.1. 依存関係
Spring AIはMCPサーバーのスターターを提供しています。Spring AI BOMをインポートし、Spring Boot 3.xとの最新互換バージョンを使用してください:
<properties>
<java.version>21</java.version>
<spring-ai.version><!-- 現在のSpring AIバージョン --></spring-ai.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server</artifactId>
</dependency>
</dependencies>
スターターspring-ai-starter-mcp-serverはstdioトランスポートをサポートしています。HTTPでサーバーを実行する場合、Spring AIはWeb MVCおよびWebFlux用の個別のスターターを持っています。
注意: Spring AIのアーティファクト名はマイルストーン間で変わっています。選択したバージョンに応じたドキュメントを確認してください。
4.2. ドメインとツール
public record Order(String id, String customerName, double total, String status) {}
@Service
public class OrderTools {
private final Map<String, Order> orders = new ConcurrentHashMap<>(Map.of(
"ORD-1001", new Order("ORD-1001", "Nguyen Van An", 350_000, "SHIPPING"),
"ORD-1002", new Order("ORD-1002", "Tran Thi Binh", 120_000, "PENDING")
));
@Tool(description = "注文コードで注文を検索し、顧客、合計、ステータスを返す")
public Order findOrder(@ToolParam(description = "注文コード、例:ORD-1001") String orderId) {
Order order = orders.get(orderId);
if (order == null) {
throw new IllegalArgumentException("注文が見つかりません " + orderId);
}
return order;
}
@Tool(description = "注文をキャンセルする。PENDING状態の注文のみキャンセルできる")
public String cancelOrder(@ToolParam(description = "キャンセルする注文コード") String orderId) {
Order order = findOrder(orderId);
if (!"PENDING".equals(order.status())) {
return "キャンセルできません:注文は" + order.status() + "状態です";
}
orders.put(orderId, new Order(order.id(), order.customerName(), order.total(), "CANCELLED"));
return "注文" + orderId + "をキャンセルしました";
}
}
2つのアノテーションが主要な役割を果たします:
@Tool(パッケージorg.springframework.ai.tool.annotation)はメソッドをツールとしてマークします。description部分はモデルがツールを呼び出すか決定するために読む内容です。説明がより明確であるほど、モデルは正しく選択します。@ToolParamは各パラメータを説明します。Spring AIはパラメータの型とこの説明からJSONスキーマを生成します。
cancelOrderは、ビジネスルール違反時に例外をスローする代わりにメッセージを返すことに注意してください。モデルそのメッセージを読んで、ユーザーに自然な方法で説明し直します。
4.3. ツールをMCPサーバーに登録する
@SpringBootApplication
public class OrderMcpServerApplication {
public static void main(String[] args) {
SpringApplication.run(OrderMcpServerApplication.class, args);
}
@Bean
ToolCallbackProvider orderToolProvider(OrderTools orderTools) {
return MethodToolCallbackProvider.builder()
.toolObjects(orderTools)
.build();
}
}
MethodToolCallbackProviderはorderTools内の@Toolを持つメソッドをスキャンします。MCPサーバーのオートコンフィギュレーションは、すべてのToolCallbackProvider beanを見つけてtools/list経由で公開します。
4.4. stdioの設定
spring.ai.mcp.server.name=order-mcp-server
spring.ai.mcp.server.version=0.0.1
spring.ai.mcp.server.stdio=true
spring.main.web-application-type=none
spring.main.banner-mode=off
logging.pattern.console=
logging.file.name=./logs/order-mcp-server.log
stdioの場合、stdoutはJSON-RPCチャネルです。stdoutに出力される他のものはすべてプロトコルを破損します。バナーやログを含みます。したがって、上記の設定はバナーをオフにし、コンソールへのログをオフにして、ログをファイルに転送します。
注意: これはstdioでMCPサーバーを初めて作成するときに最も一般的なエラーです。デバッグ用の1行の
System.out.printlnでもクライアントにパースエラーまたは接続を切断させることができます。ログをファイルに記録するロガーを使用してください。
JARファイルをビルドします:
./mvnw clean package -DskipTests
5. クライアントから接続する
多くのホストはmcpServersキーを持つJSONファイルでサーバーを設定しています。ファイル名と場所はホストごとに異なります:
{
"mcpServers": {
"orders": {
"command": "java",
"args": ["-jar", "/opt/mcp/order-mcp-server.jar"]
}
}
}
起動時、ホストは子プロセスとしてjava -jar ...コマンドを実行し、initializeを送信してからtools/listを送信します。あなたが聞きます:「注文ORD-1002をまだキャンセルできますか?できればキャンセルしてください。」モデルはfindOrderを呼び出してからcancelOrderを呼び出します。2番目の呼び出しに対して返される結果は次のようになります:
{"content":[{"type":"text","text":"注文ORD-1002をキャンセルしました"}],"isError":false}
通常、ホストはツールを実行する前に許可ダイアログを表示します。cancelOrderなどの副作用があるツールでこの動作を保つべきです。
あなたのSpringアプリケーションがクライアントの役割を果たす場合、Spring AIにはMCPクライアントスターターもあります。このスターターは、外部サーバーのツールをChatClientで使用可能なツールに変換します。
6. ツールの動作をテストする
ツールは単なる通常のJavaメソッドなので、MCPを起動せずにテストできます:
class OrderToolsTest {
private final OrderTools tools = new OrderTools();
@Test
void shouldCancelPendingOrder() {
String result = tools.cancelOrder("ORD-1002");
assertThat(result).isEqualTo("注文ORD-1002をキャンセルしました");
assertThat(tools.findOrder("ORD-1002").status()).isEqualTo("CANCELLED");
}
@Test
void shouldRefuseToCancelShippingOrder() {
assertThat(tools.cancelOrder("ORD-1001"))
.contains("キャンセルできません")
.contains("SHIPPING");
}
@Test
void shouldFailWhenOrderNotFound() {
assertThatThrownBy(() -> tools.findOrder("ORD-9999"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("ORD-9999");
}
}
3つのテストは、成功するフロー、ビジネスルール、注文が見つからないケースをカバーしています。プロトコル部分はSDKで処理されます。あなたが最も確認すべき点は、ビジネスロジックが正しい結果を返し、モデルが理解しやすいメッセージを返すことです。
エンドツーエンドチェックを実行するには、MCP用のインスペクターツールを使用できます。これらを使用して、ツールをリストして手動でテスト呼び出しを実行できます。
7. 実際の適用:何に注意すべきか
- ビジネスロジックに応じてツールを設計し、テーブルに応じてではなく。
findOrderは万能なexecuteSqlツールより優れています。狭いツールは権限を制御しやすく、モデルが正しく選択するのに役立ちます。 - 最小権限の原則。 サーバーは必要な権限のみを持つアカウントを使用すべきです。読み取りツールと書き込みツールを分離し、書き込みツールを一部の環境でのみ有効にすることを検討してください。
- プロンプトインジェクションに注意してください。 ツールが返すデータ(顧客が入力したメモなど)は、モデルを制御することを目的とするコマンドを含む可能性があります。ツールからの結果が、人間の確認なしに危険なアクションを自動的にトリガーしないようにしてください。
- 信頼できるサーバーのみを使用してください。 stdinで実行される第三者のMCPサーバーはあなたのマシン上のプロセスであり、あなたの権限で実行されます。ツールの説明には悪意のあるコンテンツが含まれている可能性があります。ソースコードを読むか、信頼できるソースのみを使用してください。
- リモートサーバーの認証。 HTTPで実行する場合、サーバーを適切な認証メカニズムの後ろに配置し、すべてのツール呼び出しの監査ログを記録してください。
- ツール数を適切に保つ。 各ツールはその説明のためにコンテキストを消費します。ツールが多すぎるとモデルが選択を誤りやすくなります。
8. MCPを使うべきではない場合
- 小規模なアプリケーション、1つのモデルのみ、わずかな関数。 フレームワークの直接関数呼び出し(例えば、
ChatClientで直接使用される@Tool)はよりシンプルです。追加のプロセスとプロトコル層は必要ありません。 - 固定された処理フロー、モデル決定が不要。 ステップ1が常にAPI Aを呼び出し、ステップ2が常にAPI Bを呼び出す場合、通常のコードを書いてください。
ifステートメントが上手く処理できる作業をLLMに委ねるべきではありません。 - 超低遅延または高い決定論が必要。 モデル→クライアント→サーバーの各ラウンドは遅延を追加します。モデルの結果も完全に決定論的ではありません。
- 人間の審査なしに取り消せない操作。 送金やバッチデータ削除は、自動ツールとして公開すべきではありません。必須の場合は、常に確認ステップを含めてください。
- 他のシステムが呼び出すAPIが必要。 MCPはAIに対応するために作成されました。サービス間では、REST、gRPC、またはメッセージングが依然として適切な選択肢です。
9. 結論
MCPは、各アプリケーションが独自の方法を書く代わりに、AIをツールとデータに一般的な方法で接続する、非常に実用的な問題を解決します。ホスト–クライアント–サーバーモデル、3つのプリミティブ(ツール/リソース/プロンプト)、およびJSON-RPCベースのおかげで、あなたが書いたサーバーは複数の異なるホストで再利用できます。
Spring AIを使用すると、MCPサーバーを書くには、いくつかの@Toolアノテーション、1つのToolCallbackProvider beanとstdioの適切な設定が必要です。難しい部分は他にあります:ツールの狭く明確な設計、厳密な権限、プロンプトインジェクションへの対策。通常のコードが依然としてより良い解決策である場合を認識する冷静さも必要です。