Mở đầu: Tại sao nên xây dựng MCP Server cho PostgreSQL?
Trong bối cảnh AI agent ngày càng phổ biến, việc kết nối mô hình ngôn ngữ lớn (LLM) với cơ sở dữ liệu quan hệ như PostgreSQL trở thành nhu cầu thiết yếu. Custom MCP Server (Model Context Protocol Server) cho phép bạn tạo cầu nối an toàn giữa AI và database, hỗ trợ truy vấn tự nhiên bằng ngôn ngữ thường ngày thay vì SQL thuần túy. Bài viết này sẽ hướng dẫn bạn xây dựng từ A đến Z một MCP Server kết nối PostgreSQL, tích hợp HolySheep AI API với chi phí chỉ từ $0.42/1M tokens — tiết kiệm đến 85% so với các giải pháp truyền thống.
Bảng so sánh chi phí và hiệu suất
| Tiêu chí | HolySheep AI | OpenAI API | Anthropic API |
|---|---|---|---|
| GPT-4.1 | $8/MTok | $60/MTok | - |
| Claude Sonnet 4.5 | $15/MTok | - | $18/MTok |
| Gemini 2.5 Flash | $2.50/MTok | - | - |
| DeepSeek V3.2 | $0.42/MTok | - | - |
| Độ trễ trung bình | <50ms | 150-300ms | 100-250ms |
| Phương thức thanh toán | WeChat, Alipay, Visa | Thẻ quốc tế | Thẻ quốc tế |
| Tín dụng miễn phí | Có, khi đăng ký | $5 trial | Có |
| Độ phủ mô hình | GPT, Claude, Gemini, DeepSeek | Chỉ dòng GPT | Chỉ Claude |
| Phù hợp | Doanh nghiệp Việt, dev China | Khách quốc tế | Enterprise US |
Kiến trúc tổng quan
MCP Server hoạt động như một layer trung gian, nhận yêu cầu từ LLM, chuyển đổi thành SQL an toàn và thực thi trên PostgreSQL. Khi tích hợp HolySheep AI, bạn có thể sử dụng DeepSeek V3.2 với chi phí cực thấp ($0.42/MTok) để xử lý việc chuyển đổi ngôn ngữ tự nhiên sang SQL.
Thiết lập môi trường
# Tạo thư mục dự án
mkdir mcp-postgres && cd mcp-postgres
Khởi tạo Node.js project
npm init -y
Cài đặt dependencies cần thiết
npm install @modelcontextprotocol/sdk pg dotenv
Cài đặt dev dependencies
npm install -D typescript @types/node @types/pg ts-node
Khởi tạo TypeScript config
npx tsc --init
Cấu hình file .env
# Database configuration
DATABASE_URL=postgresql://username:password@localhost:5432/mydb
HolySheep AI API - Đăng ký tại đây: https://www.holysheep.ai/register
HOLYSHEEP_API_KEY=YOUR_HOLYSHEEP_API_KEY
HOLYSHEEP_BASE_URL=https://api.holysheep.ai/v1
MCP Server configuration
MCP_PORT=3000
LOG_LEVEL=info
Xây dựng Database Schema Reader
import { Pool } from 'pg';
import dotenv from 'dotenv';
dotenv.config();
class DatabaseSchemaReader {
private pool: Pool;
constructor(connectionString: string) {
this.pool = new Pool({
connectionString,
max: 10,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
}
async getTables(): Promise<any[]> {
const query = `
SELECT
table_name,
table_type
FROM information_schema.tables
WHERE table_schema = 'public'
ORDER BY table_name;
`;
const result = await this.pool.query(query);
return result.rows;
}
async getColumns(tableName: string): Promise<any[]> {
const query = `
SELECT
column_name,
data_type,
is_nullable,
column_default
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = $1
ORDER BY ordinal_position;
`;
const result = await this.pool.query(query, [tableName]);
return result.rows;
}
async getPrimaryKeys(tableName: string): Promise<string[]> {
const query = `
SELECT a.attname as column_name
FROM pg_index i
JOIN pg_attribute a ON a.attrelid = i.indrelid
AND a.attnum = ANY(i.indkey)
JOIN pg_class c ON c.oid = i.indrelid
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE i.indisprimary
AND n.nspname = 'public'
AND c.relname = $1;
`;
const result = await this.pool.query(query, [tableName]);
return result.rows.map(row => row.column_name);
}
async getSchemaDescription(): Promise<string> {
const tables = await this.getTables();
let description = 'Database Schema:\n\n';
for (const table of tables) {
const columns = await this.getColumns(table.table_name);
const primaryKeys = await this.getPrimaryKeys(table.table_name);
description += Table: ${table.table_name}\n;
description += Columns:\n;
for (const col of columns) {
const pk = primaryKeys.includes(col.column_name) ? ' (PK)' : '';
const nullable = col.is_nullable === 'YES' ? ' NULL' : ' NOT NULL';
const defaultVal = col.column_default ? DEFAULT ${col.column_default} : '';
description += - ${col.column_name}: ${col.data_type}${nullable}${defaultVal}${pk}\n;
}
description += '\n';
}
return description;
}
async executeQuery(query: string, params?: any[]): Promise<any> {
const startTime = Date.now();
try {
const result = await this.pool.query(query, params);
const executionTime = Date.now() - startTime;
return {
success: true,
data: result.rows,
rowCount: result.rowCount,
executionTimeMs: executionTime
};
} catch (error: any) {
return {
success: false,
error: error.message,
code: error.code,
executionTimeMs: Date.now() - startTime
};
}
}
async close(): Promise<void> {
await this.pool.end();
}
}
export default DatabaseSchemaReader;
Tạo SQL Generator với HolySheep AI
import fetch from 'node-fetch';
interface SQLGenerationRequest {
userQuestion: string;
schemaDescription: string;
chatHistory?: string;
}
interface SQLGenerationResponse {
sql: string;
confidence: number;
explanation: string;
}
class SQLGenerator {
private apiKey: string;
private baseUrl: string;
constructor(apiKey: string, baseUrl: string = 'https://api.holysheep.ai/v1') {
this.apiKey = apiKey;
this.baseUrl = baseUrl;
}
async generateSQL(request: SQLGenerationRequest): Promise<SQLGenerationResponse> {
const systemPrompt = `Bạn là một chuyên gia SQL PostgreSQL. Dựa trên câu hỏi của người dùng và schema database, hãy tạo câu SQL tối ưu.
QUY TẮC BẮT BUỘC:
1. Chỉ sử dụng SELECT (không INSERT, UPDATE, DELETE để đảm bảo an toàn)
2. Thêm LIMIT 100 để tránh trả về quá nhiều dữ liệu
3. Sử dụng tên bảng và cột CHÍNH XÁC từ schema
4. Format kết quả JSON với các trường: sql, confidence (0-1), explanation
CHỈ TRẢ VỀ JSON, không có text khác.`;
const userPrompt = `Schema Database:
${request.schemaDescription}
Câu hỏi: ${request.userQuestion}
${request.chatHistory ? Lịch sử hội thoại:\n${request.chatHistory} : ''}`;
try {
const response = await fetch(${this.baseUrl}/chat/completions, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': Bearer ${this.apiKey}
},
body: JSON.stringify({
model: 'deepseek-chat',
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt }
],
temperature: 0.3,
max_tokens: 1000
})
});
if (!response.ok) {
throw new Error(API Error: ${response.status} ${response.statusText});
}
const data = await response.json() as any;
const content = data.choices[0].message.content;
// Parse JSON response
const cleanedContent = content.replace(/``json\n?/g, '').replace(/``\n?/g, '').trim();
const result = JSON.parse(cleanedContent);
return result as SQLGenerationResponse;
} catch (error: any) {
throw new Error(SQL Generation failed: ${error.message});
}
}
// Validate SQL để ngăn SQL injection
validateSQL(sql: string): { valid: boolean; reason?: string } {
const normalizedSQL = sql.trim().toUpperCase();
// Chỉ cho phép SELECT
if (!normalizedSQL.startsWith('SELECT')) {
return { valid: false, reason: 'Chỉ chấp nhận câu lệnh SELECT' };
}
// Kiểm tra từ khóa nguy hiểm
const dangerousKeywords = ['DROP', 'DELETE', 'INSERT', 'UPDATE', 'TRUNCATE', 'ALTER', 'CREATE'];
for (const keyword of dangerousKeywords) {
if (normalizedSQL.includes(keyword)) {
return { valid: false, reason: Từ khóa cấm: ${keyword} };
}
}
// Kiểm tra LIMIT
if (!normalizedSQL.includes('LIMIT')) {
return { valid: false, reason: 'Phải có LIMIT để giới hạn kết quả' };
}
return { valid: true };
}
}
export default SQLGenerator;
Xây dựng MCP Server hoàn chỉnh
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 DatabaseSchemaReader from './schemaReader.js';
import SQLGenerator from './sqlGenerator.js';
import dotenv from 'dotenv';
dotenv.config();
// Khởi tạo các thành phần
const dbReader = new DatabaseSchemaReader(process.env.DATABASE_URL!);
const sqlGenerator = new SQLGenerator(process.env.HOLYSHEEP_API_KEY!);
// Định nghĩa các tools
const tools = [
{
name: 'get_schema',
description: 'Lấy mô tả schema của database PostgreSQL',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'query_database',
description: 'Truy vấn database bằng ngôn ngữ tự nhiên (tiếng Việt)',
inputSchema: {
type: 'object',
properties: {
question: {
type: 'string',
description: 'Câu hỏi bằng tiếng Việt về database'
}
},
required: ['question']
}
},
{
name: 'execute_sql',
description: 'Thực thi câu SQL trực tiếp (chỉ SELECT)',
inputSchema: {
type: 'object',
properties: {
sql: {
type: 'string',
description: 'Câu SQL cần thực thi'
},
params: {
type: 'array',
description: 'Tham số cho câu SQL'
}
},
required: ['sql']
}
}
];
// Khởi tạo MCP Server
const server = new Server(
{
name: 'postgres-mcp-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// Đăng ký handlers
server.setRequestHandler(ListToolsRequestSchema, async () => {
return { tools };
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'get_schema': {
const schema = await dbReader.getSchemaDescription();
return {
content: [
{
type: 'text',
text: schema
}
]
};
}
case 'query_database': {
const question = args.question as string;
// Bước 1: Lấy schema
const schema = await dbReader.getSchemaDescription();
// Bước 2: Sinh SQL từ HolySheep AI
console.log([${new Date().toISOString()}] Generating SQL for: ${question});
const sqlResult = await sqlGenerator.generateSQL({
userQuestion: question,
schemaDescription: schema
});
// Bước 3: Validate SQL
const validation = sqlGenerator.validateSQL(sqlResult.sql);
if (!validation.valid) {
return {
content: [
{
type: 'text',
text: SQL Validation failed: ${validation.reason}\n\nGenerated SQL:\n${sqlResult.sql}
}
],
isError: true
};
}
// Bước 4: Thực thi SQL
console.log([${new Date().toISOString()}] Executing SQL: ${sqlResult.sql});
const queryResult = await dbReader.executeQuery(sqlResult.sql);
return {
content: [
{
type: 'text',
text: **Câu hỏi:** ${question}\n\n**SQL được tạo:**\n\\\sql\n${sqlResult.sql}\n\\\`\n\n**Độ tin cậy:** ${(sqlResult.confidence * 100).toFixed(0)}%\n\n**Giải thích:** ${sqlResult.explanation}\n\n**Kết quả:**\n${queryResult.success
? \\\json\n${JSON.stringify(queryResult.data, null, 2)}\n\\\\n\nTìm thấy ${queryResult.rowCount} dòng trong ${queryResult.executionTimeMs}ms
: Lỗi: ${queryResult.error}
}`
}
]
};
}
case 'execute_sql': {
const sql = args.sql as string;
const params = args.params as any[];
// Validate trước khi thực thi
const validation = sqlGenerator.validateSQL(sql);
if (!validation.valid) {
return {
content: [
{
type: 'text',
text: SQL Validation failed: ${validation.reason}
}
],
isError: true
};
}
const result = await dbReader.executeQuery(sql, params);
return {
content: [
{
type: 'text',
text: result.success
? **Kết quả:**\n\\\json\n${JSON.stringify(result.data, null, 2)}\n\\\\n\n${result.rowCount} dòng trong ${result.executionTimeMs}ms
: **Lỗi:** ${result.error}\n\nCode: ${result.code}
}
]
};
}
default:
return {
content: [
{
type: 'text',
text: Unknown tool: ${name}
}
],
isError: true
};
}
} catch (error: any) {
console.error([ERROR] ${error.message});
return {
content: [
{
type: 'text',
text: Error: ${error.message}
}
],
isError: true
};
}
});
// Khởi động server
async function main() {
console.log('[MCP Server] Starting PostgreSQL MCP Server...');
const transport = new StdioServerTransport();
await server.connect(transport);
console.log('[MCP Server] Connected and ready!');
// Cleanup khi process kết thúc
process.on('SIGINT', async () => {
console.log('[MCP Server] Shutting down...');
await dbReader.close();
process.exit(0);
});
}
main().catch(console.error);
Tạo client test
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
async function testMCPClient() {
// Khởi tạo transport
const transport = new StdioClientTransport({
command: 'npx',
args: ['tsx', 'src