Skip to content
Lesson 3/11 · MCP cho người mới bắt đầu (Microsoft)

Client có LLM

Thêm LLM vào MCP client để người dùng tương tác bằng ngôn ngữ tự nhiên, chuyển tool của server sang định dạng LLM hiểu và gọi tool theo quyết định của LLM.

This content is currently available in Vietnamese only.

Beginner@shine190426 min read

Tới giờ bạn đã biết tạo server và client. Client gọi tường minh tới server để liệt kê tool, resource và prompt. Nhưng cách này không thực tế lắm. Người dùng sống trong thời đại agent và muốn dùng prompt, nói chuyện với một LLM. Họ không quan tâm bạn có dùng MCP để chứa các khả năng (capability) hay không; họ chỉ muốn tương tác bằng ngôn ngữ tự nhiên. Vậy giải quyết thế nào? Lời giải là thêm một LLM vào client.

Tổng quan

Bài này tập trung vào việc thêm LLM vào client và cho thấy điều đó mang lại trải nghiệm tốt hơn nhiều cho người dùng.

Mục tiêu học tập

Sau bài này, bạn sẽ có thể:

  • Tạo client có LLM.
  • Tương tác liền mạch với MCP server qua LLM.
  • Mang lại trải nghiệm tốt hơn cho người dùng cuối ở phía client.

Cách tiếp cận

Hãy hiểu cách tiếp cận cần dùng. Thêm LLM nghe đơn giản, nhưng thực tế ta làm thế nào?

Đây là cách client tương tác với server:

  1. Thiết lập kết nối với server.
  2. Liệt kê capability, prompt, resource và tool, lưu lại schema của chúng.
  3. Thêm LLM và truyền các capability đã lưu cùng schema theo định dạng LLM hiểu.
  4. Xử lý prompt của người dùng bằng cách gửi nó tới LLM cùng các tool client đã liệt kê.

Tốt, giờ đã hiểu tổng quan, hãy thử trong bài thực hành dưới đây.

Bài thực hành: tạo client có LLM

Cấu hình Microsoft Foundry

GitHub Models đã ngừng hoạt động từ ngày 30/7/2026. Hãy tạo một resource Microsoft Foundry, triển khai (deploy) một model đang hoạt động như gpt-5.1, rồi đặt các biến môi trường:

bash
export AZURE_OPENAI_ENDPOINT="https://<resource-name>.openai.azure.com"
export AZURE_OPENAI_API_KEY="<api-key>"
export AZURE_OPENAI_DEPLOYMENT="gpt-5.1"

Dùng tên deployment trong lời gọi API; nó có thể khác tên model bên dưới. Hãy xem lịch ngừng hỗ trợ model của Microsoft Foundry trước khi chọn model.

Bước 1: kết nối tới server

Trước hết hãy tạo client:

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"; // Import zod for schema validation

class MCPClient {
    private openai: OpenAI;
    private client: Client;
    constructor(){
        const endpoint = process.env.AZURE_OPENAI_ENDPOINT;
        const apiKey = process.env.AZURE_OPENAI_API_KEY;
        if (!endpoint || !apiKey) {
            throw new Error("AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_API_KEY must be set");
        }

        this.openai = new OpenAI({
            baseURL: `${endpoint.replace(/\/$/, "")}/openai/v1/`,
            apiKey,
        });

        this.client = new Client(
            {
                name: "example-client",
                version: "1.0.0"
            },
            {
                capabilities: {}
            }
            );    
    }
}

Trong code trên, chúng ta:

  • Import các thư viện cần thiết.
  • Tạo một lớp có hai thành viên, client và openai, lần lượt để quản lý client và tương tác với LLM.
  • Cấu hình client OpenAI dùng endpoint v1 và API key của Microsoft Foundry.

Tốt, bước tiếp theo là liệt kê capability của server.

Bước 2: liệt kê capability của server

Giờ ta kết nối tới server và hỏi các capability của nó:

Trong cùng lớp, thêm các phương thức sau:

typescript
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");

    // listing tools
    const toolsResult = await this.client.listTools();
}

Trong code trên, chúng ta:

  • Thêm code kết nối tới server, connectToServer.
  • Tạo phương thức run điều phối luồng ứng dụng. Tới giờ nó chỉ liệt kê tool, sẽ thêm phần khác ngay sau đây.

Bước 3: chuyển capability của server thành tool cho LLM

