Claude Code & MCP MasterclassModule 4

4.1Building Database MCP Servers — SQLite, PostgreSQL & MongoDB

35 min 4 code blocks Practice Lab Quiz (4Q)

Building Database MCP Servers — SQLite, PostgreSQL & MongoDB

The real power of MCP (Model Context Protocol) becomes clear when you connect Claude to your actual data. Instead of pasting database records into chat windows and hoping Claude understands the schema, you build a Database MCP Server that gives Claude direct, structured access to query, insert, update, and analyze your database. For Pakistani freelancers and developers, this means Claude can browse your client's SQLite project database, generate migration scripts for their PostgreSQL production server, or analyze MongoDB collections for patterns — all from a single Claude Code conversation. This lesson builds all three.

Section 1: Database MCP Architecture

A Database MCP Server is a Node.js or Python server that exposes your database operations as MCP "tools" that Claude can call. The architecture:

code
Claude Code CLI
     ↓ calls tool
MCP Server (localhost)
     ↓ executes query
Database (SQLite / PostgreSQL / MongoDB)
     ↓ returns results
Claude Code CLI
     ↓ shows answer

Claude never has direct database credentials — it calls the MCP server, which has the credentials and enforces what operations are allowed. This is important for security: you can restrict Claude to read-only operations on production databases.

Section 2: Building the SQLite MCP Server

SQLite is perfect for local development and single-file project databases. Many Pakistani freelancers' projects use SQLite for APIs, bots, and internal tools.

Create a new file: sqlite-mcp-server.py

python
#!/usr/bin/env python3
import sqlite3
import json
import sys
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp import types

app = Server("sqlite-database")

# Configure your database path
DB_PATH = "./project.db"

@app.list_tools()
async def list_tools():
    return [
        types.Tool(
            name="query_database",
            description="Execute a SELECT query on the SQLite database and return results",
            inputSchema={
                "type": "object",
                "properties": {
                    "query": {"type": "string", "description": "SQL SELECT query to execute"},
                    "limit": {"type": "integer", "description": "Max rows to return (default 50)"}
                },
                "required": ["query"]
            }
        ),
        types.Tool(
            name="get_schema",
            description="Get the complete database schema — all tables, columns, and types",
            inputSchema={"type": "object", "properties": {}}
        ),
        types.Tool(
            name="insert_record",
            description="Insert a new record into a table",
            inputSchema={
                "type": "object",
                "properties": {
                    "table": {"type": "string"},
                    "data": {"type": "object", "description": "Column:value pairs to insert"}
                },
                "required": ["table", "data"]
            }
        )
    ]

@app.call_tool()
async def call_tool(name: str, arguments: dict):
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()

    if name == "query_database":
        query = arguments["query"]
        limit = arguments.get("limit", 50)
        # Safety: only allow SELECT
        if not query.strip().upper().startswith("SELECT"):
            return [types.TextContent(type="text", text="Error: Only SELECT queries are allowed")]
        cursor.execute(query + f" LIMIT {limit}")
        rows = [dict(row) for row in cursor.fetchall()]
        return [types.TextContent(type="text", text=json.dumps(rows, indent=2))]

    elif name == "get_schema":
        cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
        tables = [row[0] for row in cursor.fetchall()]
        schema = {}
        for table in tables:
            cursor.execute(f"PRAGMA table_info({table})")
            columns = [{"name": col[1], "type": col[2]} for col in cursor.fetchall()]
            schema[table] = columns
        return [types.TextContent(type="text", text=json.dumps(schema, indent=2))]

    elif name == "insert_record":
        table = arguments["table"]
        data = arguments["data"]
        columns = ", ".join(data.keys())
        placeholders = ", ".join(["?" for _ in data])
        cursor.execute(f"INSERT INTO {table} ({columns}) VALUES ({placeholders})", list(data.values()))
        conn.commit()
        return [types.TextContent(type="text", text=f"Inserted record with ID {cursor.lastrowid}")]

    conn.close()

async def main():
    async with stdio_server() as (read, write):
        await app.run(read, write, app.create_initialization_options())

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

Section 3: PostgreSQL and MongoDB Extensions

PostgreSQL MCP Server: For production databases (common in deployed Pakistani SaaS apps), replace SQLite with asyncpg:

python
import asyncpg

# Replace the sqlite3 connection with:
conn = await asyncpg.connect(
    host=os.getenv("PG_HOST", "localhost"),
    database=os.getenv("PG_DB", "myapp"),
    user=os.getenv("PG_USER", "postgres"),
    password=os.getenv("PG_PASSWORD")
)
rows = await conn.fetch(query)
results = [dict(row) for row in rows]

MongoDB MCP Server: For NoSQL databases (popular in Pakistani startups using Atlas free tier):

python
from pymongo import MongoClient

client = MongoClient(os.getenv("MONGO_URI", "mongodb://localhost:27017"))
db = client["myapp"]

# Tool: find_documents
def find_documents(collection_name, filter_query, limit=50):
    collection = db[collection_name]
    docs = list(collection.find(filter_query).limit(limit))
    # Convert ObjectId to string for JSON serialization
    for doc in docs:
        doc["_id"] = str(doc["_id"])
    return docs
Practice Lab

Practice Lab

Exercise 1: Install the MCP Python SDK: pip install mcp. Create a simple SQLite database with 3 tables representing a Pakistani business: customers (id, name, phone, city), orders (id, customer_id, product, amount_pkr, date), products (id, name, category, price_pkr). Insert 10 sample rows. Connect your SQLite MCP server to Claude Code by adding it to your claude_mcp_config.json. Ask Claude: "How many orders are from Karachi customers?" — verify it runs the correct JOIN query.

Exercise 2: Add a new tool to your SQLite MCP server called run_analytics. This tool takes a metric parameter (e.g., "total_revenue", "top_customers", "monthly_orders") and executes a pre-defined safe aggregation query for each metric. This shows how to expose complex analytics to Claude without giving it unrestricted SQL access.

Exercise 3: Create a CLAUDE.md file in your project that describes your database schema in detail. Ask Claude Code (with the MCP server running): "Look at the schema and suggest 3 database optimizations — missing indexes, normalization issues, or constraint problems." Compare Claude's suggestions against what you manually identified. This is AI-assisted database review in practice.

Key Takeaways

  • Database MCP Servers give Claude structured, secure access to query and modify databases — far more powerful than pasting schema into chat windows
  • Always restrict production database MCP servers to SELECT-only operations — the insert/update/delete tools should require a confirmation flag or be disabled entirely
  • SQLite is the best starting point for learning Database MCP — zero setup, single file, works on every machine without a server
  • The schema awareness tool (get_schema) is the most useful tool to build first — once Claude knows your schema, all subsequent queries and code generation become dramatically more accurate

Lesson Summary

Includes hands-on practice lab4 runnable code examples4-question knowledge check below

Quiz: Building Database MCP Servers — SQLite, PostgreSQL & MongoDB

4 questions to test your understanding. Score 60% or higher to pass.