コンテンツにスキップ

出典: https://github.com/microsoft/mcp-for-beginners フォーク元リポジトリ: https://github.com/microsoft/mcp-for-beginners (Microsoft) ライセンス: MIT License


LLMを使用したクライアントの作成

これまで、サーバーとクライアントの作成方法を学びました。クライアントは、サーバーに明示的に呼び出してツール、リソース、プロンプトを一覧表示することができました。しかし、この方法はあまり実用的ではありません。ユーザーはエージェント時代に生きており、プロンプトを使用してLLMとやり取りすることを期待しています。ユーザーにとって、MCPを使用して機能を保存するかどうかは重要ではありませんが、自然言語でやり取りできることを期待しています。では、これをどのように解決するのでしょうか?その解決策は、クライアントにLLMを追加することです。

概要

このレッスンでは、クライアントにLLMを追加する方法に焦点を当て、それがユーザーにとってどれほど優れた体験を提供するかを示します。

学習目標

このレッスンの終わりまでに、以下ができるようになります:

  • LLMを使用したクライアントを作成する。
  • LLMを使用してMCPサーバーとシームレスにやり取りする。
  • クライアント側でより良いエンドユーザー体験を提供する。

アプローチ

どのようなアプローチを取るべきかを理解してみましょう。LLMを追加するのは簡単そうに聞こえますが、実際にはどうするのでしょうか?

クライアントがサーバーとやり取りする方法は以下の通りです:

  1. サーバーとの接続を確立する。

  2. 機能、プロンプト、リソース、ツールを一覧表示し、それらのスキーマを保存する。

  3. LLMを追加し、保存した機能とそのスキーマをLLMが理解できる形式で渡す。

  4. ユーザープロンプトを処理し、それをクライアントが一覧表示したツールとともにLLMに渡す。

素晴らしいですね。これで高レベルでの理解ができたので、以下の演習で試してみましょう。

演習:LLMを使用したクライアントの作成

この演習では、クライアントにLLMを追加する方法を学びます。

GitHub Personal Access Tokenを使用した認証

GitHubトークンの作成は簡単なプロセスです。以下の手順で行います:

  • GitHubの設定に移動 – 右上のプロフィール画像をクリックし、「Settings」を選択します。
  • 「Developer Settings」に移動 – 下にスクロールして「Developer Settings」をクリックします。
  • 「Personal Access Tokens」を選択 – 「Personal access tokens」をクリックし、「Generate new token」を選択します。
  • トークンを設定 – 参照用のメモを追加し、有効期限を設定し、必要なスコープ(権限)を選択します。
  • トークンを生成してコピー – 「Generate token」をクリックし、すぐにコピーしてください。一度しか表示されません。

-1- サーバーに接続する

まずクライアントを作成しましょう:

TypeScript

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
import OpenAI from "openai";
import { z } from "zod"; // Import zod for schema validation

class MCPClient {
    private openai: OpenAI;
    private client: Client;
    constructor(){
        this.openai = new OpenAI({
            baseURL: "https://models.inference.ai.azure.com", 
            apiKey: process.env.GITHUB_TOKEN,
        });

        this.client = new Client(
            {
                name: "example-client",
                version: "1.0.0"
            },
            {
                capabilities: {
                prompts: {},
                resources: {},
                tools: {}
                }
            }
            );    
    }
}

上記のコードでは以下を行いました:

  • 必要なライブラリをインポートしました。
  • clientopenaiの2つのメンバーを持つクラスを作成しました。これにより、クライアントを管理し、LLMとやり取りできます。
  • baseUrlを推論APIに設定することで、GitHub Modelsを使用するようにLLMインスタンスを構成しました。

Python

from mcp import ClientSession, StdioServerParameters, types
from mcp.client.stdio import stdio_client

# Create server parameters for stdio connection
server_params = StdioServerParameters(
    command="mcp",  # Executable
    args=["run", "server.py"],  # Optional command line arguments
    env=None,  # Optional environment variables
)


async def run():
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(
            read, write
        ) as session:
            # Initialize the connection
            await session.initialize()


if __name__ == "__main__":
    import asyncio

    asyncio.run(run())

