OpenCode là AI coding agent chạy trong terminal, open-source, hỗ trợ nhiều model backend (Claude, GPT, model local qua Ollama...) và có hệ thống MCP client riêng tương thích chuẩn Model Context Protocol. Nếu team bạn không muốn phụ thuộc vào một vendor cụ thể, hoặc muốn tự host agent với model tuỳ chọn, OpenCode + Postgres MCP là một combo đáng thử. Bài này tập trung vào cách cấu hình MCP server cho OpenCode, các thao tác query schema thực tế, một ví dụ sinh migration script hoàn chỉnh, và những hạn chế bạn cần biết trước khi đưa vào workflow chính thức.
Cài đặt và kết nối Postgres MCP vào OpenCode
OpenCode dùng file cấu hình opencode.json (thường đặt ở root project hoặc ~/.config/opencode/config.json cho cấu hình global) để khai báo MCP server. Cấu trúc khai báo gần giống chuẩn MCP chung nhưng để trong key mcp thay vì mcpServers:
{
"mcp": {
"postgres": {
"type": "local",
"command": ["npx", "-y", "@modelcontextprotocol/server-postgres",
"postgres://mcp_readonly:StrongPass123@localhost:5432/app_dev"],
"enabled": true
}
}
}
Nếu bạn dùng bản postgres-mcp có safe mode (khuyến nghị cho môi trường gần production), cấu hình tương tự nhưng đổi command:
{
"mcp": {
"postgres-safe": {
"type": "local",
"command": ["uvx", "postgres-mcp", "--access-mode=restricted",
"postgres://mcp_readonly:StrongPass123@localhost:5432/app_dev"],
"enabled": true
}
}
}
Sau khi lưu file, khởi động lại session OpenCode (opencode trong terminal), gõ lệnh nội bộ để kiểm tra tool MCP đã sẵn sàng:
/mcp
Lệnh này in ra danh sách server đã connect kèm trạng thái. Nếu thấy postgres: connected, nghĩa là agent đã có thể gọi tool query, list_tables... Nếu trạng thái là error, thường do connection string sai hoặc port bị chặn firewall — OpenCode sẽ in log lỗi kèm message gốc từ driver pg, khá dễ debug so với một số client MCP khác chỉ báo lỗi generic.
Mẹo: Khi OpenCode báo lỗi connect MCP, chạy thử đúng connection string đó bằng
psqltrực tiếp trước (psql "postgres://mcp_readonly:pass@localhost:5432/app_dev"). Việc này tách biệt rõ lỗi do network/credential với lỗi do cấu hình MCP, tránh mất thời gian debug sai hướng.
Truy vấn schema, table và index từ OpenCode
Sau khi kết nối, cách hiệu quả nhất để bắt đầu một task liên quan tới database trong OpenCode là yêu cầu agent introspect trước, thay vì lao vào viết code ngay. Prompt mẫu:
Dùng MCP postgres, liệt kê tất cả table trong schema public kèm số dòng ước lượng
(dùng reltuples từ pg_class, không cần COUNT(*) chính xác vì bảng lớn).
Với 5 bảng có nhiều dòng nhất, liệt kê chi tiết column, index, và cho biết index nào
đang không được dùng (dựa vào pg_stat_user_indexes.idx_scan = 0 nếu có quyền đọc view này).
Agent thường sẽ chạy một chuỗi query dạng:
SELECT relname AS table_name, reltuples::bigint AS estimated_rows
FROM pg_class
WHERE relkind = 'r' AND relnamespace = 'public'::regnamespace
ORDER BY reltuples DESC
LIMIT 5;
và tiếp theo với từng bảng:
SELECT indexrelname, idx_scan, idx_tup_read, idx_tup_fetch
FROM pg_stat_user_indexes
WHERE relname = 'orders';
Việc tìm index không dùng (idx_scan = 0) rất hữu ích trước khi bạn định thêm index mới cho một cột — đôi khi vấn đề performance không phải "thiếu index" mà là "có index nhưng planner không chọn dùng" do thống kê (statistics) cũ, cần chạy ANALYZE lại bảng.
Một câu hỏi follow-up thực tế khác, khai thác đúng thế mạnh "đọc trực tiếp DB" của MCP:
Trong bảng "orders", cột "status" có bao nhiêu giá trị khác nhau, phân phối như thế nào
(SELECT status, COUNT(*) GROUP BY status)? Dựa vào phân phối đó, có nên tạo partial index
cho giá trị "pending" không?
Đây là dạng câu hỏi mà nếu không có MCP, bạn sẽ phải tự chạy query rồi paste kết quả vào chat — với MCP, agent tự chạy, tự đọc phân phối dữ liệu thật, và đưa khuyến nghị index dựa trên số liệu cụ thể chứ không phải giả định chung.
Mẹo: Luôn hỏi thêm "giá trị NULL chiếm bao nhiêu phần trăm" khi agent phân tích phân phối cột — nhiều quyết định index/constraint sai vì bỏ qua tỷ lệ NULL, đặc biệt với cột được thêm sau bằng migration mà chưa backfill đầy đủ.
Ví dụ thực tế: sinh migration script sửa dữ liệu trong OpenCode
Migration không chỉ là thay đổi schema (ALTER TABLE), mà nhiều khi là data migration — chuyển đổi dữ liệu cũ sang định dạng mới. Đây là task agent làm tốt vì cần vừa hiểu schema, vừa hiểu dữ liệu thật để viết migration an toàn (không mất dữ liệu, có thể rollback).
Ví dụ tình huống: bảng customers có cột full_name dạng text tự do, giờ cần tách thành first_name và last_name để phục vụ tính năng mới.
Prompt:
Bảng customers hiện có cột full_name (text). Cần thêm 2 cột first_name, last_name,
và migrate dữ liệu cũ sang 2 cột này (tách theo khoảng trắng đầu tiên, phần còn lại
là last_name; nếu không có khoảng trắng thì last_name để trống).
Trước khi viết migration, chạy thử SELECT full_name FROM customers LIMIT 50 để xem
dữ liệu thật có case đặc biệt nào không (tên có nhiều khoảng trắng, tên rỗng, ký tự lạ).
Viết migration dạng file .sql có thể chạy độc lập, kèm phần rollback.
Sau khi agent chạy thử và xem dữ liệu thật (bước quan trọng — dữ liệu production luôn có case lạ hơn bạn tưởng), nó có thể trả về migration dạng:
-- migration: split_customer_full_name.up.sql
BEGIN;
ALTER TABLE customers
ADD COLUMN IF NOT EXISTS first_name text,
ADD COLUMN IF NOT EXISTS last_name text;
UPDATE customers
SET first_name = split_part(trim(full_name), ' ', 1),
last_name = CASE
WHEN position(' ' in trim(full_name)) > 0
THEN substring(trim(full_name) FROM position(' ' in trim(full_name)) + 1)
ELSE ''
END
WHERE full_name IS NOT NULL;
COMMIT;
Kèm file rollback tương ứng:
-- migration: split_customer_full_name.down.sql
BEGIN;
ALTER TABLE customers DROP COLUMN IF EXISTS first_name;
ALTER TABLE customers DROP COLUMN IF EXISTS last_name;
COMMIT;
Bước bắt buộc sau khi có script: chạy thử trên bản copy dữ liệu (hoặc staging), rồi yêu cầu agent tự viết luôn câu kiểm tra sau migration:
SELECT full_name, first_name, last_name
FROM customers
WHERE last_name = '' OR first_name IS NULL
LIMIT 20;
để bạn review nhanh các case migration xử lý chưa đúng trước khi apply lên production.
Mẹo: Với mọi data migration, luôn nhờ agent bọc trong transaction (
BEGIN/COMMIT) và viết kèm câuSELECTđối chiếu số dòng bị ảnh hưởng dự kiến (SELECT COUNT(*) FROM customers WHERE full_name IS NOT NULL) trước khi chạyUPDATEthật — so khớp con số này với số dòngUPDATEbáo ra sau khi chạy để chắc chắn không có gì bị bỏ sót hoặc chạy trùng.
Hạn chế đã biết của Postgres MCP khi dùng trong OpenCode
Vài điểm cần lưu ý thực tế khi bạn đưa Postgres MCP vào OpenCode cho công việc hàng ngày, để tránh kỳ vọng sai:
- Không có UI hiển thị kết quả dạng bảng đẹp: khác với extension VS Code của một số công cụ khác, OpenCode chạy trong terminal nên kết quả query lớn hiển thị dạng text thô, khó đọc nếu có nhiều cột. Với kết quả nhiều dòng, nên yêu cầu agent tóm tắt hoặc lưu ra file CSV/markdown thay vì in trực tiếp ra terminal.
- Session dài dễ mất context về schema đã introspect: OpenCode chưa có cơ chế cache kết quả MCP xuyên suốt session tốt như một số client khác, nên với session dài (nhiều giờ làm việc), agent có thể phải introspect lại schema nhiều lần, tốn token. Giải pháp là lưu bản tóm tắt schema ra file như đã nói ở các bài trước.
- Một số MCP server Postgres cộng đồng chưa hỗ trợ đầy đủ transaction đa câu lệnh qua tool
query— nghĩa là bạn khó yêu cầu agent chạy một blockBEGIN...COMMITnhiều câu như một transaction thật qua MCP; thường phải tách thành việc agent sinh file.sqlrồi bạn (hoặc một bước CI riêng) chạy bằngpsql -f. - Không có cơ chế MFA/approval trong luồng OpenCode gốc cho các thao tác nguy hiểm — mọi guardrail phải đến từ phía database role (REVOKE quyền viết) hoặc từ MCP server có safe mode, không nên trông chờ OpenCode tự hỏi xác nhận trước khi chạy lệnh nguy hiểm như một số IDE có UI approval riêng.
- Độ ổn định phụ thuộc nhiều vào model backend đang chọn — vì OpenCode cho phép đổi model linh hoạt, một model yếu hơn có thể sinh SQL sai cú pháp thường xuyên hơn khi làm việc với schema phức tạp nhiều bảng lồng nhau.
Mẹo: Vì OpenCode có thể đổi model linh hoạt, hãy chốt một model đủ mạnh (ví dụ Claude Sonnet hoặc tương đương) làm mặc định cho các task liên quan tới database — đừng dùng model rẻ/nhanh cho task sinh migration hay query production, rủi ro sai sót không đáng để đánh đổi vài giây tốc độ.
Tips
- Luôn dùng role read-only cho MCP server mặc định trong OpenCode; chỉ tạo thêm server riêng có quyền viết khi thật sự cần, và giới hạn nó vào database staging/dev.
- Kiểm tra
opencode.jsonkhông bị commit kèm password thật — dùng biến môi trường hoặc file.envđược.gitignore. - Với schema lớn, cân nhắc viết một MCP tool wrapper riêng (hoặc dùng
search_path) để giới hạn agent chỉ thấy các schema/table liên quan tới task đang làm. - Lưu output introspect quan trọng ra file trong repo — đừng để nó chỉ tồn tại trong lịch sử chat của session hiện tại.
Mẹo: Định kỳ export lịch sử session OpenCode có liên quan tới thao tác database ra file log riêng, để làm audit trail — hữu ích khi cần trả lời câu hỏi "AI đã chạy query gì trên DB tuần trước" từ team lead hoặc security review.