Hướng dẫn tự build MCP Server bằng TypeScript: Kết nối AI Assistant với hệ thống nội bộ

Development tutorial - IT technology blog
Development tutorial - IT technology blog

Vấn đề: LLM mạnh nhưng đang bị “mù” dữ liệu nội bộ

Dùng Claude hay ChatGPT xử lý tác vụ code hàng ngày rất tiện. Thế nhưng, khi đưa vào quy trình làm việc thực tế của team, các mô hình ngôn ngữ lớn (LLM) lập tức chạm trần vì bị cô lập hoàn toàn. AI không thể tự chui vào PostgreSQL nội bộ để kiểm tra đơn hàng lỗi. Nó không đọc được file log 500MB trên cụm máy chủ staging, cũng chẳng thể gọi API để tạo ticket Jira tự động.

Trước đây, muốn AI làm được việc đó, lập trình viên buộc phải tự viết Function Calling riêng cho từng nền tảng. Code tích hợp cho OpenAI không thể tái sử dụng trên Claude. Bạn đổi sang Cursor hay Windsurf thì lại phải đập đi viết lại một mớ code glue khác. Dự án càng lớn, chi phí bảo trì càng tăng vọt. Sự phân mảnh này chính là lý do Anthropic tung ra chuẩn mở Model Context Protocol (MCP).

Model Context Protocol (MCP) là gì?

Hiểu đơn giản, MCP giống như một cổng USB-C tiêu chuẩn dành cho các ứng dụng AI. Trước đây mỗi hãng thiết bị dùng một loại chân sạc riêng, còn giờ đây tất cả dùng chung một chuẩn.

Giao thức này hoạt động tương tự Language Server Protocol (LSP) trong thế giới IDE. Thay vì mỗi trình soạn thảo phải tự viết lại parser cho từng ngôn ngữ, LSP chuẩn hóa việc phân tích cú pháp. Tương tự, MCP giúp bất kỳ AI client nào cũng có thể cắm vào các nguồn dữ liệu bên ngoài qua cùng một giao diện chuẩn.

Mô hình kiến trúc Client – Server

Hệ thống MCP vận hành theo mô hình Client – Server quen thuộc:

  • MCP Host / Client: Ứng dụng AI mà bạn tương tác hàng ngày (Claude Desktop, Cursor, Zed, hay các AI agent tự build). Client quản lý ngữ cảnh trò chuyện và quyết định thời điểm cần gọi công cụ bên ngoài.
  • MCP Server: Tiến trình độc lập do bạn hoặc cộng đồng phát triển. Server này đóng vai trò gateway, phơi bày dữ liệu hoặc các hàm xử lý an toàn ra bên ngoài.
  • Kênh truyền thông (Transports): Client và Server bắt tay nhau bằng giao thức JSON-RPC 2.0. Hai hình thức phổ biến nhất gồm: stdio (truyền nhận qua luồng Standard In/Out, tối ưu cho tiến trình chạy local) và SSE / HTTP Stream (phù hợp cho server từ xa qua mạng).

3 tài nguyên cốt lõi mà MCP Server cung cấp

  1. Tools: Các hàm thực thi có thể gây ra side-effect (gọi REST API, chạy câu lệnh SQL, kích hoạt build). AI có thể chủ động đề xuất chạy tool kèm tham số cụ thể, nhưng quyền bấm nút duyệt cuối cùng vẫn nằm ở người dùng.
  2. Resources: Nguồn dữ liệu tĩnh hoặc động dạng file/URI (file cấu hình, log hệ thống, schema database). Client chủ động đọc nội dung này để đưa vào ngữ cảnh (context window) của prompt.
  3. Prompts: Các template câu lệnh dựng sẵn kèm tham số. Thành phần này giúp chuẩn hóa quy trình prompt engineering cho cả team (ví dụ: template review pull request, template điều tra mã lỗi).

Thực hành: Xây dựng MCP Server đầu tiên bằng TypeScript

Chúng ta sẽ cùng dựng một server local chạy bằng Node.js và TypeScript. Server này cung cấp 2 công cụ thực tế: một tool kiểm tra tài nguyên phần cứng máy chủ (`get_system_metrics`) và một tool tính toán chiết khấu bán hàng (`calculate_discount`).

Bước 1: Khởi tạo project TypeScript

Mở terminal và tạo một thư mục mới:

mkdir my-mcp-server
cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx

Tạo file tsconfig.json để cấu hình module:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"]
}

Mở package.json, chuyển sang chuẩn ES Module và thêm các script tiện ích:

{
  "name": "my-mcp-server",
  "version": "1.0.0",
  "type": "module",
  "bin": {
    "my-mcp-server": "./dist/index.js"
  },
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "tsx src/index.ts"
  }
}

Bước 2: Viết mã nguồn MCP Server

Tạo file src/index.ts. Đoạn code dưới đây khởi tạo một server kết nối qua transport stdio chuẩn:

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import os from "os";