上記のコードでは以下を行いました:

  • MCP用の必要なライブラリをインポートしました。
  • クライアントを作成しました。

.NET

using Azure;
using Azure.AI.Inference;
using Azure.Identity;
using System.Text.Json;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol.Transport;
using System.Text.Json;

var clientTransport = new StdioClientTransport(new()
{
    Name = "Demo Server",
    Command = "/workspaces/mcp-for-beginners/03-GettingStarted/02-client/solution/server/bin/Debug/net8.0/server",
    Arguments = [],
});

await using var mcpClient = await McpClientFactory.CreateAsync(clientTransport);

Java

まず、pom.xmlファイルにLangChain4jの依存関係を追加する必要があります。以下の依存関係を追加して、MCP統合とGitHub Modelsのサポートを有効にします:

<properties>
    <langchain4j.version>1.0.0-beta3</langchain4j.version>
</properties>

<dependencies>
    <!-- LangChain4j MCP Integration -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-mcp</artifactId>
        <version>${langchain4j.version}</version>
    </dependency>

    <!-- OpenAI Official API Client -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai-official</artifactId>
        <version>${langchain4j.version}</version>
    </dependency>

    <!-- GitHub Models Support -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-github-models</artifactId>
        <version>${langchain4j.version}</version>
    </dependency>

    <!-- Spring Boot Starter (optional, for production apps) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
</dependencies>

次に、Javaクライアントクラスを作成します:

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.ChatLanguageModel;
import dev.langchain4j.model.openaiofficial.OpenAiOfficialChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.tool.ToolProvider;

import java.time.Duration;
import java.util.List;

public class LangChain4jClient {

    public static void main(String[] args) throws Exception {        // Configure the LLM to use GitHub Models
        ChatLanguageModel model = OpenAiOfficialChatModel.builder()
                .isGitHubModels(true)
                .apiKey(System.getenv("GITHUB_TOKEN"))
                .timeout(Duration.ofSeconds(60))
                .modelName("gpt-4.1-nano")
                .build();

        // Create MCP transport for connecting to server
        McpTransport transport = new HttpMcpTransport.Builder()
                .sseUrl("http://localhost:8080/sse")
                .timeout(Duration.ofSeconds(60))
                .logRequests(true)
                .logResponses(true)
                .build();

        // Create MCP client
        McpClient mcpClient = new DefaultMcpClient.Builder()
                .transport(transport)
                .build();
    }
}

上記のコードでは以下を行いました:

  • LangChain4jの依存関係を追加:MCP統合、OpenAI公式クライアント、GitHub Modelsサポートに必要。
  • LangChain4jライブラリをインポート:MCP統合とOpenAIチャットモデル機能のため。
  • ChatLanguageModelを作成:GitHubトークンを使用してGitHub Modelsを構成。
  • HTTPトランスポートを設定:Server-Sent Events(SSE)を使用してMCPサーバーに接続。
  • MCPクライアントを作成:サーバーとの通信を処理。
  • LangChain4jの組み込みMCPサポートを使用:LLMとMCPサーバー間の統合を簡素化。

次のステップでは、サーバーの機能を一覧表示します。

-2- サーバーの機能を一覧表示する

次に、サーバーに接続してその機能を問い合わせます:

TypeScript

同じクラスに以下のメソッドを追加します:

async connectToServer(transport: Transport) {
     await this.client.connect(transport);
     this.run();
     console.error("MCPClient started on stdin/stdout");
}

async run() {
    console.log("Asking server for available tools");

    // listing tools
    const toolsResult = await this.client.listTools();
}

上記のコードでは以下を行いました:

  • サーバーに接続するコードconnectToServerを追加しました。
  • アプリのフローを処理するrunメソッドを作成しました。現時点ではツールを一覧表示するだけですが、後でさらに追加します。

Python

# List available resources
resources = await session.list_resources()
print("LISTING RESOURCES")
for resource in resources:
    print("Resource: ", resource)

# List available tools
tools = await session.list_tools()
print("LISTING TOOLS")
for tool in tools.tools:
    print("Tool: ", tool.name)
    print("Tool", tool.inputSchema["properties"])

追加した内容は以下の通りです:

  • リソースとツールを一覧表示し、それらを出力しました。ツールについては、後で使用するinputSchemaも一覧表示しました。

