AizuDemy

Tutorial MCP: Hubungkan Database PostgreSQL ke AI Agent Lokal

Tutorial MCP: Hubungkan Database PostgreSQL ke AI Agent Lokal
๐ŸŽง
Dengarkan Artikel Ini
Suara AI Otomatis โ€ข 6 mnt baca baca
โšก TL;DR

Poin Kunci Artikel Ini:

  • Node.js v18.0.0 atau versi lebih baru
  • Instance PostgreSQL berjalan (lokal atau cloud)
  • Package manager (npm, pnpm, atau yarn)
๐Ÿ“‹ Daftar Isi Materi Tutup โ–ด

1. Arsitektur Model Context Protocol (MCP) untuk Database

Large Language Model (LLM) memiliki batasan context window dan knowledge cutoff. Mengirim data database secara manual via prompt tidak efisien dan berisiko membocorkan konteks sensitif. Model Context Protocol (MCP) memecahkan masalah ini melalui standar arsitektur client-server terbuka buatan Anthropic.

MCP mendefinisikan protokol interaksi terstandarisasi. AI Agent bertindak sebagai MCP Client. Aplikasi pengelola koneksi database bertindak sebagai MCP Server. Komunikasi antar proses terjadi melalui transmisi Standard Input/Output (stdio) atau Server-Sent Events (SSE).

  • Akses Data Real-Time: AI Client membaca state database terbaru tanpa skenario ETL (Extract, Transform, Load) eksternal.
  • Isolasi Keamanan: AI Client tidak memegang kredensial koneksi DB langsung. MCP Server membatasi akses melalui antarmuka tool tervalidasi.
  • Efisiensi Konteks: AI Agent mengambil skema dan baris data relevan secara dinamis sesuai kebutuhan query user.

2. Persiapan Environment dan Dependensi

Prasyarat sistem sebelum implementasi:

  • Node.js v18.0.0 atau versi lebih baru
  • Instance PostgreSQL berjalan (lokal atau cloud)
  • Package manager (npm, pnpm, atau yarn)
  • Claude Desktop App atau MCP Inspector untuk pengujian

Eksekusi urutan perintah terminal berikut untuk menginisialisasi direktori proyek TypeScript:

mkdir mcp-postgres-server
cd mcp-postgres-server
npm init -y
npm install @modelcontextprotocol/sdk pg zod dotenv
npm install --save-dev typescript @types/node @types/pg tsx
npx tsc --init

Konfigurasi file tsconfig.json untuk mendukung modul NodeNext dan target ES2022:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"]
}

3. Konfigurasi Keamanan PostgreSQL (Principle of Least Privilege)

Keamanan database mewajibkan pemisahan hak akses. Jangan pernah menggunakan akun superuser (seperti user postgres) dalam integrasi MCP Agent.

Jalankan skrip SQL berikut pada instance PostgreSQL untuk membuat role khusus berkemampuan read-only:

-- Membuat role khusus AI Agent
CREATE ROLE ai_readonly WITH LOGIN PASSWORD 'PasswordAmanAI2025!';

-- Memberikan hak akses koneksi ke database target
GRANT CONNECT ON DATABASE db_bisnis TO ai_readonly;

-- Pindah ke database target lalu berikan hak penggunaan skema public
\c db_bisnis;
GRANT USAGE ON SCHEMA public TO ai_readonly;

-- Memberikan hak akses SELECT pada seluruh tabel saat ini dan masa depan
GRANT SELECT ON ALL TABLES IN SCHEMA public TO ai_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON ALL TABLES TO ai_readonly;

Buat file .env di akar proyek untuk menyimpan variabel lingkungan koneksi database:

DATABASE_URL=postgres://ai_readonly:PasswordAmanAI2025!@localhost:5432/db_bisnis

4. Membangun MCP Server TypeScript

Buat berkas src/index.ts. Kode berikut mendefinisikan dua tool utama: get_database_schema untuk inspeksi struktur tabel dan execute_read_query untuk eksekusi query SELECT terisolasi dengan validasi ketat.

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
  Tool
} from "@modelcontextprotocol/sdk/types.js";
import pkg from "pg";
const { Pool } = pkg;
import dotenv from "dotenv";

dotenv.config();

const connectionString = process.env.DATABASE_URL;
if (!connectionString) {
  console.error("FATAL: DATABASE_URL tidak ditemukan di environment variable.");
  process.exit(1);
}

const dbPool = new Pool({
  connectionString,
  max: 5,
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 5000,
});

const READ_SCHEMA_TOOL: Tool = {
  name: "get_database_schema",
  description: "Mengambil daftar tabel, nama kolom, dan tipe data dari database PostgreSQL.",
  inputSchema: {
    type: "object",
    properties: {},
    required: []
  }
};

const EXECUTE_QUERY_TOOL: Tool = {
  name: "execute_read_query",
  description: "Mengeksekusi query SQL SELECT yang aman untuk membaca data. Hanya menerima statement SELECT.",
  inputSchema: {
    type: "object",
    properties: {
      sql: {
        type: "string",
        description: "Query SQL SELECT yang valid."
      }
    },
    required: ["sql"]
  }
};

