GC-301f · Module 2

SQL Databases

3 min read

SQL database tools for Gemini CLI connect through standard client libraries — pg for PostgreSQL, mysql2 for MySQL, better-sqlite3 for SQLite. The connection string comes from an environment variable. The tool opens a connection pool at server startup and reuses it across tool calls. Never open a new connection per query. Pool configuration matters: set a maximum of 5 connections for MCP server tools. These are not web servers handling hundreds of concurrent users — they serve one AI agent making sequential queries.

Parameterized queries are non-negotiable. Every value that comes from Gemini — table names excluded — goes through parameter binding. The query template uses $1, $2 placeholders (PostgreSQL) or ? placeholders (MySQL), and the values array passes separately. This is not optional security hygiene. Gemini generates tool inputs from natural language. A user saying "find orders where name is Robert; DROP TABLE orders" will produce exactly that string as a parameter. Parameterization neutralizes it.

import pg from "pg";

const pool = new pg.Pool({
  connectionString: process.env.DATABASE_URL,
  max: 5,
  idleTimeoutMillis: 30000,
});

async function queryOrders(status: string, limit: number = 20) {
  const result = await pool.query(
    `SELECT id, customer_name, total, created_at
     FROM orders
     WHERE status = $1
     ORDER BY created_at DESC
     LIMIT $2`,
    [status, Math.min(limit, 100)]
  );

  return {
    count: result.rowCount,
    orders: result.rows,
  };
}

Read-only access patterns protect production data. Create a dedicated database user for the MCP server with SELECT-only permissions. Revoke INSERT, UPDATE, DELETE, and DDL privileges. If a tool needs write access — creating a record, updating a status — use a separate connection with a scoped user that can only modify specific tables. The principle: minimum privilege per tool. A reporting tool should never have the ability to delete data.