Files
2026-07-13 13:31:35 +08:00

45 KiB
Raw Permalink Blame History

Ustvarjanje odjemalca z LLM

Do zdaj ste videli, kako ustvariti strežnik in odjemalca. Odjemalec je lahko eksplicitno poklical strežnik za seznam njegovih orodij, virov in pozivov. Vendar pa to ni zelo praktičen pristop. Vaši uporabniki živijo v dobi agentnosti in pričakujejo uporabo pozivov ter komunikacijo z LLM. Ni jim pomembno, ali uporabljate MCP za shranjevanje svojih zmogljivosti; preprosto pričakujejo interakcijo v naravnem jeziku. Kako torej to rešimo? Rešitev je dodati LLM odjemalcu.

Pregled

V tej lekciji se osredotočamo na dodajanje LLM k vašemu odjemalcu in prikazujemo, kako to nudi veliko boljšo izkušnjo za vašega uporabnika.

Cilji učenja

Do konca te lekcije boste znali:

  • Ustvariti odjemalca z LLM.
  • Brezhibno komunicirati s strežnikom MCP z uporabo LLM.
  • Zagotoviti boljšo uporabniško izkušnjo na strani odjemalca.

Pristop

Poskusimo razumeti pristop, ki ga moramo uporabiti. Dodajanje LLM se sliši preprosto, vendar ali bomo to dejansko naredili?

Tako bo odjemalec komuniciral s strežnikom:

  1. Vzpostavi povezavo s strežnikom.

  2. Našteje zmogljivosti, pozive, vire in orodja ter shrani njihovo shemo.

  3. Doda LLM in posreduje shranjene zmogljivosti s shemo v obliki, ki jo LLM razume.

  4. Obravnava uporabniški poziv tako, da ga posreduje LLM skupaj z orodji, ki jih je naštel odjemalec.

Odlično, zdaj ko razumemo, kako to lahko storimo na višji ravni, poskusimo to v spodnji vaji.

Vaja: Ustvarjanje odjemalca z LLM

V tej vaji bomo izvedeli, kako dodati LLM našemu odjemalcu.

Avtentikacija z osebnim dostopnim žetonom GitHub

Ustvarjanje GitHub žetona je enostaven postopek. Tako ga lahko naredite:

  • Pojdite na nastavitve GitHub Kliknite na svojo profilno sliko v zgornjem desnem kotu in izberite Nastavitve.
  • Pomaknite se do Nastavitev razvijalca Pomaknite se navzdol in kliknite na Nastavitve razvijalca.
  • Izberite osebne dostopne žetone Kliknite na Natančno določeni žetoni in nato Ustvari nov žeton.
  • Konfigurirajte svoj žeton Dodajte opombo za referenco, nastavite datum poteka in izberite potrebna dovoljenja (skope). V tem primeru ne pozabite dodati dovoljenja Models.
  • Ustvarite in kopirajte žeton Kliknite Ustvari žeton in ga takoj kopirajte, saj ga ne boste mogli več videti.

-1- Povežite se s strežnikom

Najprej ustvarimo našega odjemalca:

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"; // Uvozi zod za validacijo sheme

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: {}
                }
            }
            );    
    }
}

V zgornji kodi smo:

  • Uvozili potrebne knjižnice
  • Ustvarili razred z dvema članoma, client in openai, ki bosta pomagala upravljati odjemalca in komunicirati z LLM.
  • Nastavili naš LLM primer tako, da uporablja GitHub Models z nastavitvijo baseUrl, ki kaže na inference API.

Python

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

# Ustvari parametre strežnika za stdio povezavo
server_params = StdioServerParameters(
    command="mcp",  # Izvedljiva datoteka
    args=["run", "server.py"],  # Neobvezni ukazni argumenti
    env=None,  # Neobvezne okoljske spremenljivke
)


async def run():
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(
            read, write
        ) as session:
            # Inicializiraj povezavo
            await session.initialize()


