Claude Media
JavaでMCPサーバーを書く手順とSpring AIとの関係

JavaでMCPサーバーを書く手順とSpring AIとの関係

MCP Java SDKでツールを実装する手順と、Spring AI 2.0でSpring連携が本体から分離された経緯を、動くコード付きで解説します。

JavaでMCPサーバーを書くと何ができるか

MCP(Model Context Protocol)のJava実装はio.modelcontextprotocol.sdk配下の公式SDKとして提供されており、社内APIやデータベースをツールとして公開するサーバーを純粋なJavaだけで書けます。SpringのWebフレームワークは必須ではありません。Mavenの依存関係を1つ足せば、stdio(標準入出力)方式のサーバーがものの数十行で動きます。

一方で、Spring Bootのアプリケーションに組み込みたい場合はSpring AIのMCP Boot Starterとアノテーションを使う道があります。この2つは別物です。Spring AI 2.0からは、Spring向けのトランスポート実装がMCP Java SDK本体から分離され、Spring AI側のモジュールに移りました。本記事は、①素のSDKでサーバーを書く手順、②Spring AIでの実装方法、③両者の役割分担、の3点を順に扱います。

MCPそのものの仕組みや他言語SDKとの比較はMCP実用ガイド、TypeScript・PythonでのMCPサーバー自作はMCPサーバー自作ガイドが扱っています。本記事はJavaとSpring AIの組み合わせに絞ります。

前提条件と3つの実装経路

Java SDKの動作要件はJava 17以上です。GitHub上の公式リポジトリのバッジがこれを明記しています。Mavenまたはgradleのビルド環境があれば準備は揃います。

実装経路は3つに分かれ、フレームワークへの依存度で選びます。

経路依存モジュール向く場面
素のJava(stdio)依存モジュールio.modelcontextprotocol.sdk:mcp向く場面ローカル起動、CLIツール、フレームワーク非依存
素のJava(Servlet HTTP)依存モジュールio.modelcontextprotocol.sdk:mcp向く場面Jakarta EEやQuarkusなどSpring以外のサーブレット環境
Spring AI Boot Starter依存モジュールorg.springframework.ai:spring-ai-starter-mcp-server(-webmvc/-webflux)向く場面既存のSpring Bootアプリに組み込む

Mavenで依存関係を揃える

最小構成は、コア実装とJackson 3系のJSONシリアライズをまとめたmcpモジュール1つです。バージョン管理はmcp-bom(Bill of Materials)をインポートして揃えます。

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>io.modelcontextprotocol.sdk</groupId>
      <artifactId>mcp-bom</artifactId>
      <version>2.0.1</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>
 
<dependencies>
  <dependency>
    <groupId>io.modelcontextprotocol.sdk</groupId>
    <artifactId>mcp</artifactId>
  </dependency>
</dependencies>

2.0.1はMaven Centralで公開されている値の一例です。BOMのバージョンは更新されるため、実際に指定する前にMaven Central上のmcp-bomページで最新値を確認してください。Jackson 2系を使いたいプロジェクトは、mcp-coremcp-json-jackson2を個別に組み合わせる構成も用意されています。

STDIOサーバーを実装する

stdioはローカル利用の第一候補です。クライアント(Claude Code)がサーバーをサブプロセスとして起動し、標準入出力でJSON-RPCメッセージをやり取りします。

手順1: ツールを1つ定義する

ツールの入力スキーマはJSON Schema文字列で渡します。Tool.builder(name, jsonMapper, schema)がスキーマ文字列をMapへ変換してくれるため、手書きのMap構築は不要です。

import io.modelcontextprotocol.json.McpJsonDefaults;
import io.modelcontextprotocol.server.McpServer;
import io.modelcontextprotocol.server.McpServerFeatures.SyncToolSpecification;
import io.modelcontextprotocol.server.McpSyncServer;
import io.modelcontextprotocol.server.transport.StdioServerTransportProvider;
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
import io.modelcontextprotocol.spec.McpSchema.TextContent;
import io.modelcontextprotocol.spec.McpSchema.Tool;
 
import java.util.List;
 
public class DocsServer {
 
