Setakat ini, anda telah melihat cara untuk mencipta pelayan dan klien. Klien telah dapat memanggil pelayan secara eksplisit untuk menyenaraikan alat, sumber, dan promptnya. Walau bagaimanapun, pendekatan ini tidak begitu praktikal. Pengguna anda hidup dalam era agen dan mengharapkan untuk menggunakan prompt serta berkomunikasi dengan LLM untuk melakukannya. Bagi pengguna anda, mereka tidak peduli sama ada anda menggunakan MCP atau tidak untuk menyimpan keupayaan anda, tetapi mereka mengharapkan untuk berinteraksi menggunakan bahasa semula jadi. Jadi, bagaimana kita menyelesaikan ini? Penyelesaiannya adalah dengan menambah LLM pada klien.
Dalam pelajaran ini, kita akan memberi tumpuan kepada menambah LLM pada klien anda dan menunjukkan bagaimana ini memberikan pengalaman yang jauh lebih baik untuk pengguna anda.
Pada akhir pelajaran ini, anda akan dapat:
- Mencipta klien dengan LLM.
- Berinteraksi dengan pelayan MCP secara lancar menggunakan LLM.
- Memberikan pengalaman pengguna akhir yang lebih baik di sisi klien.
Mari kita cuba memahami pendekatan yang perlu diambil. Menambah LLM kedengaran mudah, tetapi bagaimana sebenarnya kita melakukannya?
Berikut adalah cara klien akan berinteraksi dengan pelayan:
-
Menjalin sambungan dengan pelayan.
-
Menyenaraikan keupayaan, prompt, sumber, dan alat, serta menyimpan skema mereka.
-
Menambah LLM dan menghantar keupayaan yang disimpan serta skema mereka dalam format yang difahami oleh LLM.
-
Mengendalikan prompt pengguna dengan menghantarnya kepada LLM bersama dengan alat yang disenaraikan oleh klien.
Bagus, sekarang kita faham bagaimana kita boleh melakukannya pada tahap tinggi, mari kita cuba dalam latihan di bawah.
Dalam latihan ini, kita akan belajar menambah LLM pada klien kita.
Mencipta token GitHub adalah proses yang mudah. Berikut adalah caranya:
- Pergi ke Tetapan GitHub – Klik pada gambar profil anda di sudut kanan atas dan pilih Tetapan.
- Navigasi ke Tetapan Pembangun – Tatal ke bawah dan klik pada Tetapan Pembangun.
- Pilih Token Akses Peribadi – Klik pada Token akses peribadi dan kemudian Jana token baharu.
- Konfigurasikan Token Anda – Tambahkan nota untuk rujukan, tetapkan tarikh luput, dan pilih skop (kebenaran) yang diperlukan.
- Jana dan Salin Token – Klik Jana token, dan pastikan untuk menyalinnya segera, kerana anda tidak akan dapat melihatnya lagi.
Mari kita cipta klien kita dahulu:
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: {}
}
}
);
}
}Dalam kod di atas, kita telah:
- Mengimport perpustakaan yang diperlukan.
- Mencipta kelas dengan dua ahli,
clientdanopenai, yang akan membantu kita menguruskan klien dan berinteraksi dengan LLM masing-masing. - Mengkonfigurasi instans LLM kita untuk menggunakan Model GitHub dengan menetapkan
baseUrluntuk menunjuk kepada API inferens.
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())Dalam kod di atas, kita telah:
- Mengimport perpustakaan yang diperlukan untuk MCP.
- Mencipta klien.
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);Pertama, anda perlu menambah kebergantungan LangChain4j ke dalam fail pom.xml anda. Tambahkan kebergantungan ini untuk membolehkan integrasi MCP dan sokongan Model GitHub:
<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>Kemudian cipta kelas klien Java anda:
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();
}
}Dalam kod di atas, kita telah:
- Menambah kebergantungan LangChain4j: Diperlukan untuk integrasi MCP, klien rasmi OpenAI, dan sokongan Model GitHub.
- Mengimport perpustakaan LangChain4j: Untuk integrasi MCP dan fungsi model sembang OpenAI.
- Mencipta
ChatLanguageModel: Dikonfigurasi untuk menggunakan Model GitHub dengan token GitHub anda. - Menetapkan pengangkutan HTTP: Menggunakan Server-Sent Events (SSE) untuk menyambung ke pelayan MCP.
- Mencipta klien MCP: Yang akan mengendalikan komunikasi dengan pelayan.
- Menggunakan sokongan MCP bawaan LangChain4j: Yang mempermudah integrasi antara LLM dan pelayan MCP.
Contoh ini mengandaikan anda mempunyai pelayan MCP berasaskan Rust yang sedang berjalan. Jika anda tidak memilikinya, rujuk kembali pelajaran 01-first-server untuk mencipta pelayan.
Setelah anda mempunyai pelayan MCP Rust, buka terminal dan navigasi ke direktori yang sama dengan pelayan. Kemudian jalankan perintah berikut untuk mencipta projek klien LLM baharu:
mkdir calculator-llmclient
cd calculator-llmclient
cargo initTambahkan kebergantungan berikut ke dalam fail Cargo.toml anda:
[dependencies]
async-openai = { version = "0.29.0", features = ["byot"] }
rmcp = { version = "0.5.0", features = ["client", "transport-child-process"] }
serde_json = "1.0.141"
tokio = { version = "1.46.1", features = ["rt-multi-thread"] }Note
Tiada perpustakaan rasmi Rust untuk OpenAI, namun, crate async-openai adalah perpustakaan yang diselenggara oleh komuniti yang sering digunakan.
Buka fail src/main.rs dan gantikan kandungannya dengan kod berikut:
use async_openai::{Client, config::OpenAIConfig};
use rmcp::{
RmcpError,
model::{CallToolRequestParam, ListToolsResult},
service::{RoleClient, RunningService, ServiceExt},
transport::{ConfigureCommandExt, TokioChildProcess},
};
use serde_json::{Value, json};
use std::error::Error;
use tokio::process::Command;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Initial message
let mut messages = vec![json!({"role": "user", "content": "What is the sum of 3 and 2?"})];
// Setup OpenAI client
let api_key = std::env::var("OPENAI_API_KEY")?;
let openai_client = Client::with_config(
OpenAIConfig::new()
.with_api_base("https://models.github.ai/inference/chat")
.with_api_key(api_key),
);
// Setup MCP client
let server_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.join("calculator-server");
let mcp_client = ()
.serve(
TokioChildProcess::new(Command::new("cargo").configure(|cmd| {
cmd.arg("run").current_dir(server_dir);
}))
.map_err(RmcpError::transport_creation::<TokioChildProcess>)?,
)
.await?;
// TODO: Get MCP tool listing
// TODO: LLM conversation with tool calls
Ok(())
}Kod ini menyediakan aplikasi Rust asas yang akan menyambung ke pelayan MCP dan Model GitHub untuk interaksi LLM.
Important
Pastikan untuk menetapkan pembolehubah persekitaran OPENAI_API_KEY dengan token GitHub anda sebelum menjalankan aplikasi.
Bagus, untuk langkah seterusnya, mari kita senaraikan keupayaan pada pelayan.
Sekarang kita akan menyambung ke pelayan dan meminta keupayaannya:
Dalam kelas yang sama, tambahkan kaedah berikut:
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();
}Dalam kod di atas, kita telah:
- Menambah kod untuk menyambung ke pelayan,
connectToServer. - Mencipta kaedah
runyang bertanggungjawab untuk mengendalikan aliran aplikasi kita. Setakat ini ia hanya menyenaraikan alat, tetapi kita akan menambah lebih banyak tidak lama lagi.
# 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"])Berikut adalah apa yang kita tambahkan:
- Menyenaraikan sumber dan alat serta mencetaknya. Untuk alat, kita juga menyenaraikan
inputSchemayang akan kita gunakan kemudian.
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;
}Dalam kod di atas, kita telah:
- Menyenaraikan alat yang tersedia pada Pelayan MCP.
- Untuk setiap alat, menyenaraikan nama, deskripsi, dan skemanya. Yang terakhir adalah sesuatu yang akan kita gunakan untuk memanggil alat tidak lama lagi.
// 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 responsesDalam kod di atas, kita telah:
- Mencipta
McpToolProvideryang secara automatik menemui dan mendaftarkan semua alat dari pelayan MCP. - Penyedia alat mengendalikan penukaran antara skema alat MCP dan format alat LangChain4j secara dalaman.
- Pendekatan ini mengabstrakkan proses penyenaraian dan penukaran alat secara manual.
Mendapatkan alat dari pelayan MCP dilakukan menggunakan kaedah list_tools. Dalam fungsi main anda, selepas menyediakan klien MCP, tambahkan kod berikut:
// Get MCP tool listing
let tools = mcp_client.list_tools(Default::default()).await?;Langkah seterusnya selepas menyenaraikan keupayaan pelayan adalah menukarnya ke dalam format yang difahami oleh LLM. Setelah kita melakukannya, kita boleh menyediakan keupayaan ini sebagai alat kepada LLM.
-
Tambahkan kod berikut untuk menukar respons dari Pelayan MCP kepada format alat yang boleh digunakan oleh 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, }, }, }; }
Kod di atas mengambil respons dari Pelayan MCP dan menukarnya kepada format definisi alat yang difahami oleh LLM.
-
Mari kita kemas kini kaedah
rununtuk menyenaraikan keupayaan pelayan: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, }); }); }
Dalam kod di atas, kita telah mengemas kini kaedah
rununtuk memetakan hasil dan untuk setiap entri memanggilopenAiToolAdapter.
-
Pertama, mari kita cipta fungsi penukar berikut:
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
Dalam fungsi di atas
convert_to_llm_tools, kita mengambil respons alat MCP dan menukarnya kepada format yang difahami oleh LLM. -
Seterusnya, mari kita kemas kini kod klien kita untuk memanfaatkan fungsi ini seperti berikut:
for tool in tools.tools: print("Tool: ", tool.name) print("Tool", tool.inputSchema["properties"]) functions.append(convert_to_llm_tool(tool))
Di sini, kita menambah panggilan kepada
convert_to_llm_tooluntuk menukar respons alat MCP kepada sesuatu yang boleh kita suapkan kepada LLM nanti.
- Tambahkan kod untuk menukar respons alat MCP kepada sesuatu yang difahami oleh 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;
}Dalam kod di atas, kita telah:
- Mencipta fungsi
ConvertFromyang mengambil nama, deskripsi, dan skema input. - Menentukan fungsi yang mencipta
FunctionDefinitionyang kemudian dihantar kepadaChatCompletionsDefinition. Yang terakhir adalah sesuatu yang difahami oleh LLM.
-
Mari kita lihat bagaimana kita boleh mengemas kini beberapa kod sedia ada untuk memanfaatkan fungsi ini:
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; }
Dalam kod di atas, kita telah:
-
Mengemas kini fungsi untuk menukar respons alat MCP kepada alat LLM. Mari kita soroti kod yang kita tambahkan:
JsonElement propertiesElement; tool.JsonSchema.TryGetProperty("properties", out propertiesElement); var def = ConvertFrom(tool.Name, tool.Description, propertiesElement); Console.WriteLine($"Tool definition: {def}"); toolDefinitions.Add(def);
Skema input adalah sebahagian daripada respons alat tetapi pada atribut "properties", jadi kita perlu mengekstraknya. Tambahan pula, kita kini memanggil
ConvertFromdengan butiran alat. Sekarang kita telah melakukan kerja berat, mari kita lihat bagaimana semuanya bersatu ketika kita mengendalikan permintaan prompt pengguna seterusnya.
-
// 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();Dalam kod di atas, kita telah:
- Mendefinisikan antara muka
Botyang ringkas untuk interaksi bahasa semula jadi. - Menggunakan
AiServicesLangChain4j untuk secara automatik mengikat LLM dengan penyedia alat MCP. - Rangka kerja secara automatik mengendalikan penukaran skema alat dan pemanggilan fungsi di belakang tabir.
- Pendekatan ini menghapuskan penukaran alat secara manual - LangChain4j mengendalikan semua kerumitan menukar alat MCP kepada format yang serasi dengan LLM.
Untuk menukar respons alat MCP kepada format yang difahami oleh LLM, kita akan menambah fungsi pembantu yang memformatkan senarai alat. Tambahkan kod berikut ke dalam fail main.rs anda di bawah fungsi main. Ini akan dipanggil semasa membuat permintaan kepada LLM:
async fn format_tools(tools: &ListToolsResult) -> Result<Vec<Value>, Box<dyn Error>> {
let tools_json = serde_json::to_value(tools)?;
let Some(tools_array) = tools_json.get("tools").and_then(|t| t.as_array()) else {
return Ok(vec![]);
};
let formatted_tools = tools_array
.iter()
.filter_map(|tool| {
let name = tool.get("name")?.as_str()?;
let description = tool.get("description")?.as_str()?;
let schema = tool.get("inputSchema")?;
Some(json!({
"type": "function",
"function": {
"name": name,
"description": description,
"parameters": {
"type": "object",
"properties": schema.get("properties").unwrap_or(&json!({})),
"required": schema.get("required").unwrap_or(&json!([]))
}
}
}))
})
.collect();
Ok(formatted_tools)
}Bagus, kita sudah bersedia untuk mengendalikan sebarang permintaan pengguna, jadi mari kita selesaikan itu seterusnya.
Dalam bahagian kod ini, kita akan mengendalikan permintaan pengguna.
-
Tambahkan kaedah yang akan digunakan untuk memanggil 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 } }
Dalam kod di atas, kita telah:
-
Menambah kaedah
callTools. -
Kaedah ini mengambil respons LLM dan memeriksa untuk melihat alat apa yang telah dipanggil, jika ada:
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 }
-
Memanggil alat, jika LLM menunjukkan ia perlu dipanggil:
// 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
-
-
Kemas kini kaedah
rununtuk memasukkan panggilan kepada LLM dan memanggilcallTools:// 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); } });
Bagus, mari kita senaraikan kod sepenuhnya:
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);-
Mari kita tambahkan beberapa import yang diperlukan untuk memanggil 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
-
Seterusnya, tambahkan fungsi yang akan memanggil 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
Dalam kod di atas, kita telah:
- Menghantar fungsi kita, yang kita temui pada pelayan MCP dan ditukar, kepada LLM.
- Kemudian kita memanggil LLM dengan fungsi tersebut.
- Kemudian, kita memeriksa hasil untuk melihat fungsi apa yang perlu dipanggil, jika ada.
- Akhirnya, kita menghantar senarai fungsi untuk dipanggil.
-
Langkah terakhir, mari kita kemas kini kod utama kita:
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)
Di sana, itu adalah langkah terakhir, dalam kod di atas kita:
- Memanggil alat MCP melalui
call_toolmenggunakan fungsi yang LLM fikir kita perlu panggil berdasarkan prompt kita. - Mencetak hasil panggilan alat kepada Pelayan MCP.
- Memanggil alat MCP melalui
-
Mari kita tunjukkan kod untuk membuat permintaan prompt 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;
Dalam kod di atas, kita telah:
- Mendapatkan alat dari pelayan MCP,
var tools = await GetMcpTools(). - Mendefinisikan prompt pengguna
userMessage. - Membina objek pilihan yang menentukan model dan alat.
- Membuat permintaan kepada LLM.
- Mendapatkan alat dari pelayan MCP,
-
Satu langkah terakhir, mari kita lihat jika LLM berpendapat kita perlu memanggil fungsi:
// 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); }
Dalam kod di atas, kita telah:
- Mengulang senarai panggilan fungsi.
- Untuk setiap panggilan alat, menguraikan nama dan argumen serta memanggil alat pada pelayan MCP menggunakan klien MCP. Akhirnya, kita mencetak hasilnya.
Berikut adalah kod sepenuhnya:
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}");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();
}Dalam kod di atas, kita telah:
- Menggunakan prompt bahasa semula jadi yang mudah untuk berinteraksi dengan alat pelayan MCP.
- Rangka kerja LangChain4j secara automatik mengendalikan:
- Menukar prompt pengguna kepada panggilan alat apabila diperlukan.
- Memanggil alat MCP yang sesuai berdasarkan keputusan LLM.
- Menguruskan aliran perbualan antara LLM dan pelayan MCP.
- Kaedah
bot.chat()mengembalikan respons bahasa semula jadi yang mungkin termasuk hasil dari pelaksanaan alat MCP. - Pendekatan ini menyediakan pengalaman pengguna yang lancar di mana pengguna tidak perlu mengetahui tentang pelaksanaan MCP yang mendasari.
Contoh kod lengkap:
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();
}
}
}Di sinilah sebahagian besar kerja berlaku. Kita akan memanggil LLM dengan prompt pengguna awal, kemudian memproses respons untuk melihat jika ada alat yang perlu dipanggil. Jika ya, kita akan memanggil alat tersebut dan meneruskan perbualan dengan LLM sehingga tiada lagi panggilan alat yang diperlukan dan kita mempunyai respons akhir.
Kita akan membuat beberapa panggilan kepada LLM, jadi mari kita definisikan satu fungsi yang akan mengendalikan panggilan LLM. Tambahkan fungsi berikut ke dalam fail main.rs anda:
async fn call_llm(
client: &Client<OpenAIConfig>,
messages: &[Value],
tools: &ListToolsResult,
) -> Result<Value, Box<dyn Error>> {
let response = client
.completions()
.create_byot(json!({
"messages": messages,
"model": "openai/gpt-4.1",
"tools": format_tools(tools).await?,
}))
.await?;
Ok(response)
}Fungsi ini menerima klien LLM, senarai mesej (termasuk arahan pengguna), alat dari pelayan MCP, dan menghantar permintaan kepada LLM, lalu mengembalikan respons.
Respons daripada LLM akan mengandungi satu array choices. Kita perlu memproses hasilnya untuk melihat jika terdapat sebarang tool_calls. Ini membolehkan kita mengetahui jika LLM meminta alat tertentu untuk dipanggil dengan argumen. Tambahkan kod berikut ke bahagian bawah fail main.rs anda untuk mentakrifkan fungsi yang mengendalikan respons LLM:
async fn process_llm_response(
llm_response: &Value,
mcp_client: &RunningService<RoleClient, ()>,
openai_client: &Client<OpenAIConfig>,
mcp_tools: &ListToolsResult,
messages: &mut Vec<Value>,
) -> Result<(), Box<dyn Error>> {
let Some(message) = llm_response
.get("choices")
.and_then(|c| c.as_array())
.and_then(|choices| choices.first())
.and_then(|choice| choice.get("message"))
else {
return Ok(());
};
// Print content if available
if let Some(content) = message.get("content").and_then(|c| c.as_str()) {
println!("🤖 {}", content);
}
// Handle tool calls
if let Some(tool_calls) = message.get("tool_calls").and_then(|tc| tc.as_array()) {
messages.push(message.clone()); // Add assistant message
// Execute each tool call
for tool_call in tool_calls {
let (tool_id, name, args) = extract_tool_call_info(tool_call)?;
println!("⚡ Calling tool: {}", name);
let result = mcp_client
.call_tool(CallToolRequestParam {
name: name.into(),
arguments: serde_json::from_str::<Value>(&args)?.as_object().cloned(),
})
.await?;
// Add tool result to messages
messages.push(json!({
"role": "tool",
"tool_call_id": tool_id,
"content": serde_json::to_string_pretty(&result)?
}));
}
// Continue conversation with tool results
let response = call_llm(openai_client, messages, mcp_tools).await?;
Box::pin(process_llm_response(
&response,
mcp_client,
openai_client,
mcp_tools,
messages,
))
.await?;
}
Ok(())
}Jika tool_calls wujud, ia akan mengekstrak maklumat alat, memanggil pelayan MCP dengan permintaan alat, dan menambah hasilnya ke mesej perbualan. Ia kemudian meneruskan perbualan dengan LLM dan mesej dikemas kini dengan respons pembantu dan hasil panggilan alat.
Untuk mengekstrak maklumat panggilan alat yang dikembalikan oleh LLM untuk panggilan MCP, kita akan menambah satu lagi fungsi pembantu untuk mengekstrak semua yang diperlukan untuk membuat panggilan. Tambahkan kod berikut ke bahagian bawah fail main.rs anda:
fn extract_tool_call_info(tool_call: &Value) -> Result<(String, String, String), Box<dyn Error>> {
let tool_id = tool_call
.get("id")
.and_then(|id| id.as_str())
.unwrap_or("")
.to_string();
let function = tool_call.get("function").ok_or("Missing function")?;
let name = function
.get("name")
.and_then(|n| n.as_str())
.unwrap_or("")
.to_string();
let args = function
.get("arguments")
.and_then(|a| a.as_str())
.unwrap_or("{}")
.to_string();
Ok((tool_id, name, args))
}Dengan semua bahagian telah siap, kita kini boleh mengendalikan arahan awal pengguna dan memanggil LLM. Kemas kini fungsi main anda untuk memasukkan kod berikut:
// LLM conversation with tool calls
let response = call_llm(&openai_client, &messages, &tools).await?;
process_llm_response(
&response,
&mcp_client,
&openai_client,
&tools,
&mut messages,
)
.await?;Ini akan membuat pertanyaan kepada LLM dengan arahan awal pengguna yang meminta jumlah dua nombor, dan ia akan memproses respons untuk mengendalikan panggilan alat secara dinamik.
Bagus, anda berjaya!
Ambil kod daripada latihan ini dan bina pelayan dengan beberapa alat tambahan. Kemudian cipta klien dengan LLM, seperti dalam latihan ini, dan uji dengan pelbagai arahan untuk memastikan semua alat pelayan anda dipanggil secara dinamik. Cara membina klien seperti ini memberikan pengalaman pengguna yang hebat kerana mereka dapat menggunakan arahan biasa, bukannya arahan klien yang tepat, dan tidak menyedari sebarang pelayan MCP sedang dipanggil.
- Menambah LLM ke dalam klien anda memberikan cara yang lebih baik untuk pengguna berinteraksi dengan Pelayan MCP.
- Anda perlu menukar respons Pelayan MCP kepada sesuatu yang boleh difahami oleh LLM.
- Java Calculator
- .Net Calculator
- JavaScript Calculator
- TypeScript Calculator
- Python Calculator
- Rust Calculator
- Seterusnya: Menggunakan pelayan dengan Visual Studio Code
Penafian:
Dokumen ini telah diterjemahkan menggunakan perkhidmatan terjemahan AI Co-op Translator. Walaupun kami berusaha untuk memastikan ketepatan, sila ambil maklum bahawa terjemahan automatik mungkin mengandungi kesilapan atau ketidaktepatan. Dokumen asal dalam bahasa asalnya harus dianggap sebagai sumber yang berwibawa. Untuk maklumat penting, terjemahan manusia profesional adalah disyorkan. Kami tidak bertanggungjawab atas sebarang salah faham atau salah tafsir yang timbul daripada penggunaan terjemahan ini.