Hướng Dẫn Xây Dựng MCP (Model Context Protocol) Server: Kết Nối Database Trực Tiếp Với AI Editor | DevPrompt Lab
Từng bước xây dựng Server chuẩn Model Context Protocol bằng TypeScript để AI Editor (Claude/Cursor) có thể truy vấn database an toàn.
Hướng Dẫn Xây Dựng MCP (Model Context Protocol) Server: Kết Nối Database Trực Tiếp Với AI Editor
**Model Context Protocol (MCP)** do Anthropic khởi xướng đang nhanh chóng trở thành tiêu chuẩn mở (Open Standard) để kết nối các mô hình AI với thế giới dữ liệu bên ngoài. Thay vì phải copy-paste schema database hoặc logs thủ công vào cửa sổ chat, MCP cho phép AI Editor của bạn tương tác an toàn với database, Git repo, Slack hoặc API nội bộ thông qua giao thức chuẩn hóa.
---
1. MCP Hoạt Động Như Thế Nào?
Giao thức MCP hoạt động theo mô hình Client-Server:
- **MCP Host / Client**: Ứng dụng AI Editor như Claude Desktop, Cursor, hoặc Windsurf.
- **MCP Protocol**: Chuẩn giao tiếp hai chiều dựa trên JSON-RPC 2.0 truyền qua `stdio` (Standard I/O) hoặc `Server-Sent Events (SSE)`.
- **MCP Server**: Chương trình nhẹ cung cấp 3 tài nguyên:
1. **Resources**: Dữ liệu chỉ đọc (ví dụ: schema database, logs).
2. **Tools**: Các hàm AI có thể gọi (ví dụ: chạy truy vấn `SELECT`, kiểm tra disk space).
3. **Prompts**: Các mẫu prompt dựng sẵn có ngữ cảnh.
---
2. Triển Khai PostgreSQL MCP Server Bằng TypeScript
Chúng ta sẽ xây dựng một MCP Server an toàn, chỉ cho phép thực thi các câu lệnh `SELECT` (Read-only) để tránh việc AI vô tình xóa hoặc sửa dữ liệu sản xuất.
Bước 1: Cài đặt thư viện chính thức
npm init -y
npm install @modelcontextprotocol/sdk pg dotenv zod
npm install -D typescript @types/node @types/pg
Bước 2: Viết mã nguồn Server (`src/index.ts`)
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 pg from 'pg';
import dotenv from 'dotenv';
dotenv.config();
const { Pool } = pg;
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: { rejectUnauthorized: false }
});
const server = new Server(
{
name: "postgres-safe-mcp-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
// Khai báo danh sách Tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "get_database_schema",
description: "Lấy danh sách bảng và kiểu dữ liệu các cột trong database",
inputSchema: {
type: "object",
properties: {},
},
},
{
name: "execute_safe_readonly_query",
description: "Thực thi truy vấn SQL chỉ đọc (Chỉ cho phép SELECT)",
inputSchema: {
type: "object",
properties: {
sql: {
type: "string",
description: "Câu lệnh SQL SELECT cần thực thi",
},
},
required: ["sql"],
},
},
],
};
});
// Xử lý khi AI gọi Tool
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === "get_database_schema") {
const result = await pool.query(`
SELECT table_name, column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'public'
ORDER BY table_name, ordinal_position;
`);
return {
content: [{ type: "text", text: JSON.stringify(result.rows, null, 2) }]
};
}
if (name === "execute_safe_readonly_query") {
const sql = String(args?.sql || "").trim();
// Bảo vệ an toàn dữ liệu: Ngăn chặn tuyệt đối mọi lệnh ghi
const forbiddenKeywords = ['INSERT', 'UPDATE', 'DELETE', 'DROP', 'ALTER', 'TRUNCATE', 'GRANT'];
const hasForbidden = forbiddenKeywords.some(kw => new RegExp(`\\b${kw}\\b`, 'i').test(sql));
if (hasForbidden || !sql.toUpperCase().startsWith('SELECT')) {
throw new Error("BẢO MẬT: Chỉ cho phép câu lệnh SELECT an toàn!");
}
const result = await pool.query(sql);
return {
content: [{ type: "text", text: JSON.stringify(result.rows, null, 2) }]
};
}
throw new Error(`Tool không tồn tại: ${name}`);
});
// Khởi chạy transport qua stdio
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Postgres Safe MCP Server đã sẵn sàng kết nối!");
}
main().catch(console.error);
---
3. Cấu Hình MCP Vào Editor
Thêm cấu hình vào file `claude_desktop_config.json` hoặc cấu hình MCP của Cursor:
{
"mcpServers": {
"postgres": {
"command": "node",
"args": ["/duong/dan/toi/dist/index.js"],
"env": {
"DATABASE_URL": "postgres://user:pass@localhost:5432/mydb"
}
}
}
}
4. Kết Quả Sau Khi Tích Hợp
Bây giờ, khi bạn hỏi AI trong editor: *"Hãy viết hàm TypeScript lấy top 5 khách hàng có doanh thu cao nhất tháng này"*, AI sẽ:
1. Tự động gọi tool `get_database_schema` để xem cấu trúc bảng `orders` và `customers`.
2. Tự động kiểm tra dữ liệu mẫu qua `execute_safe_readonly_query`.
3. Sinh ra đoạn mã TypeScript chuẩn xác 100% với tên cột và kiểu dữ liệu khớp hoàn hảo với cơ sở dữ liệu thực tế của bạn!