Hoàn thành bài "Nền tảng và kiến trúc MCP" trước để mở khoá bài này.Đến bài trước

Mục tiêu và kiến trúc

Trong bài học này, bạn sẽ xây dựng một MCP server cục bộ cung cấp một khả năng có cấu trúc: tính báo giá vận chuyển dựa trên trọng lượng kiện hàng, khu vực đích và cấp độ dịch vụ. Khả năng này được triển khai dưới dạng MCP tool vì client chủ động gọi tool bằng các tham số và nhận về kết quả. Khi hoàn thành, một client tương thích với MCP có thể phát hiện tool, kiểm tra input schema, xác thực một lần gọi và nhận cả văn bản dễ đọc đối với con người lẫn dữ liệu có cấu trúc.

Server sẽ sử dụng TypeScript, Node.js 20 trở lên và package MCP TypeScript server chính thức. Server sẽ giao tiếp thông qua transport stdio. Trong mô hình này, MCP client khởi chạy server dưới dạng một tiến trình con và trao đổi các thông điệp JSON-RPC qua standard input và standard output. Transport này phù hợp với các ứng dụng AI cục bộ, host dòng lệnh và công cụ phát triển. Một server dùng chung được triển khai từ xa thông thường sẽ sử dụng Streamable HTTP thay thế.

Luồng xử lý request rất đơn giản. Client khởi chạy tiến trình, hoàn tất initialization handshake của MCP, liệt kê các tool hiện có và gọi tool được chọn. SDK xác thực các tham số được cung cấp trước khi chạy handler của bạn. Handler thực hiện phép tính nghiệp vụ và trả về một MCP result. Sự phân tách này rất quan trọng: MCP định nghĩa việc discovery và giao tiếp, còn code ứng dụng của bạn chịu trách nhiệm quản lý các quy tắc vận chuyển.

sơ đồ luồng mô tả một ứng dụng AI chứa MCP client, kết nối JSON-RPC qua stdio, MCP server, bước xác thực input schema, shipping quote handler và structured result được trả về ứng dụng

Thiết lập dự án

Tạo một dự án mới và cài đặt server SDK, Zod, TypeScript cùng trình chạy tsx. Package được cấu hình dưới dạng ES module vì SDK hiện tại sử dụng các import ES module. Trình chạy tsx thực thi trực tiếp entry point TypeScript, giúp bài thực hành đầu tiên này tập trung vào MCP thay vì các công cụ build.

bash
mkdir shipping-mcp-server
cd shipping-mcp-server
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/server zod
npm install --save-dev typescript tsx @types/node
mkdir src

Thêm các script vào package.json để lệnh khởi chạy luôn ổn định. Server script chạy tiến trình stdio, còn inspect script khởi động MCP Inspector và chỉ cho công cụ này cách khởi chạy chính tiến trình đó.

json
{
"name": "shipping-mcp-server",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "tsx src/index.ts",
"inspect": "npx @modelcontextprotocol/inspector npx tsx src/index.ts"
},
"dependencies": {
"@modelcontextprotocol/server": "^2.0.0",
"zod": "^4.0.0"
},
"devDependencies": {
"@types/node": "^24.0.0",
"tsx": "^4.0.0",
"typescript": "^6.0.0"
}
}

Các phiên bản dependency được phân giải chính xác có thể mới hơn khi bạn cài đặt. Điểm quan trọng về kiến trúc là các dự án server mới sử dụng dedicated server package thay vì monolithic SDK package cũ. Nếu cài đặt package thất bại, hãy xác nhận rằng Node.js và npm khả dụng, đồng thời phiên bản Node.js của bạn ít nhất là 20.

Triển khai máy chủ

Tạo `src/index.ts` với phần triển khai sau. Hàm `createServer` tạo một MCP server mới và đăng ký tool `calculate-shipping`. Schema đầu vào chấp nhận trọng lượng kiện hàng dương, một trong ba khu vực điểm đến và một trong hai cấp độ dịch vụ. Schema đầu ra mô tả kết quả có cấu trúc mà client có thể sử dụng mà không cần phân tích văn bản diễn giải.

typescript
import { McpServer } from '@modelcontextprotocol/server';
import { serveStdio } from '@modelcontextprotocol/server/stdio';
import * as z from 'zod/v4';
const inputSchema = z.object({
weightKg: z.number().positive().max(50),
zone: z.enum(['domestic', 'regional', 'international']),
service: z.enum(['standard', 'express'])
});
const outputSchema = z.object({
currency: z.literal('USD'),
baseCost: z.number(),
serviceSurcharge: z.number(),
totalCost: z.number(),
estimatedDays: z.number().int()
});
function createServer(): McpServer {
const server = new McpServer({
name: 'shipping-quote-server',
version: '1.0.0'
});
server.registerTool(
'calculate-shipping',
{
title: 'Calculate Shipping Quote',
description: 'Calculate a shipping price and delivery estimate for a package',
inputSchema,
outputSchema
},
async ({ weightKg, zone, service }) => {
const ratePerKg = {
domestic: 2.5,
regional: 5,
international: 9
}[zone];
const standardDays = {
domestic: 4,
regional: 7,
international: 14
}[zone];
const baseCost = Number((5 + weightKg * ratePerKg).toFixed(2));
const serviceSurcharge = service === 'express'
? Number((baseCost * 0.6).toFixed(2))
: 0;
const totalCost = Number((baseCost + serviceSurcharge).toFixed(2));
const estimatedDays = service === 'express'
? Math.max(1, Math.ceil(standardDays / 2))
: standardDays;
const result = {
currency: 'USD' as const,
baseCost,
serviceSurcharge,
totalCost,
estimatedDays
};
return {
content: [
{
type: 'text',
text: `Shipping costs USD ${totalCost.toFixed(2)} and takes approximately ${estimatedDays} days.`
}
],
structuredContent: result
};
}
);
return server;
}
void serveStdio(createServer);
console.error('Shipping MCP server is running on stdio');