    public static void main(String[] args) {
        var transportProvider = new StdioServerTransportProvider(McpJsonDefaults.getMapper());
 
        var schema = """
            {
              "type": "object",
              "properties": {
                "query": { "type": "string", "description": "検索キーワード" }
              },
              "required": ["query"]
            }
            """;
 
        var searchDocsTool = SyncToolSpecification.builder()
            .tool(Tool.builder("search-docs", McpJsonDefaults.getMapper(), schema)
                .description("社内ドキュメントを検索し、該当箇所を返す")
                .build())
            .callHandler((exchange, request) -> {
                String query = (String) request.arguments().get("query");
                String result = searchInternalDocs(query); // 自前の検索処理
                return CallToolResult.builder()
                    .content(List.of(TextContent.builder(result).build()))
                    .build();
            })
            .build();
 
        McpSyncServer server = McpServer.sync(transportProvider)
            .serverInfo("docs-server", "0.1.0")
            .capabilities(ServerCapabilities.builder()
                .tools(true)
                .build())
            .build();
 
        server.addTool(searchDocsTool);
    }
 
    private static String searchInternalDocs(String query) {
        return "Result: " + query; // 実装は自前の検索処理に置き換える
    }
}

callHandlerexchangeと呼び出しリクエストを受け取りCallToolResultを返すハンドラーです。引数のexchangeからはクライアントへのログ通知や進捗報告ができ、request.arguments()Map<String, Object>としてツール呼び出しの引数を返します。同期(McpServer.sync)と非同期(McpServer.async、戻り値はMonoベース)の両APIが用意されており、Reactorに慣れたチームは非同期版のほうが既存コードと馴染みます。

手順2: 起動してInspectorで確認する

ビルドしたjarをそのまま実行するか、Mavenのexecプラグインで起動します。stdioサーバーは起動しても画面に何も出さず、標準入力を待ち続けるのが正常な状態です。動作確認はMCP Inspectorに任せるのが効率的で、手順はMCPサーバー自作ガイドのInspector節と同じ流れが使えます。

npx @modelcontextprotocol/inspector java -jar target/docs-server.jar

手順3: Claude Codeに接続する

登録はclaude mcp addコマンドで行います。実行コマンドを--より後ろに書く点はTypeScript・Pythonのサーバーと同じです。

claude mcp add --transport stdio docs-server \
  -- java -jar /absolute/path/to/docs-server.jar

HTTPで公開する場合(Spring以外)

Spring Bootを使わないサーブレット環境(Jakarta EE、Quarkusなど)向けに、mcpモジュールはHttpServletStreamableServerTransportProviderを単体で提供しています。Spring依存を一切増やさずにStreamable HTTP方式のサーバーを組めるため、「Springは使っていないがHTTPで公開したい」というチームの選択肢になります。

HttpServletStreamableServerTransportProvider transportProvider =
    HttpServletStreamableServerTransportProvider.builder()
        .jsonMapper(McpJsonDefaults.getMapper())
        .mcpEndpoint("/mcp")
        .build();

生成したインスタンスはサーブレットそのものなので、標準のweb.xmlやアノテーション(@WebServlet)でどのURLにマッピングするか指定します。Spring Bootのサーブレット環境であればServletRegistrationBean<HttpServletStreamableServerTransportProvider>@Beanとして返す形でも登録でき、Spring AI Boot Starterを使わずに素のSDKだけでHTTPサーバーを組み込むことも可能です。ただし、その場合はセッション管理やOAuth連携をすべて自前で実装する必要があり、Spring AIのBoot Starterが肩代わりしてくれる範囲を自分で埋めることになります。

Spring AIでの実装 — Boot StarterとAnnotations

既存のSpring Bootアプリに組み込むなら、Spring AIのMCP Boot Starterが近道です。依存関係を1つ足すだけで、トランスポートの配線とSpringのDIコンテナへの統合が済みます。

サーバー種別依存設定
STDIO依存spring-ai-starter-mcp-server設定spring.ai.mcp.server.stdio=true
Streamable HTTP(Servlet)依存spring-ai-starter-mcp-server-webmvc設定spring.ai.mcp.server.protocol=STREAMABLE
Streamable HTTP(Reactive)依存spring-ai-starter-mcp-server-webflux設定spring.ai.mcp.server.protocol=STREAMABLE

Boot Starterに加えて、Spring AIはMCP Annotationsモジュールでアノテーションベースの実装も提供します。サーバー側は@McpTool @McpResource @McpPrompt @McpCompleteの4種で、Springの@Componentを書く感覚でツールを増やせます。

