Connect Claude Code to Your Database: Query Real Data with MCP

Connect PostgreSQL to Claude Code via MCP, set up a read-only account with permission controls, and let Claude read schemas, write queries, and analyze data directly — no more manual schema copy-pasting.


Once you connect a database, Claude Code works differently. You don't need to paste table schemas into the prompt or explain field names — Claude connects directly, reads the schema itself, writes queries, and analyzes the results.

This article uses PostgreSQL as the example and walks through the full setup from configuration to real usage.

Which MCP Server to Use

The official @modelcontextprotocol/server-postgres supports:

  • Reading schema for all tables (field names, types, constraints)
  • Executing SQL queries and returning results
  • Multiple schemas

Read-only. This server is designed for queries only — no INSERT, UPDATE, or DELETE.

Installation

npm install -g @modelcontextprotocol/server-postgres

Configuration

Edit .claude/settings.json (project-level):

{
  "mcpServers": {
    "db": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-postgres",
        "postgresql://readonly_user:password@localhost:5432/myapp_production"
      ]
    }
  }
}

Connection string format: postgresql://USER:PASSWORD@HOST:PORT/DATABASE

To avoid storing the password in the config file, use an environment variable instead:

{
  "mcpServers": {
    "db": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-postgres"],
      "env": {
        "DATABASE_URL": "postgresql://readonly_user:password@localhost:5432/myapp_production"
      }
    }
  }
}

If .claude/settings.json is committed to git, make sure it contains no plaintext passwords. Put sensitive config in .claude/settings.local.json and add it to .gitignore.

Create a Read-Only Database User First

Don't give Claude an account with write access. Create a dedicated read-only user in PostgreSQL:

-- Create the read-only user
CREATE USER claude_readonly WITH PASSWORD 'your_password';

-- Grant connection access
GRANT CONNECT ON DATABASE myapp_production TO claude_readonly;

-- Grant schema usage
GRANT USAGE ON SCHEMA public TO claude_readonly;

-- Grant SELECT on all existing tables
GRANT SELECT ON ALL TABLES IN SCHEMA public TO claude_readonly;

-- Apply the same to future tables
ALTER DEFAULT PRIVILEGES IN SCHEMA public
  GRANT SELECT ON TABLES TO claude_readonly;

If there are tables you don't want Claude to access (like sessions or audit_logs), simply don't grant permissions on those tables, or move them to a separate schema.

Verify the Connection

Restart Claude Code and ask:

List all the tables in the database

If Claude returns a list of table names instead of saying "I don't have access to a database," the MCP connection is working.

Real Usage Examples

Once configured, Claude can do quite a bit more. Here are a few practical scenarios.

Scenario 1: Understanding data structure

What's the relationship between the users and orders tables?
Are there any foreign key constraints?

Claude queries information_schema directly to read table structure and constraints, then gives you a clear explanation — no schema copy-pasting required.

Scenario 2: Data analysis

Of the users who signed up in the last 30 days,
what percentage have placed at least one order?
Break it down by signup channel.

Claude writes the SQL, runs it, and analyzes the results. If the query fails (wrong field name, for example), it sees the error and fixes it itself.

Scenario 3: Debugging

Find orders with status = 'pending' that are more than 7 days old.
How many are there, and when was the most recent one created?

This kind of ad-hoc lookup used to require writing SQL yourself or opening a database tool. Now you just describe what you need.

Scenario 4: Development support

I want to add a last_login_at column to the users table.
Write the migration SQL and check whether the existing indexes are sufficient.

Claude reads the current table structure, writes the migration, and analyzes the indexes — all in one step.

Controlling What Claude Can See

By default, Claude can access the full schema and data of every table you've granted permissions on. Tighten this up as needed.

Table-level control

Grant access to specific tables only:

-- Grant specific tables instead of all
GRANT SELECT ON TABLE users, orders, products TO claude_readonly;
-- sessions, payment_tokens, audit_logs get no access

Filter sensitive columns with a view

If a table contains sensitive fields (password hashes, API keys, phone numbers), create a view that hides them:

CREATE VIEW users_safe AS
  SELECT id, email, created_at, plan, status
  FROM users;
  -- excludes password_digest, phone, payment_method_id

GRANT SELECT ON users_safe TO claude_readonly;
-- don't grant access to the underlying table

Claude can only query the view and never sees the raw fields.

Row limits

@modelcontextprotocol/server-postgres applies a default row limit on query results (typically 1,000 rows), which prevents Claude from pulling entire tables. This is a sensible default — don't increase it.

Connecting to a Remote Database

Production databases typically don't expose their ports directly. Use an SSH tunnel:

# Forward localhost:5433 to the remote server's port 5432
ssh -L 5433:localhost:5432 [email protected] -N

Then use the local port in your config:

"postgresql://readonly_user:password@localhost:5433/myapp_production"

Alternatively, use a connection pooler like pgBouncer.

Multiple Databases

If your project uses multiple databases (primary, analytics, cache), configure multiple MCP Servers:

{
  "mcpServers": {
    "main-db": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-postgres", "postgresql://...primary..."]
    },
    "analytics-db": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-postgres", "postgresql://...analytics..."]
    }
  }
}

Claude connects to both. Just tell it which one to query.

What Changes in Practice

The most noticeable shift after connecting a database: data-related conversations require no setup. You don't explain the schema, you don't describe field types — you state the need and Claude figures out the rest.

It's most valuable during debugging. The old flow: you query the database → copy results → paste to Claude → it analyzes. The new flow: you describe the problem → Claude queries directly → you get conclusions.