JavaでAIプログラミングをはじめよう

MCPサーバーとMCPクライアントを作る

AIの活用では、MCPという言葉が注目を浴びています。連載の最後に、JavaでMCPサーバー/クライアントを実装してみて、MCPへの理解を深めます。

なお、この記事で取り扱ったコードはこちらからダウンロードできます

MCPとは

MCP(Model Context Protocol)は、Function Calling(Tool Use)をJSON-RCPに基づいてリモートで呼び出す仕組みです。

Function Callingの場合は機能の実装をチャットプログラムと一緒に行う必要がありました。MCPではチャットプログラムと機能の実装を分離できるため、チャットプログラムにあとから機能を追加できるようになります。つまり、LM StudioやChatGPTのチャットアプリケーションに様々な機能を登録して利用できるわけです。また、さまざまなアプリケーションやサービスがMCPの規格に対応したため、チャットからいろいろな機能やサービスが呼び出せるようになりました。

MCPは、機能を実装するMCPサーバーと、MCPサーバーを呼び出すLLMアプリケーションであるMCPクライアントにわかれます。また、MCPの呼び出しにはサブプロセスとして起動したサーバーに標準入出力でやりとりを行うstdioと、HTTPによるやりとりを行うSSE(Server-Sent Event)があります。

JavaでMCPサーバーを実装する場合、起動時間の問題があるためstdioで呼び出す用途よりもサーバーを起動したままにしてSSEで呼び出すことのほうが多いと考えられます。そこで今回は、Spring Bootを使ったSSE型のMCPサーバーの実装と、LangChain4jを使ったMCPクライアントの実装を行います。

Spring AIによるMCPサーバーの実装

Spring BootでMCPサーバーを実装するには、Spring AIを使います。

プロジェクト作成

まずはSpring Bootのプロジェクトを作成します。

Spring Initializrで、Spring WebとModel Context Protocol Serverを含んだプロジェクトを作成します。Artifactは、ここではdemomcpとしています。

次のようなdependencyが追加されます。

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.ai</groupId>
      <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
    </dependency>

このようにすると、MCPサーバーがSSE型とstdio型の両方に対応します。

SSE型が不要でstdio型のMCPサーバーを作る場合にはSpring Webは不要です。Spring Webを含めずModel Context Protocol Serverを含めた場合、dependencyとしてspring-ai-starter-mcp-serverが追加されます。

ツールサービスの作成

MCPサーバーの機能実装になるツールサービスを作成します。

package com.example.demomcp;

import java.util.List;
import java.util.Random;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Service;

@Service
public class WeatherService {
    private final Random random = new Random();
    
    @Tool(description = "与えられた場所の天気の情報を取得します。")
    public String getWeather(String place) {
        var wethers = List.of("晴れ","晴れ", "雨", "曇り","曇り", "雪");
        return wethers.get(random.nextInt(wethers.size()));
    }
    @Tool(description = "現在時刻を返します")
    public String getTime() {
        return LocalDateTime.now().toString();
    }
}

このクラスは、基本的に前回のWeatherServiceクラスと同じです。LangChain4jでのFunction Calling(Tool Use)との違いは、@Serviceアノテーションがクラスについていることと、@Toolアノテーションのパッケージがorg.springframework.ai.tool.annotationで、説明にdescriptionの指定が必要になっていることです。

ツールの登録

サーバーが公開するツールとして登録します。

package com.example.demomcp;

import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class DemomcpApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemomcpApplication.class, args);
    }

    @Bean
    public ToolCallbackProvider weatherTools(WeatherService weatherService) {
        return MethodToolCallbackProvider.builder()
                .toolObjects(weatherService)
                .build();
    }
}

追加したのは、次のweatherToolsメソッドです。

@Bean
public ToolCallbackProvider weatherTools(WeatherService weatherService) {
    return MethodToolCallbackProvider.builder()
            .toolObjects(weatherService)
            .build();
}

戻り値をToolCallbackProviderとして、ここではMethodToolCallbackProviderを構築して返します。toolObjectsの登録には、メソッド引数として受け取ったオブジェクトを渡すようにします。引数には、Spring Frameworkが管理するWeatherServiceのオブジェクトがインジェクトされて渡されます。

起動

起動は通常のSpring Bootアプリケーションと同様です。mvnコマンドを使う場合はspring-boot:runを指定します。

> mvn spring-boot:run

起動ログのうち、McpServerAutoConfigurationの部分がMCPサーバーに関するログです。

