Până acum, ai văzut cum să creezi un server și un client. Clientul a putut apela explicit serverul pentru a lista uneltele, resursele și prompturile sale. Totuși, aceasta nu este o abordare foarte practică. Utilizatorii tăi trăiesc în era agentică și se așteaptă să folosească prompturi și să comunice cu un LLM. Nu le pasă dacă folosești MCP pentru a stoca capabilitățile tale; se așteaptă pur și simplu să interacționeze folosind limbaj natural. Deci, cum rezolvăm asta? Soluția este să adăugăm un LLM clientului.
În această lecție ne concentrăm pe adăugarea unui LLM la clientul tău și demonstrăm cum acest lucru oferă o experiență mult mai bună pentru utilizatorul final.
La finalul acestei lecții, vei putea:
- Să creezi un client cu un LLM.
- Să interacționezi fluent cu un server MCP folosind un LLM.
- Să oferi o experiență mai bună utilizatorului final pe partea clientului.
Să încercăm să înțelegem abordarea pe care trebuie să o luăm. Adăugarea unui LLM pare simplă, dar chiar o vom face?
Iată cum va interacționa clientul cu serverul:
-
Se stabilește conexiunea cu serverul.
-
Se listează capabilitățile, prompturile, resursele și uneltele și se salvează schema acestora.
-
Se adaugă un LLM și se transmit capabilitățile salvate împreună cu schema lor într-un format înțeles de LLM.
-
Se gestionează un prompt de la utilizator, transmițându-l către LLM împreună cu uneltele listate de client.
Groza, acum că înțelegem cum putem face asta la nivel înalt, să încercăm în exercițiul de mai jos.
În acest exercițiu, vom învăța să adăugăm un LLM clientului nostru.
Crearea unui token GitHub este un proces simplu. Iată cum poți face asta:
- Mergi la Setările GitHub – Click pe poza ta de profil din colțul dreapta sus și selectează Setări.
- Navighează la Setări pentru Dezvoltatori – Derulează în jos și apasă pe Setări pentru Dezvoltatori.
- Selectează Token-uri de Acces Personal – Apasă pe Token-uri Detaliate și apoi pe Generează un token nou.
- Configurează Token-ul – Adaugă o notă pentru referință, setează o dată de expirare și selectează permisiunile necesare. În acest caz, asigură-te că adaugi permisiunea Models.
- Generează și Copiază Token-ul – Apasă pe Generează token și asigură-te că-l copiezi imediat, deoarece nu vei mai putea să-l vezi din nou.
Să creăm mai întâi clientul nostru:
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 pentru validarea schemei
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: {}
}
}
);
}
}În codul precedent am:
- Importat librăriile necesare
- Creat o clasă cu doi membri,
clientșiopenaicare ne vor ajuta să gestionăm un client și să interacționăm cu un LLM respectiv. - Configurat instanța LLM să folosească GitHub Models prin setarea
baseUrlcare indică la API-ul de inferență.
from mcp import ClientSession, StdioServerParameters, types
from mcp.client.stdio import stdio_client
# Creează parametrii serverului pentru conexiunea stdio
server_params = StdioServerParameters(
command="mcp", # Executabil
args=["run", "server.py"], # Argumente opționale din linia de comandă
env=None, # Variabile de mediu opționale
)
async def run():
async with stdio_client(server_params) as (read, write):
async with ClientSession(
read, write
) as session:
# Inițializează conexiunea
await session.initialize()
if __name__ == "__main__":
import asyncio
asyncio.run(run())În codul precedent am:
- Importat librăriile necesare pentru MCP
- Creat un client
using Azure;
using Azure.AI.Inference;
using Azure.Identity;
using System.Text.Json;
using ModelContextProtocol.Client;
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 McpClient.CreateAsync(clientTransport);Mai întâi, trebuie să adaugi dependențele LangChain4j în fișierul tău pom.xml. Adaugă aceste dependențe pentru a permite integrarea MCP și suportul 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>Apoi creează clasa client 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 { // Configurează LLM să folosească Modelele GitHub
ChatLanguageModel model = OpenAiOfficialChatModel.builder()
.isGitHubModels(true)
.apiKey(System.getenv("GITHUB_TOKEN"))
.timeout(Duration.ofSeconds(60))
.modelName("gpt-4.1-nano")
.build();
// Creează transport MCP pentru conectarea la server
McpTransport transport = new HttpMcpTransport.Builder()
.sseUrl("http://localhost:8080/sse")
.timeout(Duration.ofSeconds(60))
.logRequests(true)
.logResponses(true)
.build();
// Creează client MCP
McpClient mcpClient = new DefaultMcpClient.Builder()
.transport(transport)
.build();
}
}În codul precedent am:
- Adăugat dependențele LangChain4j: Necesare pentru integrarea MCP, clientul oficial OpenAI și suportul GitHub Models
- Importat librăriile LangChain4j: Pentru integrarea MCP și funcționalitatea modelului de chat OpenAI
- Creat un
ChatLanguageModel: Configurat să folosească GitHub Models cu token-ul tău GitHub - Setat transportul HTTP: Folosind Server-Sent Events (SSE) pentru a conecta la serverul MCP
- Creat un client MCP: Care va gestiona comunicarea cu serverul
- Folosind suportul integrat LangChain4j pentru MCP: Care simplifică integrarea dintre LLM-uri și serverele MCP
Acest exemplu presupune că ai un server MCP bazat pe Rust deja pornit. Dacă nu ai unul, consultă lecția 01-first-server pentru a crea serverul.
Odată ce ai serverul Rust MCP, deschide un terminal și navighează în același director cu serverul. Apoi rulează următoarea comandă pentru a crea un nou proiect client LLM:
mkdir calculator-llmclient
cd calculator-llmclient
cargo initAdaugă următoarele dependențe în fișierul tău Cargo.toml:
[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
Nu există o bibliotecă oficială Rust pentru OpenAI, totuși async-openai este un pachet menținut de comunitate folosit frecvent.
Deschide fișierul src/main.rs și înlocuiește conținutul său cu următorul cod:
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>> {
// Mesaj inițial
let mut messages = vec![json!({"role": "user", "content": "What is the sum of 3 and 2?"})];
// Configurare client OpenAI
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),
);
// Configurare client MCP
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: Obține lista instrumentelor MCP
// TODO: Conversație LLM cu apeluri către instrumente
Ok(())
}Acest cod configurează o aplicație simplă Rust care se va conecta la serverul MCP și GitHub Models pentru interacțiuni LLM.
Important
Asigură-te că setezi variabila de mediu OPENAI_API_KEY cu token-ul tău GitHub înainte de a rula aplicația.
Groza, pentru pasul următor, să listăm capabilitățile serverului.
Acum vom conecta la server și vom cere capabilitățile sale:
În aceeași clasă, adaugă următoarele metode:
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");
// listarea uneltelor
const toolsResult = await this.client.listTools();
}În codul precedent am:
- Adăugat cod pentru conectarea la server,
connectToServer. - Creat o metodă
runresponsabilă pentru gestionarea fluxului aplicației. Până acum listează doar uneltele, dar vom adăuga mai multe în curând.
# Listează resursele disponibile
resources = await session.list_resources()
print("LISTING RESOURCES")
for resource in resources:
print("Resource: ", resource)
# Listează uneltele disponibile
tools = await session.list_tools()
print("LISTING TOOLS")
for tool in tools.tools:
print("Tool: ", tool.name)
print("Tool", tool.inputSchema["properties"])Iată ce am adăugat:
- Listăm resursele și uneltele și le afișăm. Pentru unelte listăm și
inputSchemape care îl vom folosi ulterior.
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;
}În codul precedent am:
- Listat uneltele disponibile pe Serverul MCP
- Pentru fiecare unealtă, am listat numele, descrierea și schema sa. Aceasta din urmă este ceva ce vom folosi pentru a apela uneltele în curând.
// Creează un furnizor de instrumente care descoperă automat instrumentele MCP
ToolProvider toolProvider = McpToolProvider.builder()
.mcpClients(List.of(mcpClient))
.build();
// Furnizorul de instrumente MCP gestionează automat:
// - Listarea instrumentelor disponibile de pe serverul MCP
// - Conversia schemelor instrumentelor MCP în format LangChain4j
// - Gestionarea execuției instrumentelor și răspunsurilorÎn codul precedent am:
- Creat un
McpToolProvidercare descoperă automat și înregistrează toate uneltele de pe serverul MCP - Furnizorul de unelte gestionează conversia între schema uneltelor MCP și formatul uneltelor LangChain4j intern
- Această abordare ascunde procesul manual de listare și conversie a uneltelor
Preluarea uneltelor de pe serverul MCP se face folosind metoda list_tools. În funcția ta main după ce setezi clientul MCP, adaugă următorul cod:
// Obține listarea uneltelor MCP
let tools = mcp_client.list_tools(Default::default()).await?;Următorul pas după listarea capabilităților serverului este să le convertim într-un format pe care LLM îl înțelege. Odată ce facem acest lucru, putem oferi aceste capabilități ca unelte LLM-ului nostru.
-
Adaugă următorul cod pentru a converti răspunsul de la serverul MCP într-un format de unealtă pe care LLM îl poate folosi:
openAiToolAdapter(tool: { name: string; description?: string; input_schema: any; }) { // Creează un schema zod bazat pe input_schema const schema = z.object(tool.input_schema); return { type: "function" as const, // Setează explicit tipul la "function" function: { name: tool.name, description: tool.description, parameters: { type: "object", properties: tool.input_schema.properties, required: tool.input_schema.required, }, }, }; }
Codul de mai sus ia un răspuns de la serverul MCP și îl convertește într-un format de definiție a unealtelor pe care LLM îl poate înțelege.
-
Să actualizăm metoda
runpentru a lista capabilitățile serverului: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, }); }); }
În codul precedent, am actualizat metoda
runsă parcurgă rezultatul și pentru fiecare element să apelezeopenAiToolAdapter.
-
Mai întâi, să creăm următoarea funcție convertor
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
În funcția de mai sus
convert_to_llm_toolsluăm un răspuns de unealtă MCP și îl convertim într-un format pe care LLM îl poate înțelege. -
Apoi, să actualizăm codul clientului nostru să folosească această funcție astfel:
functions = [] for tool in tools.tools: print("Tool: ", tool.name) print("Tool", tool.inputSchema["properties"]) functions.append(convert_to_llm_tool(tool))
Aici, adăugăm un apel pentru
convert_to_llm_toolpentru a converti răspunsul unealtelor MCP într-o formă pe care o putem da LLM-ului mai târziu.
- Hai să adăugăm cod pentru a converti răspunsul unealtelor MCP într-un format pe care LLM îl poate înțelege
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;
}În codul precedent am:
- Creat o funcție
ConvertFromcare primește numele, descrierea și schema de input. - Definim funcționalitatea care creează o definiție de funcție ce este transmisă unui
ChatCompletionsDefinition. Aceasta din urmă este ceva ce LLM poate înțelege.
-
Să vedem cum putem actualiza codul existent pentru a folosi această funcție:
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; } ``` In the preceding code, we've: - Update the function to convert the MCP tool response to an LLm tool. Let's highlight the code we added: ```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); ``` The input schema is part of the tool response but on the "properties" attribute, so we need to extract. Furthermore, we now call `ConvertFrom` with the tool details. Now we've done the heavy lifting, let's see how it call comes together as we handle a user prompt next.
// Creați o interfață Bot pentru interacțiune în limbaj natural
public interface Bot {
String chat(String prompt);
}
// Configurați serviciul AI cu instrumente LLM și MCP
Bot bot = AiServices.builder(Bot.class)
.chatLanguageModel(model)
.toolProvider(toolProvider)
.build();În codul precedent am:
- Definit o interfață simplă
Botpentru interacțiuni în limbaj natural - Folosit
AiServicesdin LangChain4j pentru a lega automat LLM-ul cu furnizorul de unelte MCP - Framework-ul gestionează automat conversia schemelor uneltelor și apelul funcțiilor în fundal
- Această abordare elimină conversia manuală a uneltelor - LangChain4j gestionează toată complexitatea convertirii uneltelor MCP în format compatibil LLM
Pentru a converti răspunsul unealtelor MCP într-un format pe care LLM îl înțelege, vom adăuga o funcție ajutătoare care formatează lista de unelte. Adaugă următorul cod în fișierul tău main.rs sub funcția main. Aceasta va fi apelată atunci când vom face cereri către 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)
}Groza, suntem gata să gestionăm orice cereri de la utilizatori, deci hai să continuăm.
În această parte a codului vom gestiona cererile utilizatorilor.
-
Adaugă o metodă care va fi folosită pentru apelarea LLM-ului:
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. Apelarea uneltei serverului const toolResult = await this.client.callTool({ name: toolName, arguments: JSON.parse(args), }); console.log("Tool result: ", toolResult); // 3. Faceți ceva cu rezultatul // DE FACUT } }
În codul precedent am:
-
Adăugat o metodă
callTools. -
Metoda primește un răspuns LLM și verifică dacă vreuna dintre unelte trebuie apelată:
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)}`); // apelează unelte }
-
Apelează o unealtă, dacă LLM indică că ar trebui apelată:
// 2. Apelează instrumentul serverului const toolResult = await this.client.callTool({ name: toolName, arguments: JSON.parse(args), }); console.log("Tool result: ", toolResult); // 3. Fă ceva cu rezultatul // TODO
-
-
Actualizează metoda
runpentru a include apelurile către LLM și apelareacallTools:// 1. Creează mesaje care sunt input pentru 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. Apelarea LLM let response = this.openai.chat.completions.create({ model: "gpt-4.1-mini", max_tokens: 1000, messages, tools: tools, }); let results: any[] = []; // 3. Parcurge răspunsul LLM, pentru fiecare opțiune, verifică dacă există apeluri către instrumente (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); } });
Groza, să vedem codul complet:
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 pentru validarea schemei
class MyClient {
private openai: OpenAI;
private client: Client;
constructor(){
this.openai = new OpenAI({
baseURL: "https://models.inference.ai.azure.com", // ar putea fi necesar să se schimbe la acest url în viitor: 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;
}) {
// Creează o schemă zod bazată pe input_schema
const schema = z.object(tool.input_schema);
return {
type: "function" as const, // Setează explicit tipul la "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. Apelează instrumentul serverului
const toolResult = await this.client.callTool({
name: toolName,
arguments: JSON.parse(args),
});
console.log("Tool result: ", toolResult);
// 3. Fă ceva cu rezultatul
// DE FACUT
}
}
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-4.1-mini",
max_tokens: 1000,
messages,
tools: tools,
});
let results: any[] = [];
// 1. Parcurge răspunsul LLM, pentru fiecare alegere, verifică dacă are apeluri către instrumente
(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);-
Hai să adăugăm câteva importuri necesare pentru a apela un 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
-
Următorul pas, să adăugăm funcția care va apela LLM-ul:
# 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, # Parametri opționali 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
În codul precedent am:
- Transmis funcțiile noastre, găsite pe serverul MCP și convertite, către LLM.
- Apoi am apelat LLM-ul cu aceste funcții.
- Apoi inspectăm rezultatul pentru a vedea ce funcții ar trebui apelate, dacă există.
- În final, transmitem un array cu funcții de apelat.
-
Pasul final, să actualizăm codul nostru principal:
prompt = "Add 2 to 20" # întreabă LLM ce unelte să folosească, dacă există functions_to_call = call_llm(prompt, functions) # apelează funcțiile sugerate for f in functions_to_call: result = await session.call_tool(f["name"], arguments=f["args"]) print("TOOLS result: ", result.content)
Asta a fost ultimul pas, în codul de mai sus noi:
- Apelăm o unealtă MCP prin
call_toolfolosind o funcție pe care LLM a considerat că ar trebui să o apelăm în baza promptului dat. - Afișăm rezultatul apelului uneltei către serverul MCP.
- Apelăm o unealtă MCP prin
-
Hai să arătăm un exemplu de solicitare 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-4.1-mini", Tools = { tools[0] } }; // 3. Call the model ChatCompletions? response = await client.CompleteAsync(options); var content = response.Content;
În codul precedent am:
- Preluat uneltele de pe serverul MCP,
var tools = await GetMcpTools(). - Definit un prompt utilizator
userMessage. - Construim un obiect opțiuni specificând modelul și uneltele.
- Făcut o cerere către LLM.
- Preluat uneltele de pe serverul MCP,
-
Un ultim pas, să vedem dacă LLM crede că ar trebui să apelăm o funcție:
// 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); }
În codul precedent am:
- Iterat prin lista de apeluri de funcții.
- Pentru fiecare apel de unealtă, extragem numele și argumentele și apelăm unealta pe serverul MCP folosind clientul MCP. În final afișăm rezultatele.
Iată codul complet:
using Azure;
using Azure.AI.Inference;
using Azure.Identity;
using System.Text.Json;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
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 McpClient.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-4.1-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.OfType<TextContentBlock>().First().Text);
}
// 5. Print the generic response
Console.WriteLine($"Assistant response: {content}");try {
// Execută cereri în limbaj natural care utilizează automat instrumentele MCP
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();
}În codul precedent am:
- Folosit prompturi simple în limbaj natural pentru a interacționa cu uneltele de pe serverul MCP
- Framework-ul LangChain4j gestionează automat:
- Conversia prompturilor utilizator la apeluri de unelte atunci când este necesar
- Apelarea uneltelor MCP potrivite bazat pe decizia LLM-ului
- Gestionarea fluxului conversației dintre LLM și serverul MCP
- Metoda
bot.chat()returnează răspunsuri în limbaj natural care pot include rezultate din execuțiile uneltelor MCP - Această abordare oferă o experiență fluidă utilizatorului, fără ca acesta să trebuiască să cunoască detaliile implementării MCP
Exemplu complet de cod:
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();
}
}
}Aici se întâmplă cea mai mare parte a lucrului. Vom apela LLM-ul cu promptul inițial al utilizatorului, apoi vom procesa răspunsul pentru a vedea dacă trebuie apelate unelte. Dacă da, vom apela aceste unelte și vom continua conversația cu LLM până când nu mai sunt apeluri de unelte necesare și avem un răspuns final.
Vom face apeluri multiple către LLM, așa că să definim o funcție care va gestiona apelul LLM. Adaugă următoarea funcție în fișierul tău main.rs:
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)
}Această funcție primește clientul LLM, o listă de mesaje (inclusiv promptul utilizatorului), uneltele de pe serverul MCP și trimite o cerere către LLM, returnând răspunsul.
Răspunsul de la LLM va conține un array de choices. Va trebui să procesăm rezultatul pentru a vedea dacă există tool_calls. Acest lucru ne arată că LLM solicită apelarea unui instrument specific cu argumente. Adaugă următorul cod la finalul fișierului tău main.rs pentru a defini o funcție care să gestioneze răspunsul 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(());
};
// Afișează conținutul dacă este disponibil
if let Some(content) = message.get("content").and_then(|c| c.as_str()) {
println!("🤖 {}", content);
}
// Gestionează apelurile instrumentului
if let Some(tool_calls) = message.get("tool_calls").and_then(|tc| tc.as_array()) {
messages.push(message.clone()); // Adaugă mesajul asistentului
// Execută fiecare apel al instrumentului
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?;
// Adaugă rezultatul instrumentului în mesaje
messages.push(json!({
"role": "tool",
"tool_call_id": tool_id,
"content": serde_json::to_string_pretty(&result)?
}));
}
// Continuă conversația cu rezultatele instrumentului
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(())
}Dacă tool_calls sunt prezente, se extrage informația despre instrument, se apelează serverul MCP cu cererea instrumentului și se adaugă rezultatele la mesajele conversației. Apoi, conversația continuă cu LLM, iar mesajele sunt actualizate cu răspunsul asistentului și rezultatele apelului instrumentului.
Pentru a extrage informațiile despre apelul instrumentului pe care LLM le returnează pentru apelurile MCP, vom adăuga o altă funcție ajutătoare pentru a extrage tot ce este necesar pentru a efectua apelul. Adaugă următorul cod la finalul fișierului tău main.rs:
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))
}Cu toate elementele la locul lor, acum putem gestiona promptul inițial al utilizatorului și apela LLM. Actualizează funcția ta main pentru a include următorul cod:
// Conversație LLM cu apeluri către unelte
let response = call_llm(&openai_client, &messages, &tools).await?;
process_llm_response(
&response,
&mcp_client,
&openai_client,
&tools,
&mut messages,
)
.await?;Acesta va interoga LLM cu promptul inițial al utilizatorului cerând suma a două numere și va procesa răspunsul pentru a gestiona dinamic apelurile instrumentelor.
Minunat, ai reușit!
Ia codul din exercițiu și construiește serverul cu câteva instrumente suplimentare. Apoi creează un client cu un LLM, ca în exercițiu, și testează-l cu prompturi diferite pentru a te asigura că toate instrumentele serverului tău sunt apelate dinamic. Această modalitate de a construi un client înseamnă că utilizatorul final va avea o experiență excelentă, deoarece poate folosi prompturi în loc de comenzi exacte ale clientului și va fi complet inconștient de orice apel către serverul MCP.
- Adăugarea unui LLM în clientul tău oferă o modalitate mai bună pentru utilizatori de a interacționa cu serverele MCP.
- Trebuie să convertești răspunsul serverului MCP într-un format pe care LLM îl poate înțelege.
- Calculator Java
- Calculator .Net
- Calculator JavaScript
- Calculator TypeScript
- Calculator Python
- Calculator Rust
- Următorul pas: Consumarea unui server folosind Visual Studio Code
Declinare de responsabilitate:
Acest document a fost tradus folosind serviciul de traducere AI Co-op Translator. Deși ne străduim pentru acuratețe, vă rugăm să fiți conștienți că traducerile automate pot conține erori sau inexactități. Documentul original în limba sa nativă trebuie considerat sursa autoritară. Pentru informații critice, se recomandă traducerea profesională realizată de un traducător uman. Nu ne asumăm răspunderea pentru eventualele neînțelegeri sau interpretări greșite care pot apărea din utilizarea acestei traduceri.