# Connecting a database

> **What you will learn**
> How to attach a database to a workspace, which four kinds are supported, and
> the one thing you must settle **before** attaching — not a tool setting, but
> **account permissions.**
>
> **If tables and SQL are new,** start with [What a database is](/guide/it-database).

## What changes

Until now a person pulled the data and pasted it into the conversation. Attach a
database and the agent **queries it directly.**

```
"Break last month's returns down by region and lay it out in a table"
  -> list tables -> inspect columns -> query -> table
```

The grounding from [attaching sources](/guide/ai-grounding) extends from
documents to **your own data.**

## Four kinds are supported

| Kind | Connection string |
|---|---|
| PostgreSQL | `postgres://…` or `postgresql://…` |
| MySQL | `mysql://…` |
| MongoDB | `mongodb://…` · `mongodb+srv://…` |
| SQLite | a file path — `file:…` or `.sqlite` · `.sqlite3` · `.db` |

**You do not pick the kind separately.** It is inferred from the scheme at the
front of the connection string. Which means a missing scheme means no connection
— writing `user:pass@host/db` without `postgres://` is most first failures.

## How to attach it

Same route as **"outward"** in [connecting an MCP server](/guide/cn-mcp). Add
the database MCP address as a connection and pass the connection string as a
**header.**

```
X-DB-Connection-String: postgres://reader:••••@db.example.com:5432/shop
```

### Attaching more than one

Put a **name** in the middle of the header name.

```
X-DB-Primary-Connection-String:   postgres://reader:••••@db1/app
X-DB-Analytics-Connection-String: mysql://reader:••••@db2/analytics
```

They are then distinguished as `primary` and `analytics`. With a single
connection the name is `default`.

> **Never paste a connection string into a conversation.** It contains a
> password. Put it in the connection settings only — the same principle as
> [managing credentials](/guide/cn-mcp).

## The ten tools the agent gets

Sorted into the three classes from [tools are
permissions](/guide/mcp-security):

| Class | Tools | Nature |
|---|---|---|
| Connection | `db_connect` · `db_disconnect` · `db_list_connections` | state |
| Read | `db_list_tables` · `db_describe_table` · `db_select` | changes nothing |
| **Raw query** | **`db_query`** | **runs the SQL it is handed, as given** |
| **Irreversible** | `db_insert` · `db_update` · **`db_delete`** | writes and deletes |

### Read that `db_query` row again

`db_query` is not read-only. **It executes the SQL string it receives.** Turn off
`db_insert`, `db_update` and `db_delete` and you still have not blocked writes if
`db_query` is on.

`db_delete` does require a `where` clause, which prevents whole-table deletions —
but deleted rows do not come back.

## So block it with the account

A read-only account is more reliable than turning tools off.

```
✗ Attaching the account the application uses
✓ Creating a new account with SELECT only, and attaching that
✓ Attaching a replica or an analytics database instead of production
```

**The difference is who enforces it.** Tool settings can be changed by someone
inside the workspace; account permissions are enforced by the database. [Least
privilege](/guide/mcp-security) here is a question about **the database account**,
not the tool list.

## Connections are cleaned up after 30 minutes

A connection opened by `db_connect` closes **30 minutes after it was last used.**
Mid-way through a long job you will see exactly this:

```
Connection not found: … It may have expired or been disconnected.
```

**Just call `db_connect` again.** For unattended work such as a schedule, put
"reconnect if the connection has dropped" into the instruction.

## Before you attach it

```
□ Is the account read-only?
□ Is this production, or a replica?
□ Is db_query enabled?
   -> if so, writes can only be blocked by the account, not by tool settings
□ Do insert, update and delete have human approval?
□ Does this workspace also have an inbound path (widget, channel, email)?
```

**The last line matters most.** It creates a path where a sentence from outside
becomes a query — the structure from [when what it reads becomes a
command](/guide/mcp-prompt-injection). If an email body from a customer can reach
`db_query`, a read-only account is **a condition, not a choice.**

## Common misconceptions

### "Can't I just tell it to only SELECT?"

Deciding by instruction and blocking by permission are different things.
Instructions can [drift or be invented](/guide/ai-hallucination), and they can be
overridden by outside input. **Blocking is the account's job.**

### "There are a lot of tables — does it see all of them?"

`db_list_tables` lists them and `db_describe_table` shows the columns. But only
what the account can see is visible — which is why **narrowing the account is
narrowing the scope.**

---

## Check yourself

**1. Why is turning off every write tool not enough?**

<details>
<summary>Answer</summary>

**Because `db_query` executes the SQL it is handed.** It is not read-only, so it
alone permits writes.
</details>

**2. Why is a read-only account more reliable than a tool setting?**

<details>
<summary>Answer</summary>

**Different enforcers.** Tool settings can be changed inside the workspace;
account permissions are enforced by the database itself.
</details>

**3. Why is an inbound path in the same workspace dangerous?**

<details>
<summary>Answer</summary>

**A sentence from outside can turn into a query.** It is the same structure as
prompt injection, so in that configuration a read-only account is a condition
rather than a choice.
</details>

---

Next, try the attached tools by hand → [The playground](/guide/cn-playground)
