# Let Claude Code and Cursor query your database (safely) — the MCP setup

> Source: <https://dev.to/wolfhound1995/let-claude-code-and-cursor-query-your-database-safely-the-mcp-setup-1376>
> Published: 2026-09-08 17:20:07+00:00

An agent that can read your database answers questions you'd otherwise open a SQL editor for. "Why did signups drop on Tuesday?" "Which orders have been stuck in `pending` for more than an hour?" "What's actually in the `events` table?" With a query capability the model answers in seconds; without one it guesses. The useful part isn't the SQL — Claude Code and Cursor both write SQL fine — it's giving the agent a way to *run* a read without handing it your credentials. That handoff is the whole friction, and it's what this post walks through for both tools.

We've written separately about [why pasting a database connection string into an agent is the move you can't undo](https://termal.in/blog/give-an-ai-agent-database-access/) — the credential is bearer access, it can write and drop as well as read, and once it's passed through a model's context you can't un-share it. Read that for the safety model; here we take it as settled and get the wiring done. The short version: the client holds the credential, the agent gets a scoped, read-only-by-default *ask*.

One expectation to set: for Claude Code and Cursor, database access runs through the **local** stdio MCP server, so the agent needs to be on the same machine as your unlocked desktop app. (There's a hosted path too — the connectors for claude.ai and ChatGPT can query an *enrolled* server's databases passwordless through `termal.in/api/v1/mcp`, using the server's own local database trust — but for a local IDE agent, the local server is the one you want.)

This is off by default, and deliberately so. Open **Settings → Agent** and enable **"Let agents query your databases."** The subtitle spells out the posture: *off by default, and stays read-only unless a connection grants full access.* Until you flip it, every database tool returns `database access for agents is off — enable it in Settings → Agent`, full stop.

Two defaults are worth internalizing before you go further:

The credential itself never moves: the connection's host, port and password stay in the app's vault, and the agent only gets to *ask the running app* to run a query and hand back rows. There's no connection string in a config file for the agent — or a prompt injection — to read.

**Claude Code** — one command:

```
claude mcp add termalin -- <path>/termalin-mcp
```

Replace `<path>` with wherever the `termalin-mcp` binary lives (the app can point you at it). Restart Claude Code and it picks up the tools.

**Cursor** — Cursor reads MCP servers from a JSON file: `.cursor/mcp.json` in a project root for that project only, or `~/.cursor/mcp.json` to make the server available everywhere. For a local stdio server the entry is a command and its arguments:

```
{
  "mcpServers": {
    "termalin": {
      "command": "<path>/termalin-mcp",
      "args": []
    }
  }
}
```

The global file is the sensible choice — database access isn't a per-repo concern. Restart Cursor (or reload the MCP list in its settings) and confirm the server shows as running. This is the same registration the [Cursor SSH walkthrough](https://termal.in/blog/cursor-ssh-access-for-agents/) and the [Claude Code one](https://termal.in/blog/connect-claude-code-to-your-server-over-ssh/) use — one server, and it now carries the database tools alongside the SSH ones.

With the server registered and the switch on, the agent sees four database tools:

```
data_list     # your saved connections: id, name, engine, and whether the agent may write
data_tables   # tables / collections / measurements for a connection, with row counts
data_schema   # one table's columns: name, type, nullable, primary key
data_query    # run a query in the connection's own language; default cap 200 rows
```

They mirror how you'd explore a database by hand: list what's connected, see the tables, describe a table, run the query. `data_query` speaks whatever the connection speaks — SQL for the relational engines and ClickHouse, Cypher for Neo4j, a Redis command, a `collection {filter}` for MongoDB — and returns rows up to a cap you can raise per call (`maxRows`), defaulting to **200** so an agent can't accidentally pull a million rows into its context.

Now give it a question you'd actually ask, not a demo query:

Signups dropped on Tuesday according to the dashboard. Using the analytics database, figure out what happened.

Watch the agent work the tools in order:

`data_list`` analytics` connection, sees the engine is Postgres, and notes it's read-only for the agent.`data_tables`` signups` and `signup_events`, and reads the row counts to know what's worth querying.` data_schema``created_at`, `source`, `status`, …) and now knows how to slice by day and by source.` data_query``SELECT date_trunc('day', created_at) AS day, source, count(*) FROM signups WHERE created_at >= now() - interval '10 days' GROUP BY 1, 2 ORDER BY 1` and reads the rows back.
The rows tell the story — maybe one `source` went to zero on Tuesday (a broken referral link), maybe the total held but `status = 'failed'` spiked (a provider outage). Either way the agent formed its query from the real schema, not a guess, and never held anything more than the ability to ask. If a result truncates at the 200-row cap, it says so and narrows the query rather than pushing for more.

Brief, because the [safety post](https://termal.in/blog/give-an-ai-agent-database-access/) covers it in full — but the defaults you're relying on:

`SELECT`, `SHOW`, `DESCRIBE`, `EXPLAIN`, `WITH`, `PRAGMA`. On Redis, only read commands; on MongoDB, an aggregation with `$out`/`$merge` is blocked; on Neo4j, `CREATE`/` MERGE`/` DELETE`/` SET`/` DROP` are blocked. A confused or hijacked model gets a read, not a For anything sensitive, do both: point the Data connection at a read replica or a least-privilege database role *and* leave the agent read-only. The client-side gate catches mistakes; the database grant sets the ceiling.

Most "connect your agent to a database" guides end at pasting a connection string into a config file, which quietly grants an agent the same write-and-drop power your app has. Termalin's Data client inverts that: the app is the custodian, the agent gets four scoped tools driven through the running window, and the whole capability is read-only until you decide otherwise and off with a single switch. It's the same custody idea we apply to [SSH access for agents](https://termal.in/blog/connect-claude-code-to-your-server-over-ssh/) and to [what an MCP server actually is](https://termal.in/blog/what-is-an-mcp-server/) — a database is just the version where a bad write is measured in rows. Start with one connection, keep it read-only, and ask something real before you consider widening anything.

*Termalin is a free, cross-platform SSH and [database client](https://termal.in/database-client/) with a built-in MCP server and a per-connection agent policy — [download it](https://termal.in/download/), or read how it [handles credentials safely](https://termal.in/security/).*