if __name__ == "__main__":
    import asyncio

    asyncio.run(run())

V zgornji kodi smo:

  • Uvozili potrebne knjižnice za MCP
  • Ustvarili odjemalca

.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

Najprej morate dodati odvisnosti LangChain4j v svojo datoteko pom.xml. Dodajte te odvisnosti za omogočanje integracije MCP in podpore 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>

Nato ustvarite svojo Java odjemalsko razredno datoteko:

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 {        // Konfigurirajte LLM za uporabo GitHub modelov
        ChatLanguageModel model = OpenAiOfficialChatModel.builder()
                .isGitHubModels(true)
                .apiKey(System.getenv("GITHUB_TOKEN"))
                .timeout(Duration.ofSeconds(60))
                .modelName("gpt-4.1-nano")
                .build();

        // Ustvarite MCP prenos za povezavo s strežnikom
        McpTransport transport = new HttpMcpTransport.Builder()
                .sseUrl("http://localhost:8080/sse")
                .timeout(Duration.ofSeconds(60))
                .logRequests(true)
                .logResponses(true)
                .build();

        // Ustvarite MCP odjemalca
        McpClient mcpClient = new DefaultMcpClient.Builder()
                .transport(transport)
                .build();
    }
}

V zgornji kodi smo:

  • Dodali odvisnosti LangChain4j: Potrebne za integracijo MCP, uradnega odjemalca OpenAI in podporo GitHub Models
  • Uvozili knjižnice LangChain4j: Za integracijo MCP in funkcionalnost klepeta modela OpenAI
  • Ustvarili ChatLanguageModel: Nastavljen za uporabo GitHub Models z vašim GitHub žetonom
  • Nastavili HTTP promet: Z uporabo Server-Sent Events (SSE) za povezavo s MCP strežnikom
  • Ustvarili MCP odjemalca: Ki bo upravljal komunikacijo s strežnikom
  • Uporabili vgrajeno podporo MCP v LangChain4j: Ki poenostavlja integracijo med LLM in MCP strežniki

Rust

Ta primer predpostavlja, da imate delujoč MCP strežnik na osnovi Rust. Če ga nimate, se vrnite k lekciji 01-first-server za ustvarjanje strežnika.

Ko imate svoj Rust MCP strežnik, odprite terminal in se premaknite v isti imenik kot strežnik. Nato zaženite naslednji ukaz za ustvarjanje novega LLM odjemalskega projekta:

mkdir calculator-llmclient
cd calculator-llmclient
cargo init

Dodajte naslednje odvisnosti v svojo datoteko 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

Za OpenAI ni uradne Rust knjižnice, vendar je async-openai paket skupnostno vzdrževana knjižnica, ki se pogosto uporablja.

Odprite datoteko src/main.rs in nadomestite vsebino z naslednjo kodo:

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>> {
    // Začetno sporočilo
    let mut messages = vec![json!({"role": "user", "content": "What is the sum of 3 and 2?"})];

    // Nastavi odjemalca 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),
    );

    // Nastavi odjemalca 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: Pridobi seznam orodij MCP

    // TODO: Pogovor LLM z klici orodij

    Ok(())
}

Ta koda nastavi osnovno Rust aplikacijo, ki se bo povezala s MCP strežnikom in GitHub Models za interakcije z LLM.

Important

Pred zagonom aplikacije ne pozabite nastaviti okoljske spremenljivke OPENAI_API_KEY z vašim GitHub žetonom.

Odlično, za naslednji korak pa naštejmo zmogljivosti strežnika.

-2- Naštejmo zmogljivosti strežnika

Zdaj se bomo povezali s strežnikom in zahtevali njegove zmogljivosti:

Typescript

V istem razredu dodajte naslednje 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");

    // orodja za izpis
    const toolsResult = await this.client.listTools();
}

V zgornji kodi smo:

  • Dodali kodo za povezavo s strežnikom, connectToServer.
  • Ustvarili metodo run, ki upravlja tok naše aplikacije. Do zdaj samo našteje orodja, kmalu pa bomo dodali še več.