// Khởi tạo MCP Server
const server = new Server(
  {
    name: "internal-system-tools",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

// 1. Khai báo danh sách Tools để AI Client nhận diện schema
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "get_system_metrics",
        description: "Lấy thông số phần cứng hiện tại của máy chủ (RAM còn trống, tổng dung lượng RAM, uptime)",
        inputSchema: {
          type: "object",
          properties: {},
        },
      },
      {
        name: "calculate_discount",
        description: "Tính giá sau chiết khấu cho đơn hàng dựa trên % giảm giá",
        inputSchema: {
          type: "object",
          properties: {
            price: { type: "number", description: "Giá gốc sản phẩm (VND hoặc USD)" },
            discountPercent: { type: "number", description: "Phần trăm giảm giá (từ 0 đến 100)" },
          },
          required: ["price", "discountPercent"],
        },
      },
    ],
  };
});

// 2. Định tuyến xử lý khi Client gửi yêu cầu gọi Tool
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  if (name === "get_system_metrics") {
    const totalMem = (os.totalmem() / (1024 ** 3)).toFixed(2);
    const freeMem = (os.freemem() / (1024 ** 3)).toFixed(2);
    const uptimeHours = (os.uptime() / 3600).toFixed(1);

    return {
      content: [
        {
          type: "text",
          text: JSON.stringify({
            platform: os.platform(),
            arch: os.arch(),
            totalMemoryGB: totalMem,
            freeMemoryGB: freeMem,
            uptimeHours: uptimeHours,
          }, null, 2),
        },
      ],
    };
  }

  if (name === "calculate_discount") {
    const { price, discountPercent } = args as { price: number; discountPercent: number };
    
    if (discountPercent < 0 || discountPercent > 100) {
      throw new Error("Tỷ lệ giảm giá phải nằm trong khoảng từ 0 đến 100.");
    }

    const finalPrice = price - (price * discountPercent) / 100;
    
    return {
      content: [
        {
          type: "text",
          text: `Giá gốc: ${price.toLocaleString()} | Giảm: ${discountPercent}% | Giá sau giảm: ${finalPrice.toLocaleString()}`,
        },
      ],
    };
  }

  throw new Error(`Công cụ không tồn tại: ${name}`);
});

// 3. Khởi động kết nối Stdio
async function run() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("[INFO] MCP Server đã khởi động thành công trên kênh stdio");
}

run().catch((error) => {
  console.error("[FATAL] Khởi động server thất bại:", error);
  process.exit(1);
});

Lưu ý chí mạng khi debug: Với transport stdio, toàn bộ gói tin JSON-RPC truyền qua luồng stdout. Nếu bạn dùng console.log() để in log linh tinh, chuỗi text đó sẽ chèn thẳng vào giữa gói tin JSON, khiến client parse lỗi ngay lập tức. Hãy luôn dùng console.error() khi cần in log gỡ lỗi.

Khi làm việc với các payload JSON-RPC phức tạp, bạn có thể tận dụng các tiện ích định dạng online như JSON Formatter trên toolcraft.app để kiểm tra cấu trúc schema hay validate dữ liệu nhanh mà không cần mở tab mới trong IDE.

Bước 3: Biên dịch dự án

Chạy lệnh build để TypeScript biên dịch mã nguồn sang JavaScript vào thư mục dist:

npm run build

Bước 4: Cấu hình trên Claude Desktop

Mở file cấu hình của Claude Desktop trên máy bạn:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
  • Linux: ~/.config/Claude/claude_desktop_config.json

Thêm server vừa tạo vào mục mcpServers (nhớ thay đúng đường dẫn tuyệt đối dẫn đến thư mục dự án của bạn):

{
  "mcpServers": {
    "my-system-tools": {
      "command": "node",
      "args": ["/Users/username/my-mcp-server/dist/index.js"]
    }
  }
}

Tắt và mở lại Claude Desktop. Nhìn xuống góc phải khung chat, bạn sẽ thấy icon chiếc búa đại diện cho Tools đã sáng lên. Giờ bạn chỉ cần gõ: “Kiểm tra giúp tôi xem RAM máy còn trống bao nhiêu GB?”. Claude sẽ tự động kích hoạt get_system_metrics, đọc dữ liệu JSON trả về từ server local và trả lời ngay trong vài giây.

Lời kết

Mô hình MCP giải quyết bài toán cốt lõi: bảo mật dữ liệu. Thay vì đẩy cả database lên đám mây của OpenAI hay Anthropic, bạn chỉ mở đúng những endpoint cần thiết ngay trên máy nội bộ. Chỉ cần viết một lần bằng TypeScript, server của bạn có thể cắm vào Claude Desktop, Cursor hay bất kỳ AI client nào hỗ trợ giao thức MCP mà không tốn công chỉnh sửa.

Share: