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

45 KiB
Raw Permalink Blame History

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:

  1. Luua ühendus serveriga.

  2. Loetleda võimekused, viibad, ressursid ja tööriistad ning salvestada nende skeemid.

  3. Lisada LLM ja anda salvestatud võimekused ning nende skeemid vormingus, mida LLM mõistab.

  4. 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 client ja openai, mis aitavad meil hallata klienti ja suhelda vastavalt LLM-iga.
  • Konfigureerinud oma LLM eksemplari kasutama GitHub Models, määrates baseUrl inference 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-openai crate 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_KEY oma 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

  1. 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.

  2. Uuendame nüüd run meetodit, 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 run meetodit, kasutades tulemuse läbimist ja iga üksuse jaoks kutsudes openAiToolAdapter.

Python

  1. 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_schema
    

    Funktsioonis convert_to_llm_tools teisendame MCP tööriistavastuse tegemaks seda LLM-i arusaadavaks.

  2. 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_tool kutse, mis teisendab MCP tööriista vastuse hilisemaks LLM sisendiks.

.NET

  1. 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 edasi ChatCompletionsDefinition-ile. Viimane on midagi, mida LLM mõistab.
  1. 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 Bot liidese 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

  1. 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
      
  2. Uuendame run meetodit, lisades LLM-ile kutsumise ja callTools meetodi 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

  1. 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
    
  2. 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_call
    

    Eelnevas 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.
  3. 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_tool abil, kasutades LLM-i poolt viipa põhjal soovitatud funktsiooni.
    • Trükib välja tööriista kõne tulemuse MCP serverisse.

.NET

  1. 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.
  2. 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

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

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.