Python

# Naštej razpoložljive vire
resources = await session.list_resources()
print("LISTING RESOURCES")
for resource in resources:
    print("Resource: ", resource)

# Naštej razpoložljiva orodja
tools = await session.list_tools()
print("LISTING TOOLS")
for tool in tools.tools:
    print("Tool: ", tool.name)
    print("Tool", tool.inputSchema["properties"])

Tukaj smo dodali:

  • Seznam virov in orodij ter jih izpisali. Za orodja naštet neposredno tudi inputSchema, ki ga bomo kasneje uporabili.

.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;
}

V zgornji kodi smo:

  • Našteli orodja, ki so na voljo na MCP strežniku
  • Za vsako orodje našteli ime, opis in njegovo shemo. Slednje bomo kmalu uporabili za klice orodij.

Java

// Ustvarite ponudnika orodja, ki samodejno odkrije MCP orodja
ToolProvider toolProvider = McpToolProvider.builder()
        .mcpClients(List.of(mcpClient))
        .build();

// Ponudnik MCP orodij samodejno upravlja:
// - Seznam razpoložljivih orodij s strežnika MCP
// - Pretvorbo shem MCP orodij v LangChain4j format
// - Upravljanje izvajanja orodij in odzivov

V zgornji kodi smo:

  • Ustvarili McpToolProvider, ki samodejno odkrije in registrira vsa orodja s MCP strežnika
  • Ponudnik orodij interno upravlja pretvorbo med shemami orodij MCP in obliko orodij LangChain4j
  • Ta pristop abstraktno odstrani ročno naštevanje in pretvorbo orodij

Rust

Pridobivanje orodij s MCP strežnika se izvaja z metodoj list_tools. V vaši funkciji main, potem ko nastavite MCP odjemalca, dodajte naslednjo kodo:

// Pridobi seznam orodij MCP
let tools = mcp_client.list_tools(Default::default()).await?;

-3- Pretvorba zmogljivosti strežnika v LLM orodja

Naslednji korak po naštetju zmogljivosti strežnika je njihova pretvorba v obliko, ki jo LLM razume. Ko to naredimo, lahko te zmogljivosti ponudimo kot orodja našemu LLM.

TypeScript

  1. Dodajte naslednjo kodo za pretvorbo odgovora MCP strežnika v obliko orodja, ki jo LLM lahko uporablja:

    openAiToolAdapter(tool: {
        name: string;
        description?: string;
        input_schema: any;
        }) {
        // Ustvari zod shemo na podlagi input_schema
        const schema = z.object(tool.input_schema);
    
        return {
            type: "function" as const, // Izrecno nastavi tip na "function"
            function: {
            name: tool.name,
            description: tool.description,
            parameters: {
            type: "object",
            properties: tool.input_schema.properties,
            required: tool.input_schema.required,
            },
            },
        };
    }
    
    

    Zgornja koda vzame odgovor MCP strežnika in ga pretvori v definicijo orodja, ki jo LLM razume.

  2. Posodobimo metodo run, da našteje zmogljivosti strežnika:

    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,
            });
        });
    }
    

    V zgornji kodi smo posodobili metodo run tako, da mapira skozi rezultat in za vsak vnos kliče openAiToolAdapter.

Python

  1. Najprej ustvarimo naslednjo funkcijo za pretvorbo

    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
    

    V funkciji convert_to_llm_tools zgoraj vzamemo MCP odgovor orodja in ga pretvorimo v obliko, ki jo LLM razume.

  2. Nato posodobimo naš odjemalec, da uporabimo to funkcijo tako:

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

    Tukaj dodajamo klic convert_to_llm_tool, da pretvorimo MCP odgovor orodja v nekaj, kar lahko kasneje posredujemo LLM.

.NET

  1. Dodajmo kodo za pretvorbo MCP odgovora orodja v nekaj, kar LLM razume
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;
}

