Så langt har du sett hvordan lage en server og en klient. Klienten har vært i stand til å kalle serveren eksplisitt for å liste opp verktøy, ressurser og prompts. Dette er imidlertid ikke en veldig praktisk tilnærming. Brukerne dine lever i agentisk tid og forventer å bruke prompts og kommunisere med en LLM i stedet. De bryr seg ikke om du bruker MCP for å lagre dine kapabiliteter; de forventer bare å interagere ved å bruke naturlig språk. Så hvordan løser vi dette? Løsningen er å legge til en LLM i klienten.
I denne leksjonen fokuserer vi på å legge til en LLM i klienten og viser hvordan dette gir en mye bedre opplevelse for brukeren.
Innen slutten av denne leksjonen vil du kunne:
- Lage en klient med en LLM.
- Sømløst interagere med en MCP-server ved hjelp av en LLM.
- Gi en bedre sluttbrukeropplevelse på klientsiden.
La oss prøve å forstå tilnærmingen vi må ta. Å legge til en LLM høres enkelt ut, men skal vi faktisk gjøre dette?
Slik vil klienten interagere med serveren:
-
Etablere forbindelse med serveren.
-
Liste opp kapabiliteter, prompts, ressurser og verktøy, og lagre skjemaet deres.
-
Legge til en LLM og sende de lagrede kapabilitetene og deres skjema i et format som LLM forstår.
-
Håndtere en brukerprompt ved å sende den til LLM sammen med verktøyene listet opp av klienten.
Flott, nå forstår vi hvordan vi kan gjøre dette på høyt nivå, la oss prøve det ut i øvelsen nedenfor.
I denne øvelsen skal vi lære å legge til en LLM til klienten vår.
Å lage et GitHub-token er en enkel prosess. Slik kan du gjøre det:
- Gå til GitHub-innstillinger – Klikk på profilbildet ditt øverst til høyre og velg Innstillinger.
- Naviger til Developer Settings – Rull ned og klikk på Developer Settings.
- Velg Personal Access Tokens – Klikk på Fine-grained tokens og deretter Generate new token.
- Konfigurer tokenet ditt – Legg til en notat for referanse, sett utløpsdato og velg nødvendige scopes (tillatelser). Sørg i dette tilfellet for å legge til Models-tillatelsen.
- Generer og kopier tokenet – Klikk Generate token, og pass på å kopiere det umiddelbart, for du vil ikke kunne se det igjen.
La oss lage klienten vår først:
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"; // Importer zod for skjema-validering
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: {}
}
}
);
}
}I den foregående koden har vi:
- Importert nødvendige biblioteker
- Opprettet en klasse med to medlemmer,
clientogopenai, som hjelper oss å administrere en klient og interagere med en LLM henholdsvis. - Konfigurert vår LLM-instans til å bruke GitHub Models ved å sette
baseUrltil inferens-APIet.
from mcp import ClientSession, StdioServerParameters, types
from mcp.client.stdio import stdio_client
# Opprett serverparametere for stdio-tilkobling
server_params = StdioServerParameters(
command="mcp", # Kjørbar fil
args=["run", "server.py"], # Valgfrie kommandolinjeargumenter
env=None, # Valgfrie miljøvariabler
)
async def run():
async with stdio_client(server_params) as (read, write):
async with ClientSession(
read, write
) as session:
# Initialiser tilkoblingen
await session.initialize()
if __name__ == "__main__":
import asyncio
asyncio.run(run())I den foregående koden har vi:
- Importert nødvendige biblioteker for MCP
- Opprettet en klient
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);Først må du legge til LangChain4j-avhengigheter i pom.xml-filen din. Legg til disse avhengighetene for å aktivere MCP-integrasjon og støtte for 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>Opprett deretter din Java-klientklasse:
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 { // Konfigurer LLM for å bruke GitHub-modeller
ChatLanguageModel model = OpenAiOfficialChatModel.builder()
.isGitHubModels(true)
.apiKey(System.getenv("GITHUB_TOKEN"))
.timeout(Duration.ofSeconds(60))
.modelName("gpt-4.1-nano")
.build();
// Opprett MCP-transport for tilkobling til server
McpTransport transport = new HttpMcpTransport.Builder()
.sseUrl("http://localhost:8080/sse")
.timeout(Duration.ofSeconds(60))
.logRequests(true)
.logResponses(true)
.build();
// Opprett MCP-klient
McpClient mcpClient = new DefaultMcpClient.Builder()
.transport(transport)
.build();
}
}I den foregående koden har vi:
- Lagt til LangChain4j-avhengigheter: Krevd for MCP-integrasjon, OpenAI offisiell klient, og GitHub Models støtte
- Importert LangChain4j-bibliotekene: For MCP-integrasjon og OpenAI chat-modell funksjonalitet
- Opprettet en
ChatLanguageModel: Konfigurert til å bruke GitHub Models med din GitHub-token - Satt opp HTTP-transport: Bruker Server-Sent Events (SSE) for å koble til MCP-serveren
- Opprettet en MCP-klient: Som håndterer kommunikasjon med serveren
- Brukt LangChain4js innebygde MCP-støtte: Som forenkler integrasjonen mellom LLMs og MCP-servere
Dette eksemplet antar at du har en Rust-basert MCP-server kjørende. Hvis du ikke har det, se tilbake til 01-first-server leksjonen for å lage serveren.
Når du har din Rust MCP-server, åpne et terminalvindu og naviger til samme mappe som serveren. Kjør deretter følgende kommando for å lage et nytt LLM-klientprosjekt:
mkdir calculator-llmclient
cd calculator-llmclient
cargo initLegg til følgende avhengigheter i din Cargo.toml-fil:
[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
Det finnes ikke et offisielt Rust-bibliotek for OpenAI, men async-openai-cratet er et fellesskapsvedlikeholdt bibliotek som ofte brukes.
Åpne src/main.rs-filen og erstatt innholdet med følgende kode:
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>> {
// Startmelding
let mut messages = vec![json!({"role": "user", "content": "What is the sum of 3 and 2?"})];
// Sett opp OpenAI-klient
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),
);
// Sett opp MCP-klient
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: Hent MCP verktøyliste
// TODO: LLM-samtale med verktøykall
Ok(())
}Denne koden setter opp en enkel Rust-applikasjon som vil koble til en MCP-server og GitHub Models for LLM-interaksjoner.
Important
Pass på å sette miljøvariabelen OPENAI_API_KEY med din GitHub-token før du kjører applikasjonen.
Flott, neste steg er å liste kapabilitetene på serveren.
Nå skal vi koble til serveren og spørre om dens kapabiliteter:
I samme klasse, legg til følgende metoder:
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");
// verktøy for oppføring
const toolsResult = await this.client.listTools();
}I den foregående koden har vi:
- Lagt til kode for å koble til serveren,
connectToServer. - Opprettet en
run-metode som håndterer flyten i appen. Så langt lister den bare opp verktøyene, men vi vil legge til mer snart.
# List tilgjengelige ressurser
resources = await session.list_resources()
print("LISTING RESOURCES")
for resource in resources:
print("Resource: ", resource)
# List tilgjengelige verktøy
tools = await session.list_tools()
print("LISTING TOOLS")
for tool in tools.tools:
print("Tool: ", tool.name)
print("Tool", tool.inputSchema["properties"])Dette la vi til:
- Listet ressurser og verktøy og skrev dem ut. For verktøy lister vi også
inputSchemasom vi bruker senere.
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;
}I den foregående koden har vi:
- Listet verktøy tilgjengelig på MCP-serveren
- For hvert verktøy, listet navn, beskrivelse og skjema. Sistnevnte er noe vi vil bruke for å kalle verktøyene snart.
// Lag en verktøyleverandør som automatisk oppdager MCP-verktøy
ToolProvider toolProvider = McpToolProvider.builder()
.mcpClients(List.of(mcpClient))
.build();
// MCP-verktøyleverandøren håndterer automatisk:
// - Å liste tilgjengelige verktøy fra MCP-serveren
// - Konvertering av MCP-verktøyskjemaer til LangChain4j-format
// - Administrering av verktøykjøring og svarI den foregående koden har vi:
- Opprettet en
McpToolProvidersom automatisk oppdager og registrerer alle verktøy fra MCP-serveren - Verktøyleverandøren håndterer konvertering mellom MCP-verktøyskjemaer og LangChain4js verktøyformat internt
- Denne tilnærmingen skjuler manuell verktøyliste og konverteringsprosess
Å hente verktøy fra MCP-serveren gjøres med list_tools-metoden. I din main-funksjon, etter oppsett av MCP-klienten, legg til følgende kode:
// Hent MCP verktøyliste
let tools = mcp_client.list_tools(Default::default()).await?;Neste steg etter å ha listet serverkapabiliteter, er å konvertere dem til et format LLM forstår. Når vi gjør det, kan vi tilby disse kapabilitetene som verktøy til vår LLM.
-
Legg til følgende kode for å konvertere respons fra MCP Server til et verktøyformat LLM kan bruke:
openAiToolAdapter(tool: { name: string; description?: string; input_schema: any; }) { // Lag et zod-skjema basert på input_schema const schema = z.object(tool.input_schema); return { type: "function" as const, // Angi eksplisitt type til "function" function: { name: tool.name, description: tool.description, parameters: { type: "object", properties: tool.input_schema.properties, required: tool.input_schema.required, }, }, }; }
Koden over tar en respons fra MCP-serveren og konverterer den til et verktøysdefinisjonsformat som LLM kan forstå.
-
La oss oppdatere
run-metoden for å liste serverkapabiliteter: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, }); }); }
I den foregående koden har vi oppdatert
run-metoden til å mappe gjennom resultatet og kalleopenAiToolAdapterfor hver oppføring.
-
Først, la oss lage følgende konverteringsfunksjon
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
I funksjonen
convert_to_llm_toolstar vi et MCP-verktøyrespons og konverterer det til et format som LLM kan skjønne. -
Så oppdaterer vi klientkoden til å bruke denne funksjonen slik:
functions = [] for tool in tools.tools: print("Tool: ", tool.name) print("Tool", tool.inputSchema["properties"]) functions.append(convert_to_llm_tool(tool))
Her legger vi til et kall til
convert_to_llm_toolfor å konvertere MCP-verktøyresponsen til noe vi kan mate inn i LLM senere.
- La oss legge til kode for å konvertere MCP-verktøyrespons til noe LLM kan skjønne
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;
}I den foregående koden har vi:
- Opprettet en funksjon
ConvertFromsom tar navn, beskrivelse og input-skjema. - Definert funksjonalitet som lager en FunctionDefinition som sendes til en ChatCompletionsDefinition. Sistnevnte er noe LLM kan forstå.
-
La oss se hvordan vi kan oppdatere eksisterende kode for å nytte denne funksjonen:
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.
// Opprett et Bot-grensesnitt for naturlig språkinteraksjon
public interface Bot {
String chat(String prompt);
}
// Konfigurer AI-tjenesten med LLM- og MCP-verktøy
Bot bot = AiServices.builder(Bot.class)
.chatLanguageModel(model)
.toolProvider(toolProvider)
.build();I den foregående koden har vi:
- Definert et enkelt
Bot-grensesnitt for naturlige språkinteraksjoner - Brukt LangChain4js
AiServicesfor automatisk å binde LLM med MCP-verktøyleverandør - Rammeverket håndterer automatisk verktøyskjema-konvertering og funksjonskall bak scenen
- Denne tilnærmingen eliminerer manuell verktøykonvertering – LangChain4j håndterer kompleksiteten med å konvertere MCP-verktøy til format kompatibelt med LLM
For å konvertere MCP-verktøyresponsen til et format LLM kan forstå, legger vi til en hjelpefunksjon som formaterer verktøystigningen. Legg til følgende kode i main.rs under main-funksjonen. Den vil bli kalt når vi gjør forespørsler til LLMen:
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)
}Flott, nå er vi klare for å håndtere brukerforespørsler, la oss gå videre til det.
I denne delen av koden skal vi håndtere brukerforespørsler.
-
Legg til en metode som skal brukes for å kalle vår 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. Kall serverens verktøy const toolResult = await this.client.callTool({ name: toolName, arguments: JSON.parse(args), }); console.log("Tool result: ", toolResult); // 3. Gjør noe med resultatet // TODO } }
I koden over har vi:
-
Lagt til metoden
callTools. -
Metoden tar et LLM-svar og sjekker hvilke verktøy som er kalt, hvis noen:
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)}`); // kall verktøyet }
-
Kaller et verktøy hvis LLM indikerer at det skal kalles:
// 2. Kall serverens verktøy const toolResult = await this.client.callTool({ name: toolName, arguments: JSON.parse(args), }); console.log("Tool result: ", toolResult); // 3. Gjør noe med resultatet // TODO
-
-
Oppdater
run-metoden til å inkludere kall til LLM og kallecallTools:// 1. Opprett meldinger som er input for 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. Kaller LLM let response = this.openai.chat.completions.create({ model: "gpt-4.1-mini", max_tokens: 1000, messages, tools: tools, }); let results: any[] = []; // 3. Gå gjennom LLM-responsen, for hvert valg, sjekk om det har verktøyanrop (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); } });
Flott, la oss liste hele koden:
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"; // Importer zod for skjema-validering
class MyClient {
private openai: OpenAI;
private client: Client;
constructor(){
this.openai = new OpenAI({
baseURL: "https://models.inference.ai.azure.com", // kan hende vi må bytte til denne URLen i fremtiden: 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;
}) {
// Lag et zod-skjema basert på input_schema
const schema = z.object(tool.input_schema);
return {
type: "function" as const, // Sett eksplisitt type til "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. Kall serverens verktøy
const toolResult = await this.client.callTool({
name: toolName,
arguments: JSON.parse(args),
});
console.log("Tool result: ", toolResult);
// 3. Gjør noe med resultatet
// 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[] = [];
// 1. Gå gjennom LLM-responsen, for hvert valg, sjekk om det har verktøysanrop
(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);-
La oss legge til noen imports som trengs for å kalle en 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
-
Deretter legger vi til funksjonen som kaller 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, # Valgfrie parametere 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
I koden over har vi:
- Sendt funksjonene våre, som vi fant på MCP-serveren og konverterte, til LLM.
- Deretter kalt LLM med disse funksjonene.
- Sjekket resultatet for å se hvilke funksjoner vi skal kalle, hvis noen.
- Til slutt sender vi en liste av funksjoner å kalle.
-
Siste steg, la oss oppdatere hovedkoden:
prompt = "Add 2 to 20" # spør LLM hvilke verktøy, om noen, som skal brukes functions_to_call = call_llm(prompt, functions) # kall foreslåtte funksjoner for f in functions_to_call: result = await session.call_tool(f["name"], arguments=f["args"]) print("TOOLS result: ", result.content)
Der, det var siste steg, i koden over:
- Kaller vi et MCP-verktøy via
call_toolmed en funksjon som LLM mente vi burde kalle basert på prompten. - Skriver ut resultatet av verktøykallet til MCP-serveren.
- Kaller vi et MCP-verktøy via
-
Her er kode for å gjøre et LLM-promptkall:
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;
I den foregående koden har vi:
- Hentet verktøy fra MCP-serveren,
var tools = await GetMcpTools(). - Definert en brukerprompt
userMessage. - Konstruert et options-objekt med modell og verktøy.
- Gjort en forespørsel til LLM.
- Hentet verktøy fra MCP-serveren,
-
En siste ting, la oss se om LLM mener vi skal kalle en funksjon:
// 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); }
I koden over har vi:
- Løpet gjennom en liste med funksjonskall.
- For hvert verktøykall, parse ut navn og argumenter og kalle verktøyet på MCP-serveren med MCP-klienten. Til slutt skriver vi ut resultatene.
Her er den fullstendige koden:
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 {
// Utfør naturlige språkforespørsler som automatisk bruker MCP-verktøy
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();
}I den foregående koden har vi:
- Brukt enkle naturlige språkprompter for å interagere med MCP-serververktøyene
- LangChain4j-rammeverket håndterer automatisk:
- Konvertering av brukerprompter til verktøykall når nødvendig
- Kall av passende MCP-verktøy basert på LLMs avgjørelse
- Håndtering av samtaleflyt mellom LLM og MCP-server
bot.chat()-metoden returnerer naturlige språkresponser som kan inkludere resultater fra MCP-verktøykjøringer- Denne tilnærmingen gir en sømløs brukeropplevelse der brukere ikke trenger å vite om underliggende MCP-implementering
Fullstendig kodeeksempel:
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();
}
}
}Her skjer det meste av arbeidet. Vi kaller LLM med den første brukerprompten, deretter evaluerer vi responsen for å se om noen verktøy må kalles. Hvis ja, kaller vi disse og fortsetter samtalen med LLM til det ikke lenger er behov for flere verktøykall og vi har et endelig svar.
Vi vil gjøre flere kall til LLM, så la oss definere en funksjon som håndterer LLM-kallet. Legg til følgende funksjon i main.rs-filen din:
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)
}Denne funksjonen tar LLM-klienten, en liste av meldinger (inkludert brukerprompt), verktøy fra MCP-serveren, og sender en forespørsel til LLM, og returnerer responsen.
Responsen fra LLM vil inneholde et array av choices. Vi må prosessere resultatet for å se om noen tool_calls er til stede. Dette lar oss vite at LLM ber om at et spesifikt verktøy skal kalles med argumenter. Legg til følgende kode nederst i main.rs-filen din for å definere en funksjon som håndterer LLM-responsen:
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(());
};
// Skriv ut innhold hvis tilgjengelig
if let Some(content) = message.get("content").and_then(|c| c.as_str()) {
println!("🤖 {}", content);
}
// Håndter verktøysamtaler
if let Some(tool_calls) = message.get("tool_calls").and_then(|tc| tc.as_array()) {
messages.push(message.clone()); // Legg til assistentmelding
// Utfør hver verktøysamtale
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?;
// Legg til verktøyresultat i meldinger
messages.push(json!({
"role": "tool",
"tool_call_id": tool_id,
"content": serde_json::to_string_pretty(&result)?
}));
}
// Fortsett samtalen med verktøyresultater
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(())
}Hvis tool_calls er til stede, ekstraherer den verktøysinformasjonen, kaller MCP-serveren med verktøyforespørselen, og legger til resultatene i samtalemeldingene. Den fortsetter deretter samtalen med LLM, og meldingene oppdateres med assistentens respons og resultatene fra verktøykallet.
For å ekstrahere verktøykallinformasjon som LLM returnerer for MCP-kall, legger vi til en annen hjelpefunksjon for å hente alt som trengs for å utføre kallene. Legg til følgende kode nederst i main.rs-filen din:
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))
}Med alle delene på plass kan vi nå håndtere den første brukerprompten og kalle LLM. Oppdater main-funksjonen din for å inkludere følgende kode:
// LLM-samtale med verktøysanrop
let response = call_llm(&openai_client, &messages, &tools).await?;
process_llm_response(
&response,
&mcp_client,
&openai_client,
&tools,
&mut messages,
)
.await?;Dette vil spørre LLM med den innledende brukerprompten som ber om summen av to tall, og det vil prosessere responsen for å håndtere verktøykall dynamisk.
Flott, det klarte du!
Ta koden fra øvelsen og bygg ut serveren med flere verktøy. Lag så en klient med en LLM, som i øvelsen, og test den ut med ulike prompts for å forsikre deg om at alle serververktøyene blir kalt dynamisk. Denne måten å bygge en klient på gir en god brukeropplevelse siden brukeren kan bruke naturlige prompts i stedet for eksakte klientkommandoer, og være uvitende om at en MCP-server kalles.
- Å legge til en LLM i klienten gir en bedre måte for brukere å interagere med MCP-servere.
- Du må konvertere MCP-serverresponsen til noe LLM kan forstå.
- Java Kalkulator
- .Net Kalkulator
- JavaScript Kalkulator
- TypeScript Kalkulator
- Python Kalkulator
- Rust Kalkulator
Ansvarsfraskrivelse: Dette dokumentet er oversatt ved hjelp av AI-oversettelsestjenesten Co-op Translator. Selv om vi streber etter nøyaktighet, vennligst vær oppmerksom på at automatiske oversettelser kan inneholde feil eller unøyaktigheter. Det opprinnelige dokumentet på originalspråket skal anses som den autoritative kilden. For kritisk informasjon anbefales profesjonell menneskelig oversettelse. Vi er ikke ansvarlige for eventuelle misforståelser eller feiltolkninger som oppstår ved bruk av denne oversettelsen.