const server = new Server(
  {
    name: "mcp-postgres-server",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [READ_SCHEMA_TOOL, EXECUTE_QUERY_TOOL],
  };
});

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  if (name === "get_database_schema") {
    try {
      const queryText = `
        SELECT 
          table_name, 
          column_name, 
          data_type 
        FROM 
          information_schema.columns 
        WHERE 
          table_schema = 'public'
        ORDER BY 
          table_name, ordinal_position;
      `;
      const result = await dbPool.query(queryText);
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(result.rows, null, 2),
          },
        ],
      };
    } catch (error: any) {
      return {
        isError: true,
        content: [
          {
            type: "text",
            text: `Gagal membaca skema: ${error.message}`,
          },
        ],
      };
    }
  }

  if (name === "execute_read_query") {
    const sql = String(args?.sql || "").trim();
    const normalizedSql = sql.toUpperCase();

    if (!normalizedSql.startsWith("SELECT")) {
      return {
        isError: true,
        content: [
          {
            type: "text",
            text: "Akses ditolak: Hanya query SELECT yang diizinkan.",
          },
        ],
      };
    }

    const forbiddenKeywords = ["INSERT", "UPDATE", "DELETE", "DROP", "ALTER", "TRUNCATE", "GRANT", "REVOKE"];
    const hasForbidden = forbiddenKeywords.some(keyword => normalizedSql.includes(keyword));
    if (hasForbidden) {
      return {
        isError: true,
        content: [
          {
            type: "text",
            text: "Akses ditolak: Query mengandung kata kunci modifikasi data terlarang.",
          },
        ],
      };
    }

    try {
      const result = await dbPool.query(sql);
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(result.rows, null, 2),
          },
        ],
      };
    } catch (error: any) {
      return {
        isError: true,
        content: [
          {
            type: "text",
            text: `Gagal mengeksekusi query: ${error.message}`,
          },
        ],
      };
    }
  }

  return {
    isError: true,
    content: [
      {
        type: "text",
        text: `Tool '${name}' tidak ditemukan.`,
      },
    ],
  };
});

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("MCP Postgres Server berjalan via stdio.");
}

main().catch((error) => {
  console.error("Fatal error pada process utama:", error);
  process.exit(1);
});

5. Integrasi MCP Server dengan Claude Desktop Client

Daftarkan MCP Server yang telah selesai dibangun ke dalam file konfigurasi Client MCP (misalnya Claude Desktop App).

Lokasi file konfigurasi sistem:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json

Tambahkan entri registrasi server pada objek mcpServers:

{
  "mcpServers": {
    "postgres-local": {
      "command": "npx",
      "args": [
        "tsx",
        "/path/absolut/ke/mcp-postgres-server/src/index.ts"
      ],
      "env": {
        "DATABASE_URL": "postgres://ai_readonly:PasswordAmanAI2025!@localhost:5432/db_bisnis"
      }
    }
  }
}

Ganti /path/absolut/ke/mcp-postgres-server/src/index.ts sesuai lokasi absolut direktori di sistem target. Simpan file lalu restart aplikasi Claude Desktop.

6. Verification dan Debugging Menggunakan MCP Inspector

Pengujian fungsi MCP Server dapat dilakukan independen tanpa Claude Desktop menggunakan alat resmi MCP Inspector.

Jalankan perintah berikut pada terminal proyek:

npx @modelcontextprotocol/inspector npx tsx src/index.ts

Perintah tersebut akan me-launch antarmuka web debugging lokal. Buka tautan yang muncul pada browser (biasanya http://localhost:5173) lalu jalankan langkah verifikasi berikut:

  1. Klik List Tools. Pastikan tool get_database_schema dan execute_read_query terdeteksi secara otomatis.
  2. Pilih get_database_schema, klik Run Tool. Verifikasi bahwa struktur skema PostgreSQL dikembalikan dalam format JSON valid.
  3. Pilih execute_read_query, masukkan input JSON {"sql": "SELECT * FROM information_schema.tables LIMIT 5;"}, lalu jalankan. Verifikasi hasil eksekusi data baris.
  4. Uji skenario kegagalan: Masukkan query mutasi data seperti DELETE FROM users;. Pastikan server merespons dengan pesan penolakan akses.

7. Praktik Terbaik Pengamanan dan Optimasi Kinerja

Implementasi MCP Server pada lingkungan produksi memerlukan beberapa pengamanan tambahan:

  • Pembatasan Baris Data Auto-Limit: Tambahkan pembatas kata kunci LIMIT secara otomatis di tingkat server jika query dari AI Agent tidak menyertakan batasan jumlah baris data. Hal ini mencegah konsumsi RAM berlebih (Out-Of-Memory) pada Node.js.
  • Query Statement Timeout: Tetapkan nilai statement_timeout pada koneksi pg pool (misal 5000 ms) agar query yang menggantung tidak membebani Resource PostgreSQL.
  • Parsing SQL via AST: Jangan mengandalkan pengecekan berbasis RegEx sederhana. Gunakan parser SQL berbasis Abstract Syntax Tree (seperti pgsql-parser) untuk memverifikasi AST query secara akurat sebelum dieksekusi.
  • Penggunaan Database Views: Buat Database View khusus yang mengeksklusi kolom data sensitif (misalnya hash password, kredensial, atau data PII) lalu batasi akses role ai_readonly hanya pada View tersebut.

๐Ÿ“– Artikel Terkait