.NET

async Task<List<ChatCompletionsToolDefinition>> GetMcpTools()
{
    Console.WriteLine("Listing tools");
    var tools = await mcpClient.ListToolsAsync();

    List<ChatCompletionsToolDefinition> toolDefinitions = new List<ChatCompletionsToolDefinition>();

    foreach (var tool in tools)
    {
        Console.WriteLine($"Connected to server with tools: {tool.Name}");
        Console.WriteLine($"Tool description: {tool.Description}");
        Console.WriteLine($"Tool parameters: {tool.JsonSchema}");

        // TODO: convert tool definition from MCP tool to LLm tool     
    }

    return toolDefinitions;
}

上記のコードでは以下を行いました:

  • MCPサーバーで利用可能なツールを一覧表示しました。
  • 各ツールについて、名前、説明、およびスキーマを一覧表示しました。後者はツールを呼び出す際に使用します。

Java

// Create a tool provider that automatically discovers MCP tools
ToolProvider toolProvider = McpToolProvider.builder()
        .mcpClients(List.of(mcpClient))
        .build();

// The MCP tool provider automatically handles:
// - Listing available tools from the MCP server
// - Converting MCP tool schemas to LangChain4j format
// - Managing tool execution and responses

上記のコードでは以下を行いました:

  • MCPサーバーからすべてのツールを自動的に検出して登録するMcpToolProviderを作成しました。
  • ツールプロバイダーは、MCPツールスキーマとLangChain4jのツール形式の間の変換を内部的に処理します。
  • このアプローチにより、手動でのツール一覧表示と変換プロセスが抽象化されます。

-3- サーバーの機能をLLMツールに変換する

次のステップでは、サーバーの機能をLLMが理解できる形式に変換します。これにより、これらの機能をLLMのツールとして提供できます。

TypeScript

  1. MCPサーバーからのレスポンスをLLMが使用できるツール形式に変換する以下のコードを追加します:

    openAiToolAdapter(tool: {
        name: string;
        description?: string;
        input_schema: any;
        }) {
        // Create a zod schema based on the input_schema
        const schema = z.object(tool.input_schema);
    
        return {
            type: "function" as const, // Explicitly set type to "function"
            function: {
            name: tool.name,
            description: tool.description,
            parameters: {
            type: "object",
            properties: tool.input_schema.properties,
            required: tool.input_schema.required,
            },
            },
        };
    }
    

    上記のコードは、MCPサーバーからのレスポンスをLLMが理解できるツール定義形式に変換します。

  2. 次に、runメソッドを更新してサーバーの機能を一覧表示します:

    async run() {
        console.log("Asking server for available tools");
        const toolsResult = await this.client.listTools();
        const tools = toolsResult.tools.map((tool) => {
            return this.openAiToolAdapter({
            name: tool.name,
            description: tool.description,
            input_schema: tool.inputSchema,
            });
        });
    }
    

    上記のコードでは、runメソッドを更新して結果をマッピングし、各エントリに対してopenAiToolAdapterを呼び出しました。

Python

  1. まず、以下の変換関数を作成します:

    def convert_to_llm_tool(tool):
        tool_schema = {
            "type": "function",
            "function": {
                "name": tool.name,
                "description": tool.description,
                "type": "function",
                "parameters": {
                    "type": "object",
                    "properties": tool.inputSchema["properties"]
                }
            }
        }
    
        return tool_schema
    

    上記のconvert_to_llm_tools関数では、MCPツールレスポンスをLLMが理解できる形式に変換します。

  2. 次に、以下のようにクライアントコードを更新してこの関数を活用します:

    for tool in tools.tools:
        print("Tool: ", tool.name)
        print("Tool", tool.inputSchema["properties"])
        functions.append(convert_to_llm_tool(tool))
    

    ここでは、MCPツールレスポンスを変換するためにconvert_to_llm_toolを呼び出し、後でLLMに渡せる形式に変換しています。

.NET

  1. MCPツールレスポンスをLLMが理解できる形式に変換するコードを追加します:
