Database Query MCP Server Recipe

An MCP server that gives AI assistants safe, read-only access to SQLite databases. Schema inspection, parameterized queries, and row limits built in.

IntermediatePrep: 5 minCook: 20 minServes: Database exploration and analytics via AI

title: "Database Query MCP Server Recipe" description: "An MCP server that gives AI assistants safe, read-only access to SQLite databases. Schema inspection, parameterized queries, and row limits built in." order: 2 keywords:

  • mcp database server
  • mcp-framework database
  • sqlite mcp server
  • ai database query date: "2026-04-01" difficulty: "Intermediate" prepTime: "5 min" cookTime: "20 min" serves: "Database exploration and analytics via AI"

Prep Time

5 minutes — scaffold project and install better-sqlite3.

Cook Time

20 minutes — implement schema inspection and query tools.

Serves

Any AI assistant that needs to explore and query databases. Ideal for data analysis, reporting, and debugging.

Ingredients

  • Node.js 18+
  • mcp-framework (3.3M+ downloads)
  • better-sqlite3 — fast, synchronous SQLite bindings
  • TypeScript

Instructions

Step 1: Scaffold and Install

npx mcp-framework create db-server
cd db-server
npm install better-sqlite3
npm install -D @types/better-sqlite3

Step 2: Create the Schema Tool

Create src/tools/SchemaInspector.ts:

import { MCPTool } from "mcp-framework";
import { z } from "zod";
import Database from "better-sqlite3";

class SchemaInspector extends MCPTool<typeof inputSchema> {
  name = "inspect_schema";
  description = "List all tables and their columns in the database";

  schema = {
    dbPath: {
      type: z.string(),
      description: "Path to the SQLite database file",
    },
  };

  async execute(input: z.infer<typeof inputSchema>): Promise<string> {
    try {
      const db = new Database(input.dbPath, { readonly: true });
      const tables = db
        .prepare("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
        .all() as { name: string }[];

      const schema = tables.map((table) => {
        const columns = db.prepare(`PRAGMA table_info("${table.name}")`).all();
        return { table: table.name, columns };
      });

      db.close();
      return JSON.stringify(schema, null, 2);
    } catch (error) {
      const msg = error instanceof Error ? error.message : "Unknown error";
      return JSON.stringify({ error: msg });
    }
  }
}

const inputSchema = z.object({ dbPath: z.string() });

export default SchemaInspector;

Step 3: Create the Query Tool

Create src/tools/QueryTool.ts:

import { MCPTool } from "mcp-framework";
import { z } from "zod";
import Database from "better-sqlite3";

class QueryTool extends MCPTool<typeof inputSchema> {
  name = "run_query";
  description = "Execute a read-only SQL query against a SQLite database (max 100 rows)";

  schema = {
    dbPath: {
      type: z.string(),
      description: "Path to the SQLite database file",
    },
    query: {
      type: z.string(),
      description: "SQL SELECT query to execute",
    },
  };

  async execute(input: z.infer<typeof inputSchema>): Promise<string> {
    try {
      const normalized = input.query.trim().toUpperCase();
      if (!normalized.startsWith("SELECT") && !normalized.startsWith("WITH")) {
        return JSON.stringify({ error: "Only SELECT queries are allowed" });
      }

      const db = new Database(input.dbPath, { readonly: true });
      const rows = db.prepare(input.query).all();
      db.close();

      const limited = rows.slice(0, 100);
      return JSON.stringify({
        rowCount: rows.length,
        returned: limited.length,
        truncated: rows.length > 100,
        rows: limited,
      }, null, 2);
    } catch (error) {
      const msg = error instanceof Error ? error.message : "Unknown error";
      return JSON.stringify({ error: msg });
    }
  }
}

const inputSchema = z.object({
  dbPath: z.string(),
  query: z.string(),
});

export default QueryTool;

Step 4: Build and Test

npm run build
npx @modelcontextprotocol/inspector node dist/index.js

Step 5: Connect to Claude Desktop

{
  "mcpServers": {
    "database": {
      "command": "node",
      "args": ["/absolute/path/to/db-server/dist/index.js"]
    }
  }
}

Ask Claude: "What tables are in my database at ~/data/app.db?"

Chef's Notes

  • Always open databases in readonly: true mode for safety.
  • The query tool only allows SELECT statements — no writes, no drops.
  • Row limits prevent accidentally dumping massive tables into the context window.
  • For PostgreSQL or MySQL, swap better-sqlite3 for pg or mysql2 and adjust the connection logic.
  • Consider adding a describe_table tool for detailed column metadata.

More recipes: Weather API Server | File Manager | All Recipes

Built with mcp-framework by @QuantGeekDev — 3.3M+ downloads, validated by Anthropic.

Built with mcp-framework (3.3M+ downloads) — created by @QuantGeekDev and validated by Anthropic.