このうち、Registered toolsが2になっていることを確認してください。

LM Studioから使う

それでは、このMCPサーバーをLM Studioから呼び出してみます。この場合、LM StudioがMCPクライアントということになります。

設定

画面右上のフラスコのアイコンをクリックすると、設定が開きます。

「Program」を開いて「Install」から「Edit mcp.json」をクリックします。

MCPでは任意のコードが実行できてしまうので、信用できないMCPサーバーを追加するなという警告が表示されますが、⁠Got It」ボタンを押します。

mcp.jsonがエディタで開くので、次のように編集します。

{
  "mcpServers": {
    "spring-ai-weather-sse": {
      "url": "http://localhost:8080/sse"
    }
  }
}

spring-ai-weather-sseが今回のMCPサーバーの登録名ということになります。他の名前でも構いません。SSE型の場合はurlを指定します。Spring BootでのMCPサーバーのパスは、sseになります。

入力したら「Save」ボタンを押します。

上手く登録できると、MCPサーバーとして表示されます。

なんらか問題があればエラーになります。Spring Bootアプリが起動できているか、URLやポートは正しいか確認してください。

使ってみる

それでは使ってみましょう。

入力欄のプラグのアイコンをクリックすると、ツール設定が表示されます。ここで「spring-ai-weather-sse」を有効にします。

「東京の天気は?」と聞いてみます。

すると、MCPでのツール呼び出しを行うかを確認されます。

ここで「Always allow...」のいずれかのチェックを入れると、今後は確認が出なくなります。

「Proceed」をクリックすると、ツールが呼び出されて、その応答を元に返答が作られて表示されます。

また、いま何月かを聞くと、getTimeの結果から「現在の月は7月(七月)です」という答えが返ってきます。

実際の返答は2025-07-05T03:44:35.267898100ですが、ここから月だけを答えています。

stdioモードでの登録

Spring AIでSSE型のMCPサーバーを実装する場合、自動的にstdio型にも対応可能になります。

stdioモードで登録する場合は、urlの代わりにcommandargsを指定します。

{
  "mcpServers": {
    "spring-ai-mcp-weather-stdio": {
      "command": "java",
      "args": [
         "-Dspring.ai.mcp.server.stdio=true",
         "-Dlogging.pattern.console=",
         "-Dspring.main.bannerMode=off",
         "-jar",
         "<projectdir>/target/demomcp-0.0.1-SNAPSHOT.jar"
      ]
    }
  }
}

<projectdir>にはプロジェクトフォルダを指定します。ここで、Windowsの場合はフォルダの区切りが\\になることに注意してください。例えば"C:\\demomcp\\target\\demomcp-0.0.1-SNAPSHOT.jar"のようになります。

また、3つの環境変数を指定していますが、これはapplication.propertiesに設定しても構いません。ただ、SSEとの共存を考えると環境変数での指定のほうがいいでしょう。

今回詳しく説明はしませんが、Claude DesktopではSSE型に対応しておらずstdio型で登録する必要があります。登録は、⁠ファイル>設定」から「開発者」のタブで「構成を編集」とすると出てくるclaude_desktop_config.jsonを編集して上記のように設定してください。

設定がうまくいくと、次のようにプロンプト入力のツール設定にMCPサーバーが表示されます。

次のように、ツールの呼び出しができます。

Claude DesktopでSSE型のMCPサーバーに接続したい場合には、mcp-remoteというnpmパッケージを使えばいいようです。

LangChain4jでのMCPクライアントからの接続

MCPサーバーが実装できてLM Studioからの接続もできました。そこで、LangChain4jを使ったMCPクライアントから接続を試してみましょう。

詳しくは次のドキュメントを参照してください。

Mavenプロジェクトのdependenciesには次のようにlangchain4j-mcpを加えてください。執筆時点ではbeta8でした。

<dependencies>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-mcp</artifactId>
        <version>1.2.0-beta8</version>
    </dependency>
    <dependency>
      <groupId>dev.langchain4j</groupId>
      <artifactId>langchain4j-open-ai</artifactId>
      <version>1.2.0</version>
    </dependency>
    <dependency>
      <groupId>dev.langchain4j</groupId>
      <artifactId>langchain4j-http-client-jdk</artifactId>
      <version>1.2.0</version>
    </dependency>
</dependencies>

第2回時点でのLangChain4jのバージョンは1.1.0でしたが、7月31日に1.2.0がリリースされました。MCP対応など周辺モジュールはbeta8になっています。

MCPサーバーを呼び出すコードは次のようになります。