ChatCompletionsToolDefinition ConvertFrom(string name, string description, JsonElement jsonElement)
{ 
    // convert the tool to a function definition
    FunctionDefinition functionDefinition = new FunctionDefinition(name)
    {
        Description = description,
        Parameters = BinaryData.FromObjectAsJson(new
        {
            Type = "object",
            Properties = jsonElement
        },
        new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase })
    };

    // create a tool definition
    ChatCompletionsToolDefinition toolDefinition = new ChatCompletionsToolDefinition(functionDefinition);
    return toolDefinition;
}

上記のコードでは以下を行いました:

  • ConvertFrom関数を作成し、名前、説明、および入力スキーマを受け取ります。
  • FunctionDefinitionを作成し、それをChatCompletionsDefinitionに渡します。後者はLLMが理解できる形式です。

  • 次に、既存のコードを更新してこの関数を活用します:

    async Task<List<ChatCompletionsToolDefinition>> GetMcpTools()
    {
        Console.WriteLine("Listing tools");
        var tools = await mcpClient.ListToolsAsync();
    
        List<ChatCompletionsToolDefinition> toolDefinitions = new List<ChatCompletionsToolDefinition>();
    
        foreach (var tool in tools)
        {
            Console.WriteLine($"Connected to server with tools: {tool.Name}");
            Console.WriteLine($"Tool description: {tool.Description}");
            Console.WriteLine($"Tool parameters: {tool.JsonSchema}");
    
            JsonElement propertiesElement;
            tool.JsonSchema.TryGetProperty("properties", out propertiesElement);
    
            var def = ConvertFrom(tool.Name, tool.Description, propertiesElement);
            Console.WriteLine($"Tool definition: {def}");
            toolDefinitions.Add(def);
    
            Console.WriteLine($"Properties: {propertiesElement}");        
        }
    
        return toolDefinitions;
    }
    

    上記のコードでは、MCPツールレスポンスをLLMツールに変換する機能を追加しました。以下のコードを強調します:

    ```csharp
    JsonElement propertiesElement;
    tool.JsonSchema.TryGetProperty("properties", out propertiesElement);
    
    var def = ConvertFrom(tool.Name, tool.Description, propertiesElement);
    Console.WriteLine($"Tool definition: {def}");
    toolDefinitions.Add(def);
    ```
    
    入力スキーマはツールレスポンスの一部ですが、「properties」属性にあります。そのため、これを抽出する必要があります。その後、ツールの詳細を使用して`ConvertFrom`を呼び出します。これで主要な作業が完了したので、次にユーザープロンプトを処理する方法を見てみましょう。
    

Java

// Create a Bot interface for natural language interaction
public interface Bot {
    String chat(String prompt);
}

// Configure the AI service with LLM and MCP tools
Bot bot = AiServices.builder(Bot.class)
        .chatLanguageModel(model)
        .toolProvider(toolProvider)
        .build();

上記のコードでは以下を行いました:

  • 自然言語でのやり取りのためのシンプルなBotインターフェースを定義しました。
  • LangChain4jのAiServicesを使用して、LLMとMCPツールプロバイダーを自動的にバインドしました。
  • フレームワークは、ツールスキーマの変換と関数呼び出しを内部的に処理します。
  • このアプローチにより、手動でのツール変換が不要になり、LangChain4jがMCPツールをLLM互換形式に変換する複雑さを処理します。

素晴らしいですね。これでユーザーリクエストを処理する準備が整いましたので、次に進みましょう。

-4- ユーザープロンプトリクエストを処理する

このコード部分では、ユーザーリクエストを処理します。