import org.springframework.ai.mcp.annotation.McpTool;
import org.springframework.ai.mcp.annotation.McpToolParam;
import org.springframework.stereotype.Component;
 
@Component
public class DocsTools {
 
    @McpTool(name = "search-docs", description = "社内ドキュメントを検索し、該当箇所を返す")
    public String searchDocs(@McpToolParam(description = "検索キーワード", required = true) String query) {
        return searchInternalDocs(query);
    }
}

依存関係はspring-ai-mcp-annotations(org.springframework.aiグループ)で、Boot Starterを使っていれば自動的に含まれます。引数の型と@McpToolParamの説明からJSON Schemaが自動生成されるため、手順1のような手書きスキーマは不要になります。この自動化がSpring AI経路の主な利点です。

Spring AI 2.0でSDKとの役割分担がどう変わったか

Spring AI 2.0より前は、Spring WebFlux・WebMVC向けのトランスポート実装(mcp-spring-webflux / mcp-spring-webmvc)がMCP Java SDK本体のモジュールとしてio.modelcontextprotocol.sdkグループに含まれていました。Spring AI 2.0でこの2つのアーティファクトはSDK本体から切り離され、org.springframework.aiグループの下でSpring AIプロジェクト自身が保守する形に変わりました。

この変更は破壊的変更(breaking change)です。旧グループIDで直接この2アーティファクトを参照していたプロジェクトは、依存関係とimport文の両方を更新する必要があります。現在の役割分担は次のとおりです。

  • MCP Java SDKチーム: プロトコルのコア実装(mcp-core)、stdio・Servlet HTTPトランスポート、JSON処理を保守
  • Spring AIチーム: Spring向けのトランスポート実装、Boot Starter、Annotationsモジュールを保守。MCP Java SDKを依存先として利用する

つまり「Spring AI MCP」は独立した別プロトコル実装ではなく、MCP Java SDKの上に立つフレームワーク統合層です。Spring特有の機能(OAuth 2.0によるMCPセキュリティ統合など)はSpring AI側に集約されており、SDK単体ではカバーしません。素のSDKで書き始めたサーバーをあとからSpring Bootへ移植する場合、プロトコルの実装(ツール定義・ハンドラー)はそのまま流用でき、書き換えが要るのはトランスポートの配線部分だけです。

よくあるつまずき

ビルドファイルの新旧が混在してエラーになる

Spring AI 2.0への移行期は、旧グループID(io.modelcontextprotocol.sdk:mcp-spring-webflux)と新グループID(org.springframework.ai:mcp-spring-webflux)が両方ネット上の記事に出回ります。依存関係を追加する前に、実際に使うSpring AIのバージョンに対応するグループIDを公式ドキュメントで確認してください。

Tool.builderの非推奨警告が出る

Tool.builder(name)(スキーマなし)やTool.builder()(引数なし)は非推奨扱いです。現行の推奨形はTool.builder(name, inputSchema)またはTool.builder(name, jsonMapper, schemaString)で、コンパイル時の警告が出たら呼び出し形を見直してください。

Tier 2ゆえに実例が少ない

Java SDKはTier 2のため、Tier 1のTypeScript・Pythonに比べると公式チュートリアルや第三者記事の蓄積が薄めです。実装で迷ったら、公式リポジトリのJavadocとテストコード(特にSyncToolSpecificationBuilderTestのようなビルダーのテストクラス)が最も正確な仕様書になります。

まとめ

Java SDKだけで完結させるならstdioサーバーが最短経路で、Mavenの依存関係1つとツール定義1つで動きます。既存のSpring Bootアプリに組み込むならSpring AIのBoot StarterとAnnotationsが近道ですが、これはSDKとは別のプロジェクトが保守する統合層である点を区別して理解しておくと、Spring AI 2.0のような破壊的変更が来たときに影響範囲を判断しやすくなります。フレームワーク非依存で書くか、Spring AIに寄せるかは、既存アプリへの組み込み要否で決めれば十分です。

他言語SDKとの機能差やTierの詳細はMCP SDK Tierシステムとは、実装したサーバーの権限設計はMCPセキュリティガイドを参照してください。

この記事を共有:XはてブLinkedIn