{"product_id":"mcp-server-cho-postgresql-mysql-claude-truy-vấn-database-trực-tiếp","title":"MCP Server cho PostgreSQL\/MySQL — Claude truy vấn database trực tiếp","description":"\n\u003cp\u003eModel Context Protocol (MCP) cho phép Claude kết nối với các nguồn dữ liệu bên ngoài thông qua các server trung gian. Một trong những ứng dụng thực tiễn nhất là cho phép Claude truy vấn database trực tiếp — bạn hỏi bằng tiếng Việt hoặc tiếng Anh, Claude tự viết SQL, thực thi và trả về kết quả đã phân tích. Bài viết này hướng dẫn thiết lập MCP Server cho PostgreSQL và MySQL với các biện pháp bảo mật cần thiết.\u003c\/p\u003e\n\n\u003ch2\u003eMCP Database Server là gì?\u003c\/h2\u003e\n\u003cp\u003eMCP Database Server là một ứng dụng trung gian chạy trên máy tính của bạn (hoặc server), đóng vai trò cầu nối giữa Claude và database. Luồng hoạt động:\u003c\/p\u003e\n\u003col\u003e\n  \u003cli\u003eBạn hỏi Claude bằng ngôn ngữ tự nhiên: \"Cho tôi top 10 khách hàng có doanh thu cao nhất quý này\"\u003c\/li\u003e\n  \u003cli\u003eClaude phân tích câu hỏi, xem schema database (thông qua MCP), viết câu SQL phù hợp\u003c\/li\u003e\n  \u003cli\u003eClaude gửi câu SQL đến MCP Server để thực thi\u003c\/li\u003e\n  \u003cli\u003eMCP Server kiểm tra quyền, chạy query trên database và trả kết quả\u003c\/li\u003e\n  \u003cli\u003eClaude nhận kết quả, phân tích và trình bày cho bạn dưới dạng dễ hiểu\u003c\/li\u003e\n\u003c\/ol\u003e\n\n\u003ch2\u003eCài đặt MCP Server cho PostgreSQL\u003c\/h2\u003e\n\n\u003ch3\u003eYêu cầu hệ thống\u003c\/h3\u003e\n\u003cul\u003e\n  \u003cli\u003eNode.js 18 trở lên\u003c\/li\u003e\n  \u003cli\u003ePostgreSQL 12 trở lên (local hoặc remote)\u003c\/li\u003e\n  \u003cli\u003eClaude Desktop (phiên bản hỗ trợ MCP)\u003c\/li\u003e\n\u003c\/ul\u003e\n\n\u003ch3\u003eBước 1: Tạo project MCP Server\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003emkdir mcp-postgres-server\ncd mcp-postgres-server\nnpm init -y\nnpm install @modelcontextprotocol\/sdk pg\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch3\u003eBước 2: Viết MCP Server\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003e\/\/ server.js\nconst { McpServer } = require(\"@modelcontextprotocol\/sdk\/server\/mcp.js\");\nconst { StdioServerTransport } = require(\"@modelcontextprotocol\/sdk\/server\/stdio.js\");\nconst { Pool } = require(\"pg\");\nconst { z } = require(\"zod\");\n\nconst pool = new Pool({\n  host: process.env.DB_HOST || \"localhost\",\n  port: parseInt(process.env.DB_PORT || \"5432\"),\n  database: process.env.DB_NAME,\n  user: process.env.DB_USER,\n  password: process.env.DB_PASSWORD,\n  max: 5\n});\n\nconst server = new McpServer({\n  name: \"postgres-query\",\n  version: \"1.0.0\"\n});\n\n\/\/ Tool: Xem danh sách bảng\nserver.tool(\n  \"list_tables\",\n  \"Liệt kê tất cả bảng trong database\",\n  {},\n  async () =\u0026gt; {\n    const result = await pool.query(\n      \"SELECT table_name, table_type \" +\n      \"FROM information_schema.tables \" +\n      \"WHERE table_schema = 'public' \" +\n      \"ORDER BY table_name\"\n    );\n    return {\n      content: [{\n        type: \"text\",\n        text: JSON.stringify(result.rows, null, 2)\n      }]\n    };\n  }\n);\n\n\/\/ Tool: Xem cấu trúc bảng\nserver.tool(\n  \"describe_table\",\n  \"Xem cấu trúc chi tiết của một bảng (columns, types, constraints)\",\n  { table_name: z.string().describe(\"Tên bảng cần xem cấu trúc\") },\n  async ({ table_name }) =\u0026gt; {\n    \/\/ Validate table name chống SQL injection\n    if (!\/^[a-zA-Z_][a-zA-Z0-9_]*$\/.test(table_name)) {\n      return { content: [{ type: \"text\", text: \"Tên bảng không hợp lệ.\" }] };\n    }\n    const result = await pool.query(\n      \"SELECT column_name, data_type, is_nullable, \" +\n      \"column_default, character_maximum_length \" +\n      \"FROM information_schema.columns \" +\n      \"WHERE table_schema = 'public' AND table_name = $1 \" +\n      \"ORDER BY ordinal_position\",\n      [table_name]\n    );\n    return {\n      content: [{\n        type: \"text\",\n        text: JSON.stringify(result.rows, null, 2)\n      }]\n    };\n  }\n);\n\n\/\/ Tool: Thực thi query READ-ONLY\nserver.tool(\n  \"run_query\",\n  \"Thực thi câu SQL read-only (SELECT) trên database. KHÔNG hỗ trợ INSERT, UPDATE, DELETE.\",\n  {\n    sql: z.string().describe(\"Câu SQL cần thực thi (chỉ SELECT)\"),\n    params: z.array(z.any()).optional()\n      .describe(\"Tham số cho prepared statement\")\n  },\n  async ({ sql, params }) =\u0026gt; {\n    \/\/ Kiểm tra read-only\n    const normalized = sql.trim().toUpperCase();\n    const forbidden = [\"INSERT\", \"UPDATE\", \"DELETE\", \"DROP\",\n      \"CREATE\", \"ALTER\", \"TRUNCATE\", \"GRANT\", \"REVOKE\"];\n    for (const keyword of forbidden) {\n      if (normalized.startsWith(keyword)) {\n        return {\n          content: [{\n            type: \"text\",\n            text: \"Chỉ cho phép câu lệnh SELECT. Các thao tác ghi không được hỗ trợ.\"\n          }]\n        };\n      }\n    }\n\n    \/\/ Giới hạn kết quả\n    if (!normalized.includes(\"LIMIT\")) {\n      sql = sql.replace(\/;?s*$\/, \" LIMIT 100;\");\n    }\n\n    try {\n      const result = await pool.query(sql, params || []);\n      return {\n        content: [{\n          type: \"text\",\n          text: `Kết quả (${result.rowCount} dòng):\\n${JSON.stringify(result.rows, null, 2)}`\n        }]\n      };\n    } catch (error) {\n      return {\n        content: [{\n          type: \"text\",\n          text: `Lỗi thực thi query: ${error.message}`\n        }]\n      };\n    }\n  }\n);\n\nasync function main() {\n  const transport = new StdioServerTransport();\n  await server.connect(transport);\n}\n\nmain();\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch3\u003eBước 3: Cấu hình Claude Desktop\u003c\/h3\u003e\n\u003cp\u003eThêm MCP Server vào file cấu hình Claude Desktop:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003e\/\/ Trên macOS: ~\/Library\/Application Support\/Claude\/claude_desktop_config.json\n\/\/ Trên Windows: %APPDATA%\/Claude\/claude_desktop_config.json\n\n{\n  \"mcpServers\": {\n    \"postgres\": {\n      \"command\": \"node\",\n      \"args\": [\"\/path\/to\/mcp-postgres-server\/server.js\"],\n      \"env\": {\n        \"DB_HOST\": \"localhost\",\n        \"DB_PORT\": \"5432\",\n        \"DB_NAME\": \"my_database\",\n        \"DB_USER\": \"readonly_user\",\n        \"DB_PASSWORD\": \"secure_password\"\n      }\n    }\n  }\n}\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eCài đặt cho MySQL\u003c\/h2\u003e\n\u003cp\u003eQuy trình tương tự PostgreSQL với một vài thay đổi:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003e# Cài đặt dependency cho MySQL\nnpm install @modelcontextprotocol\/sdk mysql2\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003cp\u003eThay đổi chính trong code:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003e\/\/ Thay thế phần kết nối PostgreSQL\nconst mysql = require(\"mysql2\/promise\");\n\nconst pool = mysql.createPool({\n  host: process.env.DB_HOST || \"localhost\",\n  port: parseInt(process.env.DB_PORT || \"3306\"),\n  database: process.env.DB_NAME,\n  user: process.env.DB_USER,\n  password: process.env.DB_PASSWORD,\n  connectionLimit: 5\n});\n\n\/\/ Thay đổi query schema introspection cho MySQL\n\/\/ Liệt kê bảng:\nconst [tables] = await pool.query(\n  \"SELECT TABLE_NAME, TABLE_TYPE FROM information_schema.TABLES WHERE TABLE_SCHEMA = ?\",\n  [process.env.DB_NAME]\n);\n\n\/\/ Xem cấu trúc bảng:\nconst [columns] = await pool.query(\n  \"SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?\",\n  [process.env.DB_NAME, table_name]\n);\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eBảo mật kết nối: Tạo user read-only\u003c\/h2\u003e\n\u003cp\u003eNguyên tắc quan trọng nhất: MCP Server chỉ nên kết nối database bằng user read-only. Tuyệt đối không dùng user root hoặc user có quyền ghi.\u003c\/p\u003e\n\n\u003ch3\u003ePostgreSQL: Tạo read-only user\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003e-- Tạo role read-only\nCREATE ROLE mcp_readonly LOGIN PASSWORD 'strong_password_here';\n\n-- Cấp quyền kết nối\nGRANT CONNECT ON DATABASE my_database TO mcp_readonly;\n\n-- Cấp quyền đọc tất cả bảng hiện tại\nGRANT USAGE ON SCHEMA public TO mcp_readonly;\nGRANT SELECT ON ALL TABLES IN SCHEMA public TO mcp_readonly;\n\n-- Cấp quyền đọc bảng tạo trong tương lai\nALTER DEFAULT PRIVILEGES IN SCHEMA public\n  GRANT SELECT ON TABLES TO mcp_readonly;\n\n-- Giới hạn số kết nối đồng thời\nALTER ROLE mcp_readonly CONNECTION LIMIT 5;\n\n-- Giới hạn thời gian thực thi query (tránh query nặng)\nALTER ROLE mcp_readonly SET statement_timeout = '30s';\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch3\u003eMySQL: Tạo read-only user\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003e-- Tạo user read-only\nCREATE USER 'mcp_readonly'@'localhost'\n  IDENTIFIED BY 'strong_password_here';\n\n-- Cấp quyền SELECT trên toàn bộ database\nGRANT SELECT ON my_database.* TO 'mcp_readonly'@'localhost';\n\n-- Giới hạn tài nguyên\nALTER USER 'mcp_readonly'@'localhost'\n  WITH MAX_QUERIES_PER_HOUR 1000\n       MAX_CONNECTIONS_PER_HOUR 100\n       MAX_USER_CONNECTIONS 5;\n\nFLUSH PRIVILEGES;\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eSchema Introspection — Claude hiểu cấu trúc database\u003c\/h2\u003e\n\u003cp\u003eĐể Claude viết SQL chính xác, nó cần hiểu cấu trúc database. Đó là lý do tool \"list_tables\" và \"describe_table\" rất quan trọng. Khi bạn hỏi Claude, quy trình diễn ra như sau:\u003c\/p\u003e\n\u003col\u003e\n  \u003cli\u003eClaude gọi \"list_tables\" để biết database có những bảng nào\u003c\/li\u003e\n  \u003cli\u003eClaude gọi \"describe_table\" cho các bảng liên quan đến câu hỏi\u003c\/li\u003e\n  \u003cli\u003eDựa trên schema, Claude viết câu SQL phù hợp\u003c\/li\u003e\n  \u003cli\u003eClaude gọi \"run_query\" để thực thi và nhận kết quả\u003c\/li\u003e\n\u003c\/ol\u003e\n\n\u003cp\u003eBạn có thể bổ sung thêm tool cho schema introspection chi tiết hơn:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003e\/\/ Tool bổ sung: Xem foreign keys và relationships\nserver.tool(\n  \"list_relationships\",\n  \"Xem quan hệ giữa các bảng (foreign keys)\",\n  { table_name: z.string().optional() },\n  async ({ table_name }) =\u0026gt; {\n    let sql = `\n      SELECT\n        tc.table_name AS source_table,\n        kcu.column_name AS source_column,\n        ccu.table_name AS target_table,\n        ccu.column_name AS target_column\n      FROM information_schema.table_constraints tc\n      JOIN information_schema.key_column_usage kcu\n        ON tc.constraint_name = kcu.constraint_name\n      JOIN information_schema.constraint_column_usage ccu\n        ON ccu.constraint_name = tc.constraint_name\n      WHERE tc.constraint_type = 'FOREIGN KEY'\n    `;\n    const params = [];\n    if (table_name) {\n      sql += \" AND tc.table_name = $1\";\n      params.push(table_name);\n    }\n    const result = await pool.query(sql, params);\n    return {\n      content: [{\n        type: \"text\",\n        text: JSON.stringify(result.rows, null, 2)\n      }]\n    };\n  }\n);\n\n\/\/ Tool bổ sung: Xem indexes\nserver.tool(\n  \"list_indexes\",\n  \"Liệt kê indexes của một bảng (hữu ích cho tối ưu query)\",\n  { table_name: z.string() },\n  async ({ table_name }) =\u0026gt; {\n    if (!\/^[a-zA-Z_][a-zA-Z0-9_]*$\/.test(table_name)) {\n      return { content: [{ type: \"text\", text: \"Tên bảng không hợp lệ.\" }] };\n    }\n    const result = await pool.query(`\n      SELECT indexname, indexdef\n      FROM pg_indexes\n      WHERE tablename = $1\n    `, [table_name]);\n    return {\n      content: [{\n        type: \"text\",\n        text: JSON.stringify(result.rows, null, 2)\n      }]\n    };\n  }\n);\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eTạo và thực thi query bằng ngôn ngữ tự nhiên\u003c\/h2\u003e\n\u003cp\u003eSau khi cấu hình xong, bạn có thể hỏi Claude bằng tiếng Việt. Một số ví dụ thực tế:\u003c\/p\u003e\n\n\u003cpre\u003e\u003ccode\u003eVí dụ các câu hỏi bạn có thể hỏi Claude:\n\n1. \"Cho tôi danh sách 10 đơn hàng mới nhất hôm nay\"\n2. \"Tổng doanh thu tháng này so với tháng trước tăng bao nhiêu phần trăm?\"\n3. \"Khách hàng nào đã mua hơn 5 lần trong quý này?\"\n4. \"Sản phẩm nào có tỷ lệ hoàn trả cao nhất?\"\n5. \"Thống kê số đơn hàng theo từng tỉnh thành, sắp xếp giảm dần\"\n\nClaude sẽ tự động:\n- Xem schema để hiểu cấu trúc bảng\n- Viết câu SQL phù hợp\n- Thực thi và phân tích kết quả\n- Trình bày dưới dạng bảng hoặc tóm tắt dễ hiểu\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eFormat kết quả trả về\u003c\/h2\u003e\n\u003cp\u003eClaude tự động format kết quả nhưng bạn có thể yêu cầu cụ thể:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003eTruy vấn doanh thu theo tháng trong năm nay.\nTrình bày kết quả dưới dạng:\n1. Bảng tổng hợp có cột: Tháng | Doanh thu | Số đơn | Giá trị TB\/đơn\n2. So sánh tháng hiện tại với tháng trước (tăng\/giảm %)\n3. Tháng có doanh thu cao nhất và thấp nhất\n4. Xu hướng chung (tăng\/giảm\/ổn định)\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eKiểm soát truy cập nâng cao\u003c\/h2\u003e\n\u003cp\u003eNgoài read-only user, bạn có thể thêm các lớp bảo mật trong MCP Server:\u003c\/p\u003e\n\n\u003ch3\u003eDanh sách bảng được phép truy vấn\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003e\/\/ Chỉ cho phép truy vấn một số bảng nhất định\nconst ALLOWED_TABLES = [\n  \"orders\", \"products\", \"customers\",\n  \"order_items\", \"categories\"\n];\n\n\/\/ Bảng chứa dữ liệu nhạy cảm - KHÔNG cho phép\nconst BLOCKED_TABLES = [\n  \"users\", \"passwords\", \"api_keys\",\n  \"payment_details\", \"personal_info\"\n];\n\nfunction validateTableAccess(sql) {\n  const normalized = sql.toLowerCase();\n  for (const table of BLOCKED_TABLES) {\n    if (normalized.includes(table)) {\n      return {\n        allowed: false,\n        reason: `Bảng \"${table}\" chứa dữ liệu nhạy cảm và không được phép truy vấn.`\n      };\n    }\n  }\n  return { allowed: true };\n}\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch3\u003eGiới hạn kết quả và thời gian\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003e\/\/ Cấu hình giới hạn\nconst QUERY_LIMITS = {\n  maxRows: 500,           \/\/ Tối đa 500 dòng kết quả\n  timeoutMs: 30000,       \/\/ Timeout 30 giây\n  maxQueryLength: 2000    \/\/ Độ dài query tối đa 2000 ký tự\n};\n\nfunction enforceQueryLimits(sql) {\n  if (sql.length \u0026gt; QUERY_LIMITS.maxQueryLength) {\n    throw new Error(\"Query quá dài. Giới hạn \" +\n      QUERY_LIMITS.maxQueryLength + \" ký tự.\");\n  }\n\n  \/\/ Đảm bảo có LIMIT\n  const upper = sql.toUpperCase();\n  if (!upper.includes(\"LIMIT\")) {\n    sql = sql.replace(\/;?s*$\/, ` LIMIT ${QUERY_LIMITS.maxRows};`);\n  }\n\n  return sql;\n}\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eGiới hạn về hiệu năng\u003c\/h2\u003e\n\u003cp\u003eMột số lưu ý khi cho phép Claude truy vấn database trực tiếp:\u003c\/p\u003e\n\u003cul\u003e\n  \u003cli\u003e\n\u003cstrong\u003eQuery phức tạp:\u003c\/strong\u003e Claude có thể tạo ra các query với nhiều JOIN hoặc subquery lồng nhau, gây tải cho database. Sử dụng statement_timeout ở mức database để tự động kill query chạy quá lâu.\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eFull table scan:\u003c\/strong\u003e Với bảng lớn (hàng triệu dòng), Claude có thể vô tình viết query không dùng index. Tool \"list_indexes\" giúp Claude biết index nào tồn tại để viết query tối ưu hơn.\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eKết quả lớn:\u003c\/strong\u003e Luôn enforce LIMIT để tránh trả về hàng nghìn dòng. Claude chỉ cần mẫu đại diện để phân tích.\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eConnection pool:\u003c\/strong\u003e Giới hạn số connection từ MCP Server đến database (5 connection trong ví dụ trên) để không chiếm tài nguyên production.\u003c\/li\u003e\n\u003c\/ul\u003e\n\n\u003ch2\u003eSử dụng thực tế: Database production vs staging\u003c\/h2\u003e\n\u003cp\u003eKhuyến nghị mạnh mẽ: Kết nối MCP Server vào read replica hoặc database staging, không kết nối trực tiếp vào production primary.\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003e\/\/ Cấu hình kết nối read replica\n{\n  \"mcpServers\": {\n    \"postgres-readonly\": {\n      \"command\": \"node\",\n      \"args\": [\"\/path\/to\/server.js\"],\n      \"env\": {\n        \"DB_HOST\": \"read-replica.example.com\",\n        \"DB_PORT\": \"5432\",\n        \"DB_NAME\": \"my_database\",\n        \"DB_USER\": \"mcp_readonly\",\n        \"DB_PASSWORD\": \"secure_password\"\n      }\n    }\n  }\n}\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eAudit logging\u003c\/h2\u003e\n\u003cp\u003eGhi log mọi query được thực thi qua MCP Server để kiểm soát và debug:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003econst fs = require(\"fs\");\nconst path = require(\"path\");\n\nfunction logQuery(sql, params, rowCount, duration) {\n  const entry = {\n    timestamp: new Date().toISOString(),\n    sql: sql,\n    params: params || [],\n    rowCount: rowCount,\n    durationMs: duration\n  };\n\n  const logFile = path.join(__dirname, \"query-log.jsonl\");\n  fs.appendFileSync(logFile, JSON.stringify(entry) + \"\\n\");\n}\n\n\/\/ Sử dụng trong tool run_query\nconst start = Date.now();\nconst result = await pool.query(sql, params || []);\nlogQuery(sql, params, result.rowCount, Date.now() - start);\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eXử lý lỗi và edge cases\u003c\/h2\u003e\n\u003cp\u003eKhi cho phép Claude truy vấn database, cần xử lý kỹ các tình huống lỗi:\u003c\/p\u003e\n\n\u003ch3\u003eTimeout handling\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003e\/\/ Bọc query trong timeout\nasync function queryWithTimeout(sql, params, timeoutMs = 30000) {\n  const timeoutPromise = new Promise((_, reject) =\u0026gt;\n    setTimeout(() =\u0026gt; reject(new Error(\"Query timeout\")), timeoutMs)\n  );\n\n  try {\n    const result = await Promise.race([\n      pool.query(sql, params),\n      timeoutPromise\n    ]);\n    return result;\n  } catch (error) {\n    if (error.message === \"Query timeout\") {\n      return {\n        content: [{\n          type: \"text\",\n          text: \"Query vượt quá thời gian cho phép (30 giây). Hãy thử query đơn giản hơn hoặc thêm điều kiện WHERE để giảm phạm vi dữ liệu.\"\n        }]\n      };\n    }\n    throw error;\n  }\n}\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch3\u003eXử lý kết nối database mất\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003e\/\/ Kiểm tra kết nối trước khi query\npool.on(\"error\", (err) =\u0026gt; {\n  console.error(\"Database pool error:\", err.message);\n});\n\nasync function healthCheck() {\n  try {\n    await pool.query(\"SELECT 1\");\n    return { status: \"connected\" };\n  } catch (error) {\n    return { status: \"disconnected\", error: error.message };\n  }\n}\n\n\/\/ Expose health check như một MCP tool\nserver.tool(\n  \"check_connection\",\n  \"Kiểm tra kết nối database có hoạt động không\",\n  {},\n  async () =\u0026gt; {\n    const health = await healthCheck();\n    return {\n      content: [{\n        type: \"text\",\n        text: JSON.stringify(health)\n      }]\n    };\n  }\n);\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eMở rộng: Thêm tool phân tích dữ liệu\u003c\/h2\u003e\n\u003cp\u003eNgoài query thuần, bạn có thể thêm các tool phân tích dữ liệu cao cấp hơn:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003e\/\/ Tool: Thống kê tổng quan bảng\nserver.tool(\n  \"table_stats\",\n  \"Lấy thống kê tổng quan của một bảng: số dòng, min\/max\/avg các cột số\",\n  { table_name: z.string() },\n  async ({ table_name }) =\u0026gt; {\n    if (!\/^[a-zA-Z_][a-zA-Z0-9_]*$\/.test(table_name)) {\n      return { content: [{ type: \"text\", text: \"Tên bảng không hợp lệ.\" }] };\n    }\n\n    const countResult = await pool.query(\n      `SELECT COUNT(*) as total_rows FROM ${table_name}`\n    );\n\n    \/\/ Lấy thống kê các cột số\n    const columnsResult = await pool.query(`\n      SELECT column_name, data_type\n      FROM information_schema.columns\n      WHERE table_name = $1\n        AND data_type IN ('integer', 'bigint', 'numeric', 'real', 'double precision')\n    `, [table_name]);\n\n    const stats = { total_rows: countResult.rows[0].total_rows };\n\n    for (const col of columnsResult.rows) {\n      const statResult = await pool.query(`\n        SELECT MIN(${col.column_name}) as min_val,\n               MAX(${col.column_name}) as max_val,\n               AVG(${col.column_name})::numeric(10,2) as avg_val\n        FROM ${table_name}\n      `);\n      stats[col.column_name] = statResult.rows[0];\n    }\n\n    return {\n      content: [{ type: \"text\", text: JSON.stringify(stats, null, 2) }]\n    };\n  }\n);\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eCác trường hợp sử dụng phổ biến\u003c\/h2\u003e\n\u003cul\u003e\n  \u003cli\u003e\n\u003cstrong\u003eData analyst:\u003c\/strong\u003e Hỏi Claude phân tích dữ liệu bán hàng, khách hàng, marketing mà không cần biết SQL\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eCustomer support:\u003c\/strong\u003e Tra cứu nhanh thông tin đơn hàng, khách hàng qua ngôn ngữ tự nhiên\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eProduct manager:\u003c\/strong\u003e Lấy số liệu sản phẩm, tỷ lệ chuyển đổi, retention mà không cần nhờ dev\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eCEO\/Manager:\u003c\/strong\u003e Xem báo cáo doanh thu, tăng trưởng trực tiếp từ database theo thời gian thực\u003c\/li\u003e\n\u003c\/ul\u003e\n\n\u003ch2\u003eBước tiếp theo\u003c\/h2\u003e\n\u003cp\u003eBạn đã biết cách thiết lập MCP Server cho PostgreSQL và MySQL với các biện pháp bảo mật cần thiết. Tiếp theo, hãy tìm hiểu cách bảo mật MCP Server toàn diện hơn cho môi trường production hoặc khám phá các MCP Server khác cho file system, API và dịch vụ web. Xem thêm tại \u003ca href=\"\/collections\/ung-dung\"\u003eThư viện Ứng dụng Claude\u003c\/a\u003e.\u003c\/p\u003e\n","brand":"Minh Tuấn","offers":[{"title":"Default Title","offer_id":47730161189076,"sku":null,"price":0.0,"currency_code":"VND","in_stock":true}],"thumbnail_url":"\/\/cdn.shopify.com\/s\/files\/1\/0821\/0264\/9044\/files\/mcp-server-cho-postgresql-mysql-claude-truy-v_n-database-tr_c-ti_p.jpg?v=1774718106","url":"https:\/\/claude.vn\/products\/mcp-server-cho-postgresql-mysql-claude-truy-v%e1%ba%a5n-database-tr%e1%bb%b1c-ti%e1%ba%bfp","provider":"CLAUDE.VN","version":"1.0","type":"link"}