TypeScript

  1. LLMを呼び出すためのメソッドを追加します:

    async callTools(
        tool_calls: OpenAI.Chat.Completions.ChatCompletionMessageToolCall[],
        toolResults: any[]
    ) {
        for (const tool_call of tool_calls) {
        const toolName = tool_call.function.name;
        const args = tool_call.function.arguments;
    
        console.log(`Calling tool ${toolName} with args ${JSON.stringify(args)}`);
    
    
        // 2. Call the server's tool 
        const toolResult = await this.client.callTool({
            name: toolName,
            arguments: JSON.parse(args),
        });
    
        console.log("Tool result: ", toolResult);
    
        // 3. Do something with the result
        // TODO  
    
        }
    }
    

    上記のコードでは以下を行いました:

    • callToolsメソッドを追加しました。
    • このメソッドはLLMレスポンスを受け取り、呼び出すべきツールがあるかどうかを確認します:

      for (const tool_call of tool_calls) {
      const toolName = tool_call.function.name;
      const args = tool_call.function.arguments;
      
      console.log(`Calling tool ${toolName} with args ${JSON.stringify(args)}`);
      
      // call tool
      }
      
    • LLMが呼び出すべきと判断したツールを呼び出します:

      // 2. Call the server's tool 
      const toolResult = await this.client.callTool({
          name: toolName,
          arguments: JSON.parse(args),
      });
      
      console.log("Tool result: ", toolResult);
      
      // 3. Do something with the result
      // TODO  
      
  2. runメソッドを更新してLLMの呼び出しとcallToolsの呼び出しを含めます:

    // 1. Create messages that's input for the LLM
    const prompt = "What is the sum of 2 and 3?"
    
    const messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [
            {
                role: "user",
                content: prompt,
            },
        ];
    
    console.log("Querying LLM: ", messages[0].content);
    
    // 2. Calling the LLM
    let response = this.openai.chat.completions.create({
        model: "gpt-4o-mini",
        max_tokens: 1000,
        messages,
        tools: tools,
    });    
    
    let results: any[] = [];
    
    // 3. Go through the LLM response,for each choice, check if it has tool calls 
    (await response).choices.map(async (choice: { message: any; }) => {
        const message = choice.message;
        if (message.tool_calls) {
            console.log("Making tool call")
            await this.callTools(message.tool_calls, results);
        }
    });
    

素晴らしいですね。コード全体を以下に示します:

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
import OpenAI from "openai";
import { z } from "zod"; // Import zod for schema validation

class MyClient {
    private openai: OpenAI;
    private client: Client;
    constructor(){
        this.openai = new OpenAI({
            baseURL: "https://models.inference.ai.azure.com", // might need to change to this url in the future: https://models.github.ai/inference
            apiKey: process.env.GITHUB_TOKEN,
        });

        this.client = new Client(
            {
                name: "example-client",
                version: "1.0.0"
            },
            {
                capabilities: {
                prompts: {},
                resources: {},
                tools: {}
                }
            }
            );    
    }

    async connectToServer(transport: Transport) {
        await this.client.connect(transport);
        this.run();
        console.error("MCPClient started on stdin/stdout");
    }

    openAiToolAdapter(tool: {
        name: string;
        description?: string;
        input_schema: any;
          }) {
          // Create a zod schema based on the input_schema
          const schema = z.object(tool.input_schema);

          return {
            type: "function" as const, // Explicitly set type to "function"
            function: {
              name: tool.name,
              description: tool.description,
              parameters: {
              type: "object",
              properties: tool.input_schema.properties,
              required: tool.input_schema.required,
              },
            },
          };
    }

    async callTools(
        tool_calls: OpenAI.Chat.Completions.ChatCompletionMessageToolCall[],
        toolResults: any[]
      ) {
        for (const tool_call of tool_calls) {
          const toolName = tool_call.function.name;
          const args = tool_call.function.arguments;

          console.log(`Calling tool ${toolName} with args ${JSON.stringify(args)}`);


          // 2. Call the server's tool 
          const toolResult = await this.client.callTool({
            name: toolName,
            arguments: JSON.parse(args),
          });

          console.log("Tool result: ", toolResult);

          // 3. Do something with the result
          // TODO  

         }
    }

    async run() {
        console.log("Asking server for available tools");
        const toolsResult = await this.client.listTools();
        const tools = toolsResult.tools.map((tool) => {
            return this.openAiToolAdapter({
              name: tool.name,
              description: tool.description,
              input_schema: tool.inputSchema,
            });
        });

        const prompt = "What is the sum of 2 and 3?";

        const messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [
            {
                role: "user",
                content: prompt,
            },
        ];

        console.log("Querying LLM: ", messages[0].content);
        let response = this.openai.chat.completions.create({
            model: "gpt-4o-mini",
            max_tokens: 1000,
            messages,
            tools: tools,
        });    

        let results: any[] = [];

        // 1. Go through the LLM response,for each choice, check if it has tool calls 
        (await response).choices.map(async (choice: { message: any; }) => {
          const message = choice.message;
          if (message.tool_calls) {
              console.log("Making tool call")
              await this.callTools(message.tool_calls, results);
          }
        });
    }

}