Sau khi liệt kê capability của server, bước tiếp theo là chuyển chúng sang định dạng LLM hiểu. Khi đó, ta có thể cung cấp các capability này làm tool cho LLM.

  1. Thêm code sau để chuyển phản hồi từ MCP server sang định dạng tool LLM dùng được:

    typescript
    openAiToolAdapter(tool: {
        name: string;
        description?: string;
        input_schema: any;
        }) {
        // Create a zod schema based on the input_schema
        const schema = z.object(tool.input_schema);
    
        return {
            type: "function" as const, // Explicitly set type to "function"
            function: {
            name: tool.name,
            description: tool.description,
            parameters: {
            type: "object",
            properties: tool.input_schema.properties,
            required: tool.input_schema.required,
            },
            },
        };
    }
    
    

    Code trên nhận phản hồi từ MCP server và chuyển thành định nghĩa tool mà LLM hiểu.

  2. Cập nhật phương thức run để liệt kê capability của server:

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

    Ở đây, run duyệt qua kết quả và gọi openAiToolAdapter cho từng phần tử.

Tốt, tiếp theo là xử lý yêu cầu của người dùng.

Bước 4: xử lý prompt của người dùng

Ở phần này, ta xử lý yêu cầu (request) của người dùng.

  1. Thêm một phương thức dùng để gọi tool theo phản hồi của LLM:

    typescript
    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. Call the server's tool 
        const toolResult = await this.client.callTool({
            name: toolName,
            arguments: JSON.parse(args),
        });
    
        console.log("Tool result: ", toolResult);
    
        // 3. Do something with the result
        // TODO  
    
        }
    }
    

    Trong code trên, chúng ta thêm phương thức callTools. Phương thức nhận phản hồi của LLM, kiểm tra xem tool nào được gọi (nếu có), và gọi tool trên server nếu LLM cho biết cần gọi.

  2. Cập nhật phương thức run để gọi LLM và gọi callTools:

    typescript
    
    // 1. Create messages that's input for the 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. Calling the LLM
    let response = this.openai.chat.completions.create({
        model: process.env.AZURE_OPENAI_DEPLOYMENT ?? "gpt-5.1",
        max_completion_tokens: 1000,
        messages,
        tools: tools,
    });    
    
    let results: any[] = [];
    
    // 3. Go through the LLM response,for each choice, check if it has tool calls 
    (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);
        }
    });
    

Đây là code đầy đủ:

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"; // Import zod for schema validation

class MyClient {
    private openai: OpenAI;
    private client: Client;
    constructor(){
        const endpoint = process.env.AZURE_OPENAI_ENDPOINT;
        const apiKey = process.env.AZURE_OPENAI_API_KEY;
        if (!endpoint || !apiKey) {
            throw new Error("AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_API_KEY must be set");
        }

        this.openai = new OpenAI({
            baseURL: `${endpoint.replace(/\/$/, "")}/openai/v1/`,
            apiKey,
        });

        this.client = new Client(
            {
                name: "example-client",
                version: "1.0.0"
            },
            {
                capabilities: {}
            }
            );    
    }

    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;
          }) {
          // Create a zod schema based on the input_schema
          const schema = z.object(tool.input_schema);
      
          return {
            type: "function" as const, // Explicitly set type to "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. Call the server's tool 
          const toolResult = await this.client.callTool({
            name: toolName,
            arguments: JSON.parse(args),
          });
    
          console.log("Tool result: ", toolResult);
    
          // 3. Do something with the result
          // 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: process.env.AZURE_OPENAI_DEPLOYMENT ?? "gpt-5.1",
            max_completion_tokens: 1000,
            messages,
            tools: tools,
        });    

        let results: any[] = [];
    
        // 3. Go through the LLM response, for each choice, check if it has tool calls 
        (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);

Tuyệt, bạn đã làm được!

Bài tập

Lấy code từ bài thực hành và bổ sung thêm tool cho server. Sau đó tạo một client có LLM như trong bài, thử với nhiều prompt khác nhau để đảm bảo mọi tool của server đều được gọi động. Cách xây client này mang lại trải nghiệm tuyệt vời cho người dùng cuối: họ dùng prompt thay vì lệnh client chính xác, và không cần biết có MCP server nào đang được gọi.

Lời giải: solution.

Điểm chính

  • Thêm LLM vào client mang lại cách tốt hơn để người dùng tương tác với MCP server.
  • Bạn cần chuyển phản hồi của MCP server sang thứ LLM hiểu được.

Mẫu code

Bài tiếp theo: Dùng MCP server trong Visual Studio Code.

Nguồn