import dev.langchain4j.http.client.jdk.JdkHttpClient;
import dev.langchain4j.mcp.McpToolProvider;
import dev.langchain4j.mcp.client.DefaultMcpClient;
import dev.langchain4j.mcp.client.McpClient;
import dev.langchain4j.mcp.client.transport.McpTransport;
import dev.langchain4j.mcp.client.transport.http.HttpMcpTransport;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.tool.ToolProvider;
import java.net.http.HttpClient;

public class McpClinet {
    interface Assistant {
        String chat(String userMessage);
    }
    public static void main(String[] args) throws Exception {
        
        McpTransport transport = new HttpMcpTransport.Builder()
                .sseUrl("http://localhost:8080/sse")
                .build();
        McpClient client = new DefaultMcpClient.Builder()
                .transport(transport)
                .build();
        ToolProvider provider = McpToolProvider.builder()
                .mcpClients(client)
                .build();
        
        String modelName = 
                "qwen/qwen3-1.7b";
        ChatModel model = OpenAiChatModel.builder()
                .baseUrl("http://localhost:1234/v1")
                .modelName(modelName)                
                .httpClientBuilder(JdkHttpClient.builder().httpClientBuilder(
                        HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1)))
                .build();
        Assistant assistant = AiServices.builder(Assistant.class)
                .chatModel(model)
                .toolProvider(provider)
                .build();
        String res = assistant.chat("""
                /no_think
                東京の天気は?いま何時?""");
        System.out.println(res);
        client.close();
        
    }
}

次のように、getWeathergetTimeが呼び出されて返答が作られています。

<think>

</think>

東京の天気は「雨」です。現在の時間は2025年7月8日 14時40分頃です。

それではコードを見てみます。基本的にはAiServiceを使うコードになりますが、toolProviderを指定するところが前回のサンプルと違うところです。

Assistant assistant = AiServices.builder(Assistant.class)
        .chatModel(model)
        .toolProvider(provider)
        .build();

ToolProviderを得るには、まずMCPに接続するトランスポートをMcpTransportとして指定します。SSE型のMCPサーバーに接続する場合にはHttpMcpTransportを使ってsseUrlを指定します。

McpTransport transport = new HttpMcpTransport.Builder()
        .sseUrl("http://localhost:8080/sse")
        .build();

stdio型のMCPサーバーに接続する場合にはStdioMcpTransportを使って、commandにコマンドを指定します。

McpTransport transport = new StdioMcpTransport.Builder()
        .command(List.of(
            "java",
            "-Dspring.ai.mcp.server.stdio=true",
            "-Dlogging.pattern.console=",
            "-Dspring.main.bannerMode=off",
            "-jar",
            "<projectdir>/target/demomcp-0.0.1-SNAPSHOT.jar"                
        ))
        .build();

あとはtransportとして先ほどのMcpTransportを指定したMcpClientを作成し、そのMcpClientを指定したToolProviderを作成してAiServicesのビルダーに渡します。

McpClient client = new DefaultMcpClient.Builder()
        .transport(transport)
        .build();
ToolProvider provider = McpToolProvider.builder()
        .mcpClients(client)
        .build();

このコードからも、MCPがFunctionCallingのリモート版ということがわかります。McpClientAutoClosableになっているので、try-with-resource構文を使うほうがいいのですが、今までのサンプルとの対比のためにcloseを明示的に呼び出す形にしています。

今回は、次のように天気と時刻を同時に尋ねているため、getWeathergetTimeが両方呼び出されて「東京の天気は「雨」です。現在の時間は2025年7月8日 14時40分頃です。」という返答になっています。

String res = assistant.chat("""
        /no_think
        東京の天気は?いま何時?""");

終わりに

今回はMCPを使ってLLMから呼び出される機能の実装、LLMからMCPを使った機能を呼び出す処理の実装を行いました。これで、LLMを使うJavaプログラムは一通り実装できるのではないかと思います。

ただ、実用的なAIアプリケーションを開発しようとすると、AIエージェントということになっていきます。その際にはLangGraph4jのようなライブラリも有用になるでしょう。

また、AIエージェントを作成するとなると、ライブラリの使い方だけではなく、AIエージェントをどう作るかということも大事になります。LangChainとLangGraphによるRAG・AIエージェント[実践]入門が参考になるでしょう。

この連載をきっかけにLLMを使ったJavaアプリケーションの開発に興味を持ってもらえたら幸いです。

おすすめ記事

記事・ニュース一覧