Phần mô tả tool cần nêu rõ capability này thực hiện gì, vì một AI agent sử dụng tên, mô tả và JSON Schema được sinh ra khi quyết định có gọi tool đó hay không. Schema cũng là một ranh giới thực thi. Trọng lượng âm, khu vực không được hỗ trợ hoặc dịch vụ không xác định sẽ bị từ chối trước khi handler tính toán được thực thi.

Tìm hiểu kết quả

Handler trả về hai dạng biểu diễn của cùng một kết quả. Mảng `content` chứa một khối văn bản mà model hoặc giao diện người dùng có thể đọc trực tiếp. Giá trị `structuredContent` chứa các trường có cấu trúc, dễ dự đoán, dành cho các ứng dụng cần hiển thị giá, so sánh các lựa chọn, lưu kết quả hoặc truyền kết quả cho một tool khác. Vì một output schema đã được đăng ký, giá trị có cấu trúc phải khớp với schema đó.

Với đầu vào gồm trọng lượng 3 kilogram, khu vực regional và dịch vụ express, chi phí cơ bản là 20 dollar. Phụ phí express là 12 dollar, tạo thành tổng chi phí 32 dollar và thời gian giao hàng ước tính là bốn ngày. Một kết quả thành công có cấu trúc logic như sau.

json
{
"content": [
{
"type": "text",
"text": "Shipping costs USD 32.00 and takes approximately 4 days."
}
],
"structuredContent": {
"currency": "USD",
"baseCost": 20,
"serviceSurcharge": 12,
"totalCost": 32,
"estimatedDays": 4
}
}

Output có cấu trúc phù hợp hơn so với việc nhúng toàn bộ thông tin vào một câu. Client không nên cần dùng regular expression để lấy lại tổng giá hoặc thời gian giao hàng ước tính. Văn bản vẫn hữu ích cho ngữ cảnh của model, trong khi object có cấu trúc cung cấp một interface ứng dụng ổn định.

Chạy và kiểm thử

Khởi động server trực tiếp để xác minh rằng tiến trình tải mà không gặp lỗi cú pháp hoặc lỗi import. Server sẽ in thông báo trạng thái rồi tiếp tục chờ vì một stdio server cần MCP client gửi request. Dừng server bằng lệnh ngắt trong terminal sau khi xác nhận rằng tiến trình vẫn đang hoạt động.

bash
npm start

Tiếp theo, khởi chạy MCP Inspector. Inspector khởi động server của bạn, thực hiện quá trình bắt tay kết nối và cung cấp giao diện trình duyệt để liệt kê và gọi các tool. Kết nối với server, mở chế độ xem Tools, chọn `calculate-shipping` và gửi các giá trị hợp lệ như 3, regional và express.

bash
npm run inspect

Hãy xác minh ba hành vi thay vì chỉ kiểm tra trường hợp hoạt động bình thường. Trước tiên, xác nhận rằng tool xuất hiện với phần mô tả và các trường đầu vào như mong đợi. Tiếp theo, gọi tool bằng dữ liệu hợp lệ và đối chiếu kết quả có cấu trúc với các quy tắc tính toán. Cuối cùng, thử một trọng lượng không hợp lệ, chẳng hạn -2, hoặc một khu vực không được hỗ trợ. Request không hợp lệ phải bị lỗi khi kiểm tra schema thay vì đi tới handler.

chuỗi kiểm thử cho thấy quá trình phát hiện tool, một lần gọi `calculate-shipping` hợp lệ với `weightKg` bằng 3, `zone` là regional, `service` là express, kết quả có cấu trúc là 32 USD và một lần gọi với trọng lượng âm bị dừng do kiểm tra schema

Các lỗi thường gặp và bước tiếp theo

Lỗi stdio nghiêm trọng nhất là ghi log của ứng dụng vào standard output. Standard output được dành riêng cho các thông báo giao thức, vì vậy một dòng log thông thường có thể làm hỏng luồng JSON-RPC và khiến client bị ngắt kết nối. Hãy sử dụng standard error cho thông tin chẩn đoán, như minh họa trong thông báo trạng thái. Hạn chế này áp dụng cho cả mã của bạn và các thư viện có thể in ra nội dung ngoài dự kiến.

Nếu Inspector không thể khởi động process, hãy chạy trực tiếp lệnh khởi động từ thư mục gốc của project. Lỗi import thường cho thấy tên package không chính xác, phiên bản Node.js cũ hoặc thiếu cấu hình ES module. Nếu tool hiển thị nhưng các lệnh gọi thất bại, hãy kiểm tra tên và kiểu của các đối số đã gửi. Schema yêu cầu weight là một số và các giá trị enum phải khớp chính xác; một chuỗi số không tự động tương đương với một số.

Một server khởi động thành công không tự động trở thành một MCP server hữu ích. Tool của server cần có mô tả chính xác, input được giới hạn, hành vi xác định và kết quả dễ được cả model lẫn ứng dụng tiếp nhận. Đến đây, bạn đã có đầy đủ quy trình local: tạo server, đăng ký một tool có cấu trúc, cung cấp tool qua stdio và kiểm thử tool bằng một MCP client thực tế. Bước thực tế tiếp theo là kết nối cùng lệnh khởi chạy đó với một AI host hoặc thay thế phép tính local bằng database hay dịch vụ bên ngoài, đồng thời duy trì tool contract.

Luyện tập sau buổi học

Bài kiểm tra cuối bài

Ôn lại kiến thức, thử sức và nhận góp ý cho bài làm.

0/1
Đã trả lời · 0/1