let client = new MyClient();
 const transport = new StdioClientTransport({
            command: "node",
            args: ["./build/index.js"]
        });

client.connectToServer(transport);

Python

  1. LLMを呼び出すために必要なインポートを追加します:

    # llm
    import os
    from azure.ai.inference import ChatCompletionsClient
    from azure.ai.inference.models import SystemMessage, UserMessage
    from azure.core.credentials import AzureKeyCredential
    import json
    
  2. 次に、LLMを呼び出す関数を追加します:

    # llm
    
    def call_llm(prompt, functions):
        token = os.environ["GITHUB_TOKEN"]
        endpoint = "https://models.inference.ai.azure.com"
    
        model_name = "gpt-4o"
    
        client = ChatCompletionsClient(
            endpoint=endpoint,
            credential=AzureKeyCredential(token),
        )
    
        print("CALLING LLM")
        response = client.complete(
            messages=[
                {
                "role": "system",
                "content": "You are a helpful assistant.",
                },
                {
                "role": "user",
                "content": prompt,
                },
            ],
            model=model_name,
            tools = functions,
            # Optional parameters
            temperature=1.,
            max_tokens=1000,
            top_p=1.    
        )
    
        response_message = response.choices[0].message
    
        functions_to_call = []
    
        if response_message.tool_calls:
            for tool_call in response_message.tool_calls:
                print("TOOL: ", tool_call)
                name = tool_call.function.name
                args = json.loads(tool_call.function.arguments)
                functions_to_call.append({ "name": name, "args": args })
    
        return functions_to_call
    

    上記のコードでは以下を行いました:

    • MCPサーバーで見つけた関数をLLMに渡しました。
    • その後、LLMをこれらの関数で呼び出しました。
    • 結果を調べて、呼び出すべき関数があるかどうかを確認しました。
    • 最後に、呼び出すべき関数の配列を渡しました。
  3. 最後のステップとして、メインコードを更新します:

    prompt = "Add 2 to 20"
    
    # ask LLM what tools to all, if any
    functions_to_call = call_llm(prompt, functions)
    
    # call suggested functions
    for f in functions_to_call:
        result = await session.call_tool(f["name"], arguments=f["args"])
        print("TOOLS result: ", result.content)
    

    上記のコードでは以下を行いました:

    • LLMが呼び出すべきと判断した関数を使用して、call_toolを介してMCPツールを呼び出しました。
    • MCPサーバーへのツール呼び出しの結果を出力しました。

.NET

  1. LLMプロンプトリクエストを行うコードを示します:

    var tools = await GetMcpTools();
    
    for (int i = 0; i < tools.Count; i++)
    {
        var tool = tools[i];
        Console.WriteLine($"MCP Tools def: {i}: {tool}");
    }
    
    // 0. Define the chat history and the user message
    var userMessage = "add 2 and 4";
    
    chatHistory.Add(new ChatRequestUserMessage(userMessage));
    
    // 1. Define tools
    ChatCompletionsToolDefinition def = CreateToolDefinition();
    
    
    // 2. Define options, including the tools
    var options = new ChatCompletionsOptions(chatHistory)
    {
        Model = "gpt-4o-mini",
        Tools = { tools[0] }
    };
    
    // 3. Call the model  
    
    ChatCompletions? response = await client.CompleteAsync(options);
    var content = response.Content;
    

    上記のコードでは以下を行いました:

    • MCPサーバーからツールを取得しました(var tools = await GetMcpTools())。
    • ユーザープロンプトuserMessageを定義しました。
    • モデルとツールを指定するオプションオブジェクトを構築しました。
    • LLMにリクエストを行いました。
  2. 最後のステップとして、LLMが関数を呼び出すべきかどうかを確認します:

    // 4. Check if the response contains a function call
    ChatCompletionsToolCall? calls = response.ToolCalls.FirstOrDefault();
    for (int i = 0; i < response.ToolCalls.Count; i++)
    {
        var call = response.ToolCalls[i];
        Console.WriteLine($"Tool call {i}: {call.Name} with arguments {call.Arguments}");
        //Tool call 0: add with arguments {"a":2,"b":4}
    
        var dict = JsonSerializer.Deserialize<Dictionary<string, object>>(call.Arguments);
        var result = await mcpClient.CallToolAsync(
            call.Name,
            dict!,
            cancellationToken: CancellationToken.None
        );
    
        Console.WriteLine(result.Content.First(c => c.Type == "text").Text);
    
    }
    

    上記のコードでは以下を行いました:

    • 関数呼び出しのリストをループしました。
    • 各ツール呼び出しについて、名前と引数を解析し、MCPクライアントを使用してMCPサーバー上のツールを呼び出しました。最後に結果を出力しました。

コード全体は以下の通りです:

using Azure;
using Azure.AI.Inference;
using Azure.Identity;
using System.Text.Json;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol.Transport;
using System.Text.Json;

var endpoint = "https://models.inference.ai.azure.com";
var token = Environment.GetEnvironmentVariable("GITHUB_TOKEN"); // Your GitHub Access Token
var client = new ChatCompletionsClient(new Uri(endpoint), new AzureKeyCredential(token));
var chatHistory = new List<ChatRequestMessage>
{
    new ChatRequestSystemMessage("You are a helpful assistant that knows about AI")
};

var clientTransport = new StdioClientTransport(new()
{
    Name = "Demo Server",
    Command = "/workspaces/mcp-for-beginners/03-GettingStarted/02-client/solution/server/bin/Debug/net8.0/server",
    Arguments = [],
});

Console.WriteLine("Setting up stdio transport");

await using var mcpClient = await McpClientFactory.CreateAsync(clientTransport);

ChatCompletionsToolDefinition ConvertFrom(string name, string description, JsonElement jsonElement)
{ 
    // convert the tool to a function definition
    FunctionDefinition functionDefinition = new FunctionDefinition(name)
    {
        Description = description,
        Parameters = BinaryData.FromObjectAsJson(new
        {
            Type = "object",
            Properties = jsonElement
        },
        new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase })
    };

    // create a tool definition
    ChatCompletionsToolDefinition toolDefinition = new ChatCompletionsToolDefinition(functionDefinition);
    return toolDefinition;
}



async Task<List<ChatCompletionsToolDefinition>> GetMcpTools()
{
    Console.WriteLine("Listing tools");
    var tools = await mcpClient.ListToolsAsync();

    List<ChatCompletionsToolDefinition> toolDefinitions = new List<ChatCompletionsToolDefinition>();

    foreach (var tool in tools)
    {
        Console.WriteLine($"Connected to server with tools: {tool.Name}");
        Console.WriteLine($"Tool description: {tool.Description}");
        Console.WriteLine($"Tool parameters: {tool.JsonSchema}");

        JsonElement propertiesElement;
        tool.JsonSchema.TryGetProperty("properties", out propertiesElement);

        var def = ConvertFrom(tool.Name, tool.Description, propertiesElement);
        Console.WriteLine($"Tool definition: {def}");
        toolDefinitions.Add(def);

        Console.WriteLine($"Properties: {propertiesElement}");        
    }

    return toolDefinitions;
}

// 1. List tools on mcp server

var tools = await GetMcpTools();
for (int i = 0; i < tools.Count; i++)
{
    var tool = tools[i];
    Console.WriteLine($"MCP Tools def: {i}: {tool}");
}

// 2. Define the chat history and the user message
var userMessage = "add 2 and 4";

chatHistory.Add(new ChatRequestUserMessage(userMessage));


// 3. Define options, including the tools
var options = new ChatCompletionsOptions(chatHistory)
{
    Model = "gpt-4o-mini",
    Tools = { tools[0] }
};

// 4. Call the model  

ChatCompletions? response = await client.CompleteAsync(options);
var content = response.Content;

// 5. Check if the response contains a function call
ChatCompletionsToolCall? calls = response.ToolCalls.FirstOrDefault();
for (int i = 0; i < response.ToolCalls.Count; i++)
{
    var call = response.ToolCalls[i];
    Console.WriteLine($"Tool call {i}: {call.Name} with arguments {call.Arguments}");
    //Tool call 0: add with arguments {"a":2,"b":4}

    var dict = JsonSerializer.Deserialize<Dictionary<string, object>>(call.Arguments);
    var result = await mcpClient.CallToolAsync(
        call.Name,
        dict!,
        cancellationToken: CancellationToken.None
    );

    Console.WriteLine(result.Content.First(c => c.Type == "text").Text);

}

// 5. Print the generic response
Console.WriteLine($"Assistant response: {content}");

Java

try {
    // Execute natural language requests that automatically use MCP tools
    String response = bot.chat("Calculate the sum of 24.5 and 17.3 using the calculator service");
    System.out.println(response);

    response = bot.chat("What's the square root of 144?");
    System.out.println(response);

    response = bot.chat("Show me the help for the calculator service");
    System.out.println(response);
} finally {
    mcpClient.close();
}

上記のコードでは以下を行いました:

  • シンプルな自然言語プロンプトを使用してMCPサーバーツールとやり取りしました。
  • LangChain4jフレームワークは以下を自動的に処理します:
  • 必要に応じてユーザープロンプトをツール呼び出しに変換。
  • LLMの判断に基づいて適切なMCPツールを呼び出し。
  • LLMとMCPサーバー間の会話フローを管理。
  • bot.chat()メソッドは、MCPツール実行の結果を含む自然言語レスポンスを返します。
  • このアプローチにより、ユーザーは基盤となるMCP実装を意識する必要がなく、シームレスなユーザー体験を提供します。

完全なコード例:

public class LangChain4jClient {

    public static void main(String[] args) throws Exception {        ChatLanguageModel model = OpenAiOfficialChatModel.builder()
                .isGitHubModels(true)
                .apiKey(System.getenv("GITHUB_TOKEN"))
                .timeout(Duration.ofSeconds(60))
                .modelName("gpt-4.1-nano")
                .timeout(Duration.ofSeconds(60))
                .build();

        McpTransport transport = new HttpMcpTransport.Builder()
                .sseUrl("http://localhost:8080/sse")
                .timeout(Duration.ofSeconds(60))
                .logRequests(true)
                .logResponses(true)
                .build();

        McpClient mcpClient = new DefaultMcpClient.Builder()
                .transport(transport)
                .build();

        ToolProvider toolProvider = McpToolProvider.builder()
                .mcpClients(List.of(mcpClient))
                .build();

        Bot bot = AiServices.builder(Bot.class)
                .chatLanguageModel(model)
                .toolProvider(toolProvider)
                .build();

        try {
            String response = bot.chat("Calculate the sum of 24.5 and 17.3 using the calculator service");
            System.out.println(response);

            response = bot.chat("What's the square root of 144?");
            System.out.println(response);

            response = bot.chat("Show me the help for the calculator service");
            System.out.println(response);
        } finally {
            mcpClient.close();
        }
    }
}

素晴らしいですね、これで完了です!

課題

演習のコードを使用して、さらに多くのツールを備えたサーバーを構築してください。その後、演習のようにLLMを使用したクライアントを作成し、さまざまなプロンプトでテストして、サーバーのすべてのツールが動的に呼び出されることを確認してください。このようにクライアントを構築することで、エンドユーザーはプロンプトを使用できるため、正確なクライアントコマンドを使用する必要がなく、MCPサーバーが呼び出されていることを意識する必要がありません。

解答

解答

重要なポイント

  • クライアントにLLMを追加することで、MCPサーバーとのやり取りがより良い方法で提供されます。
  • MCPサーバーのレスポンスをLLMが理解できる形式に変換する必要があります。

サンプル

追加リソース

次のステップ

免責事項:
この文書は、AI翻訳サービス Co-op Translator を使用して翻訳されています。正確性を追求しておりますが、自動翻訳には誤りや不正確な部分が含まれる可能性があることをご承知ください。元の言語で記載された文書が正式な情報源とみなされるべきです。重要な情報については、専門の人間による翻訳を推奨します。この翻訳の使用に起因する誤解や誤解釈について、当社は責任を負いません。