45 KiB
Kliendi loomine LLM-iga
Nii kaugele olete näinud, kuidas luua server ja klient. Klient on suutnud serverit otseselt kutsuda, et loetleda selle tööriistu, ressursse ja viipasid. Kuid see pole väga praktiline lähenemine. Teie kasutajad elavad agendi-ajastus ja eeldavad, et saavad kasutada viipasid ja suhelda LLM-iga. Neid ei huvita, kas te kasutate MCP-d oma võimekuse salvestamiseks; nad lihtsalt ootavad loomulikus keeles suhtlemist. Kuidas me selle lahendame? Lahendus on lisada LLM kliendile.
Ülevaade
Selles õppetükis keskendume LLM-i lisamisele teie kliendile ja näitame, kuidas see annab kasutajale palju parema kogemuse.
Õpieesmärgid
Selle õppetüki lõpuks oskate:
- Luua kliendi koos LLM-iga.
- Sujuvalt suhelda MCP serveriga, kasutades LLM-i.
- Pakku paremat lõppkasutaja kogemust kliendi poolel.
Lähenemine
Proovime mõista, millist lähenemist tuleb võtta. LLM-i lisamine tundub lihtne, kuid kas me seda tegelikult teeme?
Nii suhtleb klient serveriga:
-
Luua ühendus serveriga.
-
Loetleda võimekused, viibad, ressursid ja tööriistad ning salvestada nende skeemid.
-
Lisada LLM ja anda salvestatud võimekused ning nende skeemid vormingus, mida LLM mõistab.
-
Käsitleda kasutaja viipa, edastades selle LLM-ile koos kliendi poolt loetletud tööriistadega.
Suurepärane, nüüd, kui me mõistame suurt pilti, proovime seda allolevas ülesandes.
Harjutus: Kliendi loomine LLM-iga
Selles harjutuses õpime lisama LLM-i oma kliendile.
Autentimine GitHubi isikliku ligipääsutunnusega
GitHubi tunnuse loomine on lihtne protsess. Siin on, kuidas seda teha:
- Minge GitHubi sätetesse – klõpsake paremas ülanurgas oma profiilipildil ja valige Settings.
- Navigeerige arendussätete lehele – kerige alla ja klõpsake Developer Settings.
- Valige Personal Access Tokens – klõpsake Fine-grained tokens ja seejärel Generate new token.
- Konfigureerige oma tunnus – lisage viide, määrake aegumiskuupäev ja valige vajalikud õigused (scopes). Selles juhul kindlasti lisage Models õigused.
- Genereerige ja kopeerige tunnus – klõpsake Generate token ning kopeerige see kohe, kuna te ei näe seda enam kunagi.
-1- Ühenduse loomine serveriga
Loome kõigepealt oma kliendi:
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"; // Impordi zod skeemi valideerimiseks
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: {}
}
}
);
}
}
Eelnevas koodis me oleme:
- Importinud vajalikud teegid
- Loonud klassi kahe liikmega
clientjaopenai, mis aitavad meil hallata klienti ja suhelda vastavalt LLM-iga. - Konfigureerinud oma LLM eksemplari kasutama GitHub Models, määrates
baseUrlinference API aadressiks.
Python
from mcp import ClientSession, StdioServerParameters, types
from mcp.client.stdio import stdio_client
# Loo serveri parameetrid stdio ühenduseks
server_params = StdioServerParameters(
command="mcp", # Käivitatav fail
args=["run", "server.py"], # Valikulised käsurea argumendid
env=None, # Valikulised keskkonnamuutujad
)
async def run():
async with stdio_client(server_params) as (read, write):
async with ClientSession(
read, write
) as session:
# Alusta ühendust
await session.initialize()
if __name__ == "__main__":
import asyncio
asyncio.run(run())
Eelnevas koodis me oleme:
- Importinud MCP jaoks vajalikud teegid
- Loonud kliendi
.NET
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);
Java
Esiteks peate lisama oma pom.xml faili LangChain4j sõltuvused. Lisage need sõltuvused, et lubada MCP integreerimine ja GitHub Models tugi:
<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>
Seejärel looge oma Java kliendiklass:
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 { // Määra LLM kasutama GitHubi mudeleid
ChatLanguageModel model = OpenAiOfficialChatModel.builder()
.isGitHubModels(true)
.apiKey(System.getenv("GITHUB_TOKEN"))
.timeout(Duration.ofSeconds(60))
.modelName("gpt-4.1-nano")
.build();
// Loo MCP transport serveriga ühendamiseks
McpTransport transport = new HttpMcpTransport.Builder()
.sseUrl("http://localhost:8080/sse")
.timeout(Duration.ofSeconds(60))
.logRequests(true)
.logResponses(true)
.build();
// Loo MCP klient
McpClient mcpClient = new DefaultMcpClient.Builder()
.transport(transport)
.build();
}
}
Eelnevas koodis me oleme:
- Lisanud LangChain4j sõltuvused: MCP integreerimiseks, OpenAI ametlikuks kliendiks ja GitHub Models toeks
- Importinud LangChain4j teegid: MCP integreerimiseks ja OpenAI vestlusmudeli funktsionaalsuseks
- Loonud
ChatLanguageModel: konfigureeritud kasutama GitHub Models oma GitHubi nimetusega - Määranud HTTP transpordi: kasutades Server-Sent Events (SSE), et ühendada MCP serveriga
- Loonud MCP kliendi: mis haldab suhtlust serveriga
- Kasutanud LangChain4j sisseehitatud MCP tuge: mis lihtsustab LLM-ide ja MCP serverite vahelist integratsiooni
Rust
See näide eeldab, et teil on Rust-põhine MCP server töötamas. Kui teil seda pole, vaadake tagasi 01-first-server õppetükile, et server luua.
Kui teil on Rust MCP server, avage terminal ja navigeerige serveri kausta. Käivitage järgmine käsk, et luua uus LLM kliendiprojekt:
mkdir calculator-llmclient
cd calculator-llmclient
cargo init
Lisage oma Cargo.toml faili järgmised sõltuvused:
[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
Rustile ametlikku OpenAI teeki ei ole, kuid
async-openaicrate on kogukonna hooldatav teek, mida sageli kasutatakse.
Avage src/main.rs fail ja asendage selle sisu järgmise koodiga:
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>> {
// Algne sõnum
let mut messages = vec![json!({"role": "user", "content": "What is the sum of 3 and 2?"})];
// OpenAI kliendi seadistamine
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),
);
// MCP kliendi seadistamine
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: Hankida MCP tööriistade nimekiri
// TODO: Suurkeele mudeli vestlus tööriistakutsega
Ok(())
}
See kood seab üles lihtsa Rusti rakenduse, mis ühendub MCP serveri ja GitHub Modelsiga LLM suhtluseks.
Important
Veenduge, et olete seadistanud keskkonnamuutuja
OPENAI_API_KEYoma GitHubi tunnusega enne rakenduse käivitamist.
Suurepärane, järgmise sammuna loetleme serveri võimekused.
-2- Serveri võimekuste loetelu
Nüüd loome ühenduse serveriga ja küsime selle võimekusi:
Typescript
Selles samas klassis lisage järgmised meetodid:
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");
// tööriistade nimekirja koostamine
const toolsResult = await this.client.listTools();
}
Eelnevas koodis me oleme:
- Lisanud koodi serveriga ühenduse loomiseks,
connectToServer. - Loonud meetodi
run, mis vastutab meie rakenduse voolu haldamise eest. Seni loetleb see ainult tööriistad, kuid lisame peagi rohkem.
Python
# Loetle saadaolevad ressursid
resources = await session.list_resources()
print("LISTING RESOURCES")
for resource in resources:
print("Resource: ", resource)
# Loetle saadaolevad tööriistad
tools = await session.list_tools()
print("LISTING TOOLS")
for tool in tools.tools:
print("Tool: ", tool.name)
print("Tool", tool.inputSchema["properties"])
Siin on, mida me lisasime:
- Loetlesime ressursid ja tööriistad ning trükkisime need välja. Tööriistade puhul loetleme ka
inputSchema, mida kasutame hiljem.
.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;
}
Eelnevas koodis me oleme:
- Loetlenud MCP serveris olevad tööriistad
- Iga tööriista kohta loetlenud nime, kirjelduse ja selle skeemi. Viimane on midagi, mida kasutame tööriistade kutsumiseks.
Java
// Loo tööriistapakkuja, mis avastab automaatselt MCP tööriistad
ToolProvider toolProvider = McpToolProvider.builder()
.mcpClients(List.of(mcpClient))
.build();
// MCP tööriistapakkuja haldab automaatselt:
// - MCP serverist saadaolevate tööriistade loetelu
// - MCP tööriista skeemide teisendamine LangChain4j formaati
// - Tööriistade käivitamise ja vastuste haldamine
Eelnevas koodis me oleme:
- Loonud
McpToolProvider, mis automaatselt leiab ja registreerib kõik MCP serveri tööriistad - Tööriistapakkuja teisendab MCP tööriistade skeemid LangChain4j tööriistade formaati sisemiselt
- See lähenemine varjab käsitsi tööriistade loetlemise ja teisendamise protsessi
Rust
Tööriistade hankimine MCP serverist toimub meetodiga list_tools. Oma main funktsioonis, pärast MCP kliendi seadistamist, lisage järgmine kood:
// Hangi MCP tööriistade nimekiri
let tools = mcp_client.list_tools(Default::default()).await?;
-3- Serveri võimekuste teisendamine LLM tööriistadeks
Järgmine samm pärast serveri võimekuste loetlemist on nende teisendamine vormingusse, mida LLM saab mõista. Kui see tehtud, saame pakkuda neid võimekusi tööriistadena oma LLM-ile.
TypeScript
-
Lisage järgmine kood, et teisendada MCP serveri vastus tööriista formaadiks, mida LLM saab kasutada:
openAiToolAdapter(tool: { name: string; description?: string; input_schema: any; }) { // Loo zod skeem sisendskeemi põhjal const schema = z.object(tool.input_schema); return { type: "function" as const, // Sea tüüp selgelt "function" väärtuseks function: { name: tool.name, description: tool.description, parameters: { type: "object", properties: tool.input_schema.properties, required: tool.input_schema.required, }, }, }; }Ülaltoodud kood võtab MCP serveri vastuse ja teisendab selle selliseks tööriista definitsiooni formaadiks, mida LLM mõistab.
-
Uuendame nüüd
runmeetodit, et loetleda serveri võimekusi: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, }); }); }Eelnevas koodis uuendasime
runmeetodit, kasutades tulemuse läbimist ja iga üksuse jaoks kutsudesopenAiToolAdapter.
Python
-
Loome esmalt järgmise teisendaja funktsiooni
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_schemaFunktsioonis
convert_to_llm_toolsteisendame MCP tööriistavastuse tegemaks seda LLM-i arusaadavaks. -
Järgmiseks uuendame oma kliendi koodi, kasutades seda funktsiooni nii:
functions = [] for tool in tools.tools: print("Tool: ", tool.name) print("Tool", tool.inputSchema["properties"]) functions.append(convert_to_llm_tool(tool))Siin lisame funktsiooni
convert_to_llm_toolkutse, mis teisendab MCP tööriista vastuse hilisemaks LLM sisendiks.
.NET
- Lisame koodi, mis teisendab MCP tööriistavastuse vormingusse, mida LLM mõistab
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;
}
Eelnevas koodis me oleme:
- Loonud funktsiooni
ConvertFrom, mis võtab nime, kirjelduse ja sisendi skeemi. - Määratlenud funktsionaalsuse, mis loob
FunctionDefinition-i, mis antakse edasiChatCompletionsDefinition-ile. Viimane on midagi, mida LLM mõistab.
-
Uuendame olemasolevat koodi nii, et kasutaks seda funktsiooni eelnevalt:
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.
Java
// Loo Bot-liides loomuliku keele suhtlemiseks
public interface Bot {
String chat(String prompt);
}
// Konfigureeri tehisintellekti teenus LLM ja MCP tööriistadega
Bot bot = AiServices.builder(Bot.class)
.chatLanguageModel(model)
.toolProvider(toolProvider)
.build();
Eelnevas koodis me oleme:
- Määratlenud lihtsa
Botliidese loomuliku keele põhisteks interaktsioonideks - Kasutanud LangChain4j
AiServices-i, et automaatselt siduda LLM MCP tööriistapakkujaga - Raamistik haldab automaatselt tööriistade skeemi teisendamist ja funktsioonide väljakutset tagapõhjal
- See lähenemine elimineerib käsitsi tööriistade konverteerimise – LangChain4j haldab kogu kompleksust MCP tööriistade LLM-iga ühilduvasse formaati teisendamisel
Rust
MCP tööriistavastuse teisendamiseks LLM arusaadavasse formaati lisame abifunktsiooni, mis vormindab tööriistade nimekirja. Lisage järgmine kood oma main.rs faili main funktsiooni alla. Seda kutsutakse LLM päringute tegemisel:
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)
}
Suurepärane, meil on valmis kasutajapäringute töötlemiseks, seega lähme nüüd sellele juurde.
-4- Kasutajaviiba päringu käsitlemine
Selles koodiosas käitleme kasutajate päringuid.
TypeScript
-
Lisage meetod, mida kasutatakse LLM-i kutsumiseks:
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. Kutsu serveri tööriista const toolResult = await this.client.callTool({ name: toolName, arguments: JSON.parse(args), }); console.log("Tool result: ", toolResult); // 3. Tee midagi tulemusega // TEE } }Eelnevas koodis me:
-
Lisasime meetodi
callTools. -
Meetod võtab LLM vastuse ja kontrollib, milliseid tööriistu on kutsutud, kui neid on:
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)}`); // tööriista kutsumine } -
Kutsub tööriista, kui LLM näitab, et see tuleks kutsuda:
// 2. Kutsuge serveri tööriista const toolResult = await this.client.callTool({ name: toolName, arguments: JSON.parse(args), }); console.log("Tool result: ", toolResult); // 3. Tehke tulemusega midagi // TEGEMATA
-
-
Uuendame
runmeetodit, lisades LLM-ile kutsumise jacallToolsmeetodi kasutamise:// 1. Loo sõnumid, mis on LLM sisendiks 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. Kutsu LLM-i let response = this.openai.chat.completions.create({ model: "gpt-4.1-mini", max_tokens: 1000, messages, tools: tools, }); let results: any[] = []; // 3. Läbige LLM vastus, iga valiku puhul kontrollige, kas selles on tööriistakutseid (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); } });
Suurepärane, vaatame kogu koodi täies mahus:
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"; // Impordi zod skeemi valideerimiseks
class MyClient {
private openai: OpenAI;
private client: Client;
constructor(){
this.openai = new OpenAI({
baseURL: "https://models.inference.ai.azure.com", // võib tulevikus vaja minna seda url-i muuta: 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;
}) {
// Loo zod skeem sisendskeemi põhjal
const schema = z.object(tool.input_schema);
return {
type: "function" as const, // Määra tüübiks selgelt "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. Kutsu serveri tööriista
const toolResult = await this.client.callTool({
name: toolName,
arguments: JSON.parse(args),
});
console.log("Tool result: ", toolResult);
// 3. Tee midagi tulemusega
// 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-4.1-mini",
max_tokens: 1000,
messages,
tools: tools,
});
let results: any[] = [];
// 3. Läbi LLM vastuse, kontrolli iga valikut, kas seal on tööriista kutsed
(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
-
Lisame importid, mida on vaja LLM-i kutsumiseks
# 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 -
Lisame funktsiooni, mis LLM-i kutsub:
# 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, # Valikulised parameetrid 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_callEelnevas koodis me oleme:
- Edastanud LLM-ile funktsioonid, mida MCP serverist leidsime ja teisendasime.
- Seejärel kutsusime LLM-i nende funktsioonidega.
- Seejärel kontrollime tulemust, et näha, milliseid funktsioone kutsuda, kui üldse kutsuda.
- Lõpuks edastame massiivi funktsioonikutsudest.
-
Viimane samm, uuendame meie põhikoodi:
prompt = "Add 2 to 20" # küsi LLM-ilt, milliseid tööriistu kasutada, kui üldse functions_to_call = call_llm(prompt, functions) # kutsu soovitatud funktsioonid esile for f in functions_to_call: result = await session.call_tool(f["name"], arguments=f["args"]) print("TOOLS result: ", result.content)Seal, see oli lõppsamm, ülalolevas koodis me:
- Kutsub MCP tööriista
call_toolabil, kasutades LLM-i poolt viipa põhjal soovitatud funktsiooni. - Trükib välja tööriista kõne tulemuse MCP serverisse.
- Kutsub MCP tööriista
.NET
-
Näitame koodi LLM-viiba päringu tegemiseks:
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;Eelnevas koodis me oleme:
- Hangi tööriistad MCP serverist,
var tools = await GetMcpTools(). - Määratlesi kasutajaviiba
userMessage. - Konstrukteerisid valikud, mis määravad mudeli ja tööriistad.
- Tegid päringu LLM-ile.
- Hangi tööriistad MCP serverist,
-
Viimane samm, näeme, kas LLM arvab, et peaksime funktsiooni kutsuma:
// 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); }Eelnevas koodis me oleme:
- Läbisin funktsiooni kutsete nimekirja.
- Iga tööriista kõne puhul parsin nime ja argumendid ning kutsun tööriista MCP serveris külge MCP kliendi abil. Lõpuks trükime välja tulemused.
Siin on kogu kood:
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);
}
// 6. Print the generic response
Console.WriteLine($"Assistant response: {content}");
Java
try {
// Täida looduskeelseid päringuid, mis automaatselt kasutavad MCP tööriistu
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();
}
Eelnevas koodis me oleme:
- Kasutanud lihtsaid loomuliku keele viipasid MCP serveri tööriistadega suhtlemiseks
- LangChain4j raamistik haldab automaatselt:
- Kasutajaviipade teisendamist tööriistakutseteks vajadusel
- Sobivate MCP tööriistade kutsumist LLM-i otsuse põhjal
- Konversatsioonivoo haldamist LLM ja MCP serveri vahel
- Meetod
bot.chat()tagastab loomuliku keele vastuseid, mis võivad sisaldada MCP tööriistade käivitamise tulemusi - See lähenemine tagab sujuva kasutajakogemuse, kus kasutajad ei pea teadma aluseks olevat MCP teostust
Täielik koodi näide:
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();
}
}
}
Rust
Siin toimub enamik tööd. Me kutsume LLM-i esialgse kasutaja viibaga, seejärel töötleme vastuse, et näha, kas mõnda tööriista tuleb kutsuda. Kui jah, kutsume need tööriistad ja jätkame vestlust LLM-iga, kuni enam tööriistakutseid ei ole ja meil on lõplik vastus.
Kuna me teeme mitu LLM kutset, defineerime funktsiooni, mis haldab LLM-i kutset. Lisage järgmine funktsioon oma main.rs faili:
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)
}
See funktsioon võtab LLM kliendi, sõnumite nimekirja (kaasates kasutaja viiba), tööriistad MCP serverist ja saadab LLM-ile päringu ning tagastab vastuse.
LLMi vastus sisaldab choices massiivi. Me peame vastuse töötlema, et näha, kas leidub mõni tool_calls. See annab meile teada, et LLM palub konkreetset tööriista argumendiga käivitada. Lisage järgmine kood oma main.rs faili lõppu, et määratleda funktsioon LLMi vastuse käsitlemiseks:
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(());
};
// Trüki sisu, kui see on saadaval
if let Some(content) = message.get("content").and_then(|c| c.as_str()) {
println!("🤖 {}", content);
}
// Töötle tööriistakõnesid
if let Some(tool_calls) = message.get("tool_calls").and_then(|tc| tc.as_array()) {
messages.push(message.clone()); // Lisa assistendi sõnum
// Käivita iga tööriistakõne
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?;
// Lisa tööriista tulemus sõnumitesse
messages.push(json!({
"role": "tool",
"tool_call_id": tool_id,
"content": serde_json::to_string_pretty(&result)?
}));
}
// Jätka vestlust tööriista tulemustega
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(())
}
Kui tool_calls on olemas, võtab see välja tööriista info, kutsub MCP serverit tööriista taotlusega ja lisab tulemused vestluse sõnumitesse. Seejärel jätkub vestlus LLMiga ning sõnumid uuendatakse assistendi vastuse ja tööriista kutse tulemustega.
Et võtta välja tööriista kutse infot, mida LLM tagastab MCP kutsete jaoks, lisame teise abifunktsiooni, mis ekstraheerib kõik vajaliku kutse tegemiseks. Lisage järgmine kood oma main.rs faili lõppu:
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))
}
Kõik vajalik on paigas, nüüd saame käsitleda algset kasutajapäringut ja kutsuda LLMi. Uuendage oma main funktsiooni, et see sisaldaks järgmist koodi:
// LLM vestlus tööriistakõnedega
let response = call_llm(&openai_client, &messages, &tools).await?;
process_llm_response(
&response,
&mcp_client,
&openai_client,
&tools,
&mut messages,
)
.await?;
See teeb päringu LLMile algse kasutajapäringuga, kus küsitakse kahe arvu summat, ja töötleb vastust dünaamiliselt tööriistakutsete käsitlemiseks.
Suurepärane, see õnnestus!
Kodutöö
Võtke harjutusest kood ja looge server mõne lisatööriistaga. Seejärel looge klient LLMiga, nagu harjutuses, ja testige seda erinevate päringutega, et veenduda, et kõik teie serveritööriistad kutsutakse dünaamiliselt. Selline kliendi ülesehitamine tagab suurepärase kasutajakogemuse, kuna kasutajad saavad kasutada päringuid täpse kliendikäskluse asemel ja olla teadmatud mingist MCP serveri kutsest.
Lahendus
Peamised mõtted
- LLMi lisamine kliendile annab kasutajatele parema võimaluse suhelda MCP serveritega.
- MCP serveri vastus tuleb ümber tõlkida formaati, mida LLM suudab mõista.
Näited
- Java kalkulaator
- .Net kalkulaator
- JavaScript kalkulaator
- TypeScript kalkulaator
- Python kalkulaator
- Rust kalkulaator
Lisamaterjalid
Mis järgmiseks
Lahtiütlus: See dokument on tõlgitud kasutades AI tõlketeenust Co-op Translator. Kuigi me püüdleme täpsuse poole, palun pange tähele, et automatiseeritud tõlgetes võib esineda vigu või ebatäpsusi. Originaaldokument selle emakeeles tuleks pidada autoriteetseks allikaks. Olulise teabe puhul soovitatakse kasutada professionaalset inimtõlget. Me ei vastuta selle tõlkega seotud eksimustest või valesti mõistmistest.