V zgornji kodi smo:

  • Ustvarili funkcijo ConvertFrom, ki prejme ime, opis in vnosno shemo.
  • Definirali funkcionalnost, ki ustvari FunctionDefinition, ki se posreduje ChatCompletionsDefinition. Slednje LLM razume.
  1. Posodobimo nekaj obstoječe kode, da izkoristi zgornjo funkcijo:

    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

// Ustvari vmesnik za bota za naravno jezikovno interakcijo
public interface Bot {
    String chat(String prompt);
}

// Konfigurirajte AI storitev z orodji LLM in MCP
Bot bot = AiServices.builder(Bot.class)
        .chatLanguageModel(model)
        .toolProvider(toolProvider)
        .build();

V zgornji kodi smo:

  • Definirali preprost vmesnik Bot za interakcije v naravnem jeziku
  • Uporabili LangChain4j AiServices, da samodejno poveže LLM s MCP ponudnikom orodij
  • Okvir samodejno upravlja pretvorbo sheme orodij in klice funkcij v ozadju
  • Ta pristop odpravlja ročno pretvorbo orodij LangChain4j upravlja vso zapletenost pretvorbe MCP orodij v format združljiv z LLM

Rust

Za pretvorbo MCP odgovora orodja v obliko, ki jo LLM razume, bomo dodali pomožno funkcijo, ki formatira seznam orodij. Dodajte naslednjo kodo v vašo datoteko main.rs pod funkcijo main. Ta bo klicana pri zahtevah k 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)
}

Odlično, zdaj smo pripravljeni za obravnavo uporabniških zahtev.

-4- Obravnava uporabniškega poziva

V tem delu kode bomo obravnavali uporabniške zahteve.

TypeScript

  1. Dodajte metodo, ki bo uporabljena za klic našega 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. Pokliči orodje strežnika
        const toolResult = await this.client.callTool({
            name: toolName,
            arguments: JSON.parse(args),
        });
    
        console.log("Tool result: ", toolResult);
    
        // 3. Naredi nekaj z rezultatom
        // NAREDITI
    
        }
    }
    

    V zgornji kodi smo:

    • Dodali metodo callTools.

    • Metoda prejme odgovor LLM in preveri, katera orodja so bila klicana, če sploh:

      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)}`);
      
      // pokliči orodje
      }
      
    • Pokliče orodje, če LLM nakaže, da naj bo poklicano:

      // 2. Pokliči orodje strežnika
      const toolResult = await this.client.callTool({
          name: toolName,
          arguments: JSON.parse(args),
      });
      
      console.log("Tool result: ", toolResult);
      
      // 3. Naredite nekaj z rezultatom
      // NAREDI
      
  2. Posodobite metodo run, da vključuje klice na LLM in klic callTools:

    
    // 1. Ustvarite sporočila, ki so vhod za 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. Klicanje LLM
    let response = this.openai.chat.completions.create({
        model: "gpt-4.1-mini",
        max_tokens: 1000,
        messages,
        tools: tools,
    });    
    
    let results: any[] = [];
    
    // 3. Preglejte odgovor LLM, za vsako izbiro preverite, ali vsebuje klice orodij
    (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);
        }
    });
    

Odlično, izpišimo celotno kodo:

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"; // Uvozi zod za validacijo sheme

class MyClient {
    private openai: OpenAI;
    private client: Client;
    constructor(){
        this.openai = new OpenAI({
            baseURL: "https://models.inference.ai.azure.com", // morda bo treba spremeniti URL v prihodnosti: 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;
          }) {
          // Ustvari zod shemo na podlagi input_schema
          const schema = z.object(tool.input_schema);
      
          return {
            type: "function" as const, // Izrecno nastavi tip na "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. Pokliči orodje strežnika
          const toolResult = await this.client.callTool({
            name: toolName,
            arguments: JSON.parse(args),
          });
    
          console.log("Tool result: ", toolResult);
    
          // 3. Naredi nekaj z rezultatom
          // NAREDITI
    
         }
    }

    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. Preglej odgovor LLM, za vsako izbiro preveri, če vsebuje klice orodja
        (await response).choices.map(async (choice: { message: any; }) => {
          const message = choice.message;
          if (message.tool_calls) {
              console.log("Making tool call")
              await this.callTools(message.tool_calls, results);
          }
        });
    }
    
}

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

client.connectToServer(transport);

Python

  1. Dodajmo potrebne uvoze za klic LLM

    # llm
    import os
    from azure.ai.inference import ChatCompletionsClient
    from azure.ai.inference.models import SystemMessage, UserMessage
    from azure.core.credentials import AzureKeyCredential
    import json
    
  2. Dodajmo funkcijo, ki kliče 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,
            # Neobvezni parametri
            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
    

    V zgornji kodi smo:

    • Posredovali funkcije, ki smo jih našli na MCP strežniku in jih pretvorili, k LLM.
    • Nato poklicali LLM s temi funkcijami.
    • Nato pregledali rezultat, da vidimo, katere funkcije je treba poklicati, če sploh.
    • Nazadnje posredujemo seznam funkcij za klic.
  3. Zadnji korak, posodobimo glavno kodo:

    prompt = "Add 2 to 20"
    
    # vprašaj LLM, katera orodja uporabiti, če sploh katera
    functions_to_call = call_llm(prompt, functions)
    
    # pokliči predlagane funkcije
    for f in functions_to_call:
        result = await session.call_tool(f["name"], arguments=f["args"])
        print("TOOLS result: ", result.content)
    

    Tam, to je bil zadnji korak, kjer v zgornji kodi:

    • Kličemo MCP orodje preko call_tool z uporabo funkcije, ki jo je LLM ocenil, da jo je treba poklicati glede na naš poziv.
    • Izpisujemo rezultat klica orodja strežniku MCP.

.NET

  1. Ogled kode za poizvedbo poziva 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;
    
    

    V zgornji kodi smo:

    • Pridobili orodja s MCP strežnika, var tools = await GetMcpTools().
    • Definirali uporabniški poziv userMessage.
    • Sestavili objekt z možnostmi, ki določa model in orodja.
    • Oddali zahtevo LLM.
  2. Zadnji korak, preverimo, če LLM meni, da je treba klicati funkcijo:

    // 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);
    
    }
    

    V zgornji kodi smo:

    • Zanka skozi seznam klicev funkcij.
    • Za vsak klic orodja, razčlenimo ime in argumente ter pokličemo orodje na MCP strežniku preko MCP odjemalca. Na koncu izpišemo rezultate.

Celotna koda:

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 {
    // Izvedite zahteve naravnega jezika, ki samodejno uporabljajo orodja 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();
}

V zgornji kodi smo:

  • Uporabili enostavne pozive v naravnem jeziku za interakcijo z orodji MCP strežnika
  • Okvir LangChain4j samodejno upravlja:
    • Pretvorbo uporabniških pozivov v klice orodij, kadar je to potrebno
    • Klice ustreznih MCP orodij glede na odločitve LLM
    • Upravljanje poteka pogovora med LLM in MCP strežnikom
  • Metoda bot.chat() vrača odgovore v naravnem jeziku, ki lahko vključujejo rezultate izvedb MCP orodij
  • Ta pristop omogoča nemoteno uporabniško izkušnjo, kjer uporabniki ne potrebujejo poznavanja podlage MCP implementacije

Popoln primer kode:

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

Tu se zgodi največ dela. Klicali bomo LLM z začetnim uporabniškim pozivom, nato obdelali odgovor, da vidimo, če je treba poklicati kakšna orodja. Če da, jih bomo poklicali in nadaljevali pogovor z LLM vse dokler ne bo potrebnih več klicev orodij in bomo imeli končni odgovor.

Klicali bomo LLM večkrat, zato definirajmo funkcijo, ki bo obravnavala klic LLM. Dodajte naslednjo funkcijo v 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)
}

Ta funkcija prejme LLM odjemalca, seznam sporočil (vključno z uporabniškim pozivom), orodja iz MCP strežnika in pošlje zahtevo LLM, ki vrne odgovor. Odgovor iz LLM bo vseboval polje choices. Rezultat bomo morali obdelati, da preverimo, ali so prisotni tool_calls. Tako izvemo, da LLM zahteva klic določenega orodja z argumenti. Dodajte naslednjo kodo na dno vaše datoteke main.rs, da definirate funkcijo za upravljanje odgovora 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(());
    };

    // Natisni vsebino, če je na voljo
    if let Some(content) = message.get("content").and_then(|c| c.as_str()) {
        println!("🤖 {}", content);
    }

    // Obravnavaj klice orodij
    if let Some(tool_calls) = message.get("tool_calls").and_then(|tc| tc.as_array()) {
        messages.push(message.clone()); // Dodaj sporočilo pomočnika

        // Izvedi vsak klic orodja
        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?;

            // Dodaj rezultat orodja k sporočilom
            messages.push(json!({
                "role": "tool",
                "tool_call_id": tool_id,
                "content": serde_json::to_string_pretty(&result)?
            }));
        }

        // Nadaljuj pogovor z rezultati orodij
        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(())
}

Če so prisotni tool_calls, izlušči informacije o orodju, pokliče MCP strežnik z zahtevo orodja in doda rezultate v sporočila pogovora. Nato nadaljuje pogovor z LLM in sporočila se posodobijo z odgovorom asistenta in rezultati klica orodja.

Da izvlečemo informacije o klicu orodja, ki jih LLM vrača za MCP klice, bomo dodali še eno pomožno funkcijo, ki izlušči vse potrebno za izvedbo klica. Dodajte naslednjo kodo na dno vaše datoteke 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))
}

S celotno postavitvijo lahko zdaj obdelamo začetni uporabniški poziv in pokličemo LLM. Posodobite svojo funkcijo main, da vključite naslednjo kodo:

// Pogovor LLM z orodjnimi klici
let response = call_llm(&openai_client, &messages, &tools).await?;
process_llm_response(
    &response,
    &mcp_client,
    &openai_client,
    &tools,
    &mut messages,
)
.await?;

To bo poslalo zahtevo LLM z začetnim uporabniškim pozivom, ki prosi za vsoto dveh števil, in obdelalo odgovor za dinamično upravljanje klicev orodij.

Super, uspelo vam je!

Naloga

Vzemite kodo iz vaje in zgradite strežnik z nekaj dodatnimi orodji. Nato ustvarite odjemalca z LLM, kot v vaji, in ga preizkusite z različnimi pozivi, da se prepričate, da se vsa vaša strežniška orodja kličejo dinamično. Tak način izdelave odjemalca pomeni, da bo končni uporabnik imel odlično uporabniško izkušnjo, saj lahko uporablja pozive namesto natančnih ukazov odjemalca in ne opazi, da se kliče MCP strežnik.

Rešitev

Rešitev

Ključne ugotovitve

  • Dodajanje LLM v vaš odjemalec omogoča boljši način interakcije uporabnikov s MCP strežniki.
  • Potrebno je pretvoriti odgovor MCP strežnika v nekaj, kar LLM lahko razume.

Primeri

Dodatni viri

Kaj sledi


Omejitev odgovornosti: Ta dokument je bil preveden z uporabo AI prevajalske storitve Co-op Translator. Čeprav si prizadevamo za natančnost, vas prosimo, da upoštevate, da avtomatizirani prevodi lahko vsebujejo napake ali netočnosti. Izvirni dokument v njegovem izvirnem jeziku je treba obravnavati kot avtoritativni vir. Za kritične informacije je priporočljiv strokovni človeški prevod. Ne odgovarjamo za morebitna nesporazume ali napačne interpretacije, ki izhajajo iz uporabe tega prevoda.