# Databases

> Step-by-step connection and configuration for MongoDB, PostgreSQL, MySQL, Redis, ClickHouse, Snowflake, BigQuery, DynamoDB, Supabase and Neon.

Source: https://triagic.com/docs/integrations/databases

Ten integrations that let the agent look up the row behind a ticket. Every one of
them should be given a credential that cannot write; where the server enforces that
itself, this page says so, and where it does not, it says that too.

## MongoDB [#mongodb]

`mongodb` · runs `mongodb-mcp-server` via **npx** · read-only enforced by the server's
`--readOnly` flag, which blocks writes and admin commands.

Overview, prompts and the tool list: [/integrations/mongodb](/integrations/mongodb).

1. **Create a read-only user.** In `mongosh`, against the database the tickets are about:

   ```js
   db.getSiblingDB("admin").createUser({
     user: "triagic",
     pwd: "…",
     roles: [{ role: "read", db: "orders" }]
   })
   ```

   Note which database the user is *defined* in: that is the `authSource`, and it's the
   commonest cause of a rejected login.

2. **Assemble the connection string**, including that auth source:
   `mongodb+srv://triagic:…@cluster0.abc.mongodb.net/orders?authSource=admin`. For Atlas,
   add the machine's egress IP to the cluster's IP access list first.

3. **Put TLS in the URI too.** There are no separate certificate fields, because the
   driver reads all of it from the connection string:

   | Parameter                                | What it does                                                     |
   | ---------------------------------------- | ---------------------------------------------------------------- |
   | `tls=true`                               | Force TLS. Already implied by `mongodb+srv://`.                  |
   | `tlsCAFile=/path/ca.pem`                 | Verify against a private CA. &#x2A;*Note the capital `CA`.**     |
   | `tlsCertificateKeyFile=/path/client.pem` | A client certificate and its key, in one PEM.                    |
   | `tlsCertificateKeyFilePassword=…`        | Only if that key is encrypted.                                   |
   | `tlsAllowInvalidCertificates=true`       | Connect without checking the certificate. Encrypted, unverified. |
   | `authSource=admin`                       | The database the user is defined in.                             |
   | `authMechanism=MONGODB-X509`             | Authenticate with the client certificate itself.                 |
   | `authMechanism=MONGODB-AWS`              | Authenticate with AWS IAM credentials.                           |

   Paths are on the machine running Triagic, not the portal's host.

4. **Fill the form.**

   | Field                 | Required        | What to put                                                                                     |
   | --------------------- | --------------- | ----------------------------------------------------------------------------------------------- |
   | **Connection string** | yes             | The full URI above, credentials and TLS parameters included. Stored encrypted, returned masked. |
   | **Read-only**         | no, defaults on | Leave on. It is the server's own write block; turning it off is a deliberate act.               |

5. **Verify.** The desktop proves the credential with `list-databases` before reporting
   `running`.

| If it reports                        | It usually means                                                                                                                                                                                              |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection string is not valid`     | Not the URI: this is also what the server says when the host simply isn't reachable. Check the host and port answer from that machine, and that an SRV or replica-set name resolves, before editing anything. |
| `authentication failed` / `bad auth` | The username or password was rejected, or `authSource` points at the wrong database.                                                                                                                          |

## PostgreSQL [#postgresql]

`postgres` · runs Triagic's own read-only Postgres server, which ships inside the app
and is started with **node** · read-only three ways: one statement per call, a
`SELECT`-style first keyword, and a read-only transaction that is always rolled back.

Overview, prompts and the tool list: [/integrations/postgres](/integrations/postgres).

Triagic writes this server itself. The published package it replaced is archived, and
its read-only transaction could be escaped by sending `COMMIT;` followed by a second
statement. No maintained alternative enforces read-only for plain Postgres.

| Tool             | What it does                                                                                                                   |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `list_schemas`   | The schemas the role can see. System schemas are left out.                                                                     |
| `list_tables`    | Tables and views, across every schema or scoped to one, with an optional `LIKE` pattern on the name. Stops at 500 and says so. |
| `describe_table` | Columns, indexes and constraints for one table or view, named bare or as `schema.table`. A bare name is looked up in `public`. |
| `query`          | One read-only statement: `SELECT`, `WITH`, `VALUES`, `TABLE`, `SHOW` or `EXPLAIN`. 30 second timeout, 500 rows at most.        |

Three things keep `query` read-only. The statement must start with one of those
keywords, so `COMMIT`, `SET`, `DO` and `CALL` never reach the database. It is sent over
the protocol that carries exactly one statement, and Postgres itself refuses a second.
And it runs inside `BEGIN TRANSACTION READ ONLY`, which is rolled back whatever
happens. None of that replaces the read-only role in the first step below.

1. **Create a read-only role.**

   ```sql
   CREATE ROLE triagic LOGIN PASSWORD '…';
   GRANT CONNECT ON DATABASE billing TO triagic;
   GRANT USAGE ON SCHEMA public TO triagic;
   GRANT SELECT ON ALL TABLES IN SCHEMA public TO triagic;
   ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO triagic;
   ```

   The last line is the one people forget: without it, tables created later are invisible.

2. **Put certificates in the URL.** There are no separate certificate fields: the driver
   reads these from the connection string and loads each file from disk when it connects.

   | Parameter                  | What it does                            |
   | -------------------------- | --------------------------------------- |
   | `sslrootcert=/path/ca.pem` | Verify the server against a private CA. |
   | `sslcert=/path/client.crt` | A client certificate.                   |
   | `sslkey=/path/client.key`  | Its private key.                        |

   Paths are on the machine running Triagic, not the portal's host.

3. **Know that this driver reads `sslmode` more strictly than `psql` does.**

   Without `uselibpqcompat=true` in the URL, it treats `sslmode=prefer`, `require` and
   `verify-ca` as aliases for `verify-full`: all three verify the certificate chain *and*
   the hostname. libpq's `require` means "encrypt, don't verify", so a URL copied out of
   an RDS or Supabase console that connects fine with `psql` can fail here on certificate
   verification, with nothing pointing at the difference.

   Two ways out: add `uselibpqcompat=true` to get libpq's meanings back, or turn off
   **Verify TLS certificate**, which replaces the `sslmode` entirely.

4. **Fill the form.**

   | Field                      | Required        | What to put                                                                                                                                                 |
   | -------------------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
   | **Connection URL**         | yes             | `postgres://triagic:…@host:5432/billing`, plus any `sslrootcert` / `sslcert` / `sslkey`. Any `sslmode` you set is replaced by the toggle below.             |
   | **Verify TLS certificate** | no, defaults on | Turn **off** for managed Postgres (RDS, DigitalOcean, Supabase) and internal clusters whose certificate is signed by a private CA. Traffic stays encrypted. |

5. **Verify.** Health check is `SELECT 1`.

| If it reports                                                                   | It usually means                                                                                                                                                                                                                                                                                                          |
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `self-signed certificate in certificate chain`, `unable to verify local issuer` | The certificate isn't signed by a CA that machine trusts, which is usual for managed Postgres. Turn off **Verify TLS certificate**, set `sslrootcert=` to the provider's CA bundle, or install that bundle on the machine. The failure appears on the *health check*, not at startup, because the driver connects lazily. |
| `Hostname/IP does not match certificate`                                        | `sslmode=require` in the URL, which this driver reads as `verify-full`. Add `uselibpqcompat=true`, or use `sslrootcert=` with the name the certificate actually carries.                                                                                                                                                  |
| `only SELECT, WITH, VALUES, TABLE, SHOW, EXPLAIN statements run here`           | The agent tried a statement that is not a read. Nothing was sent to the database.                                                                                                                                                                                                                                         |
| `cannot insert multiple commands into a prepared statement`                     | Two statements in one call. Postgres refused the second; the first did not run either.                                                                                                                                                                                                                                    |
| `password authentication failed`                                                | The user or password in the URL. A password with special characters has to be percent-encoded inside the URL.                                                                                                                                                                                                             |

The connection URL travels to the server in an environment variable, not on the command
line, so the password does not appear in the machine's process list.

## MySQL [#mysql]

`mysql` · runs Triagic's own read-only MySQL server, which ships inside the app and is
started with **node** · read-only by construction: four fixed tools, and no tool that
runs a statement you write.

Overview, prompts and the tool list: [/integrations/mysql](/integrations/mysql).

This is the one integration in the catalog that isn't a published package. Every
published MySQL MCP server puts discovery behind a general `execute_sql` tool that runs
`UPDATE` and `DELETE` as readily as `SELECT`, so Triagic writes this server itself:

| Tool             | What it does                                                                                                                                      |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `list_databases` | Every database the credentials can reach, with character set and collation. MySQL's own system schemas are left out.                              |
| `list_tables`    | Tables and views, across every reachable database or scoped to one, with an optional `LIKE` pattern on the name. Stops at 500 tables and says so. |
| `describe_table` | Columns, indexes and foreign keys for one table, named bare or as `database.table`.                                                               |
| `sample_table`   | A few rows from a table: 10 by default, 50 at most.                                                                                               |

Three things keep it read-only. Every statement in the server is fixed text, and what
the agent supplies reaches MySQL only as a bound parameter. There's no general SQL
tool to hand a statement to. And each connection opens with
`SET SESSION TRANSACTION READ ONLY`, so the server itself would refuse a write.

1. **Create a read-only user.** The user's grants decide which databases and tables the
   agent can see at all: `information_schema` only shows a user what they hold a
   privilege on.

   ```sql
   CREATE USER 'triagic'@'%' IDENTIFIED BY '…';
   GRANT SELECT ON shop.* TO 'triagic'@'%';
   ```

   Repeat the `GRANT` for each database the agent should reach. MySQL grants are per
   host, so `'triagic'@'%'` and `'triagic'@'localhost'` are different users.

2. **Decide how much the connection should check the server's certificate.**

   Leaving **TLS mode** on *Driver default* encrypts the connection whenever the server
   offers TLS, and does not check the certificate. Fine inside a private network, not
   enough across one. *Required* also checks the certificate, against the trust store of
   the machine running Triagic.

   For a server whose certificate comes from a private CA, put that CA's bundle on the
   machine and choose *Verify CA*, or *Verify CA and hostname* if the name you connect to
   is the name on the certificate. Those two modes are the only ones that read
   **CA certificate path**; under *Required* it is ignored. Checking the hostname needs a
   hostname in **Host**: with a bare IP address the name check is skipped.

3. **Fill the form.** This one takes the parts, not a URL.

   | Field                   | Required                     | What to put                                                                                                                                                                                                |
   | ----------------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
   | **Host**                | yes                          | Hostname or IP reachable from the member's machine.                                                                                                                                                        |
   | **Port**                | no, defaults `3306`          |                                                                                                                                                                                                            |
   | **User**                | yes                          | `triagic`                                                                                                                                                                                                  |
   | **Password**            | no                           | Blank is accepted, and sent, for a passwordless user.                                                                                                                                                      |
   | **Default database**    | no                           | Leave blank to work across every database the credentials can reach. Setting one only makes it the default for a table named without a database; the agent can still list, describe and sample the others. |
   | **TLS mode**            | no, defaults to the driver's | See above. *Disabled* turns encryption off entirely.                                                                                                                                                       |
   | **CA certificate path** | no                           | Absolute path on the machine running Triagic, or paste the file itself. Read only under the two Verify modes.                                                                                              |

4. **Verify.** Health check is a `list_databases` call. It connects, authenticates and
   reads `information_schema`, so a wrong password, an unreachable host and a user with
   no grants all fail it.

| If it reports                                                             | It usually means                                                                                                                                                                                                                 |
| ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Access denied for user`, `ER_ACCESS_DENIED_ERROR`                        | MySQL rejected the credentials. Check **User** and **Password**, and that the user may connect from this machine's address: `'user'@'%'` and `'user'@'localhost'` are separate grants.                                           |
| `ER_NOT_SUPPORTED_AUTH_MODE`, or a message naming `caching_sha2_password` | The account uses `caching_sha2_password` and the connection is unencrypted. Set **TLS mode** to *Required* or higher.                                                                                                            |
| `self-signed certificate`, `unable to verify local issuer`                | The certificate isn't signed by a CA that machine trusts. Set **TLS mode** to *Driver default* to connect anyway, still encrypted, or point **CA certificate path** at the provider's CA bundle and use one of the Verify modes. |
| `no columns for db.table` or `no table db.table`                          | The table doesn't exist, or these credentials hold no privilege on it. MySQL shows a user only what they're granted, so the two look the same from here.                                                                         |
| `no database for "table"`                                                 | A table was named without a database and no **Default database** is set. Qualify it as `database.table`, or set the field.                                                                                                       |

## Redis [#redis]

`redis` · runs `redis-mcp-server` (the official `redis/mcp-redis`) via **uvx** ·
read-only enforced by a pinned tool allowlist: only the server's read tools are
exposed. The write tools (`set`, `hset`, `delete`, `expire`, `lpush`, `json_set`,
`zadd`, `xadd`, `publish`, …) are dropped at connect time, and so are the reads that
mutate on the way: `lpop`/`rpop` (destructive) and consumer-group reads that move
the pending-entries list.

Overview, prompts and the tool list: [/integrations/redis](/integrations/redis).

Credentials go through environment variables, so nothing here is visible in the
process list.

1. **Create a restricted ACL user** so the guarantee holds server-side too, not only in
   the allowlist:

   ```
   ACL SETUSER triagic on >… ~* +@read +@keyspace
   ```

   `+@read` grants every read command and nothing else; `+@keyspace` adds `SCAN`, `TYPE`
   and `DBSIZE`, which the tools and the health check use. On a managed Redis without
   ACLs, the shared `default` user works; the allowlist is then the only boundary.

2. **Fill the form.** This one takes the parts, not a URL.

   | Field                      | Required            | What to put                                                                                                                   |
   | -------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
   | **Host**                   | yes                 | Hostname or IP reachable from the member's machine.                                                                           |
   | **Port**                   | no, defaults `6379` |                                                                                                                               |
   | **Username**               | no                  | `triagic` for the ACL user above. Blank uses the `default` user.                                                              |
   | **Password**               | no                  |                                                                                                                               |
   | **Database number**        | no, defaults `0`    |                                                                                                                               |
   | **Use TLS**                | no                  | Turn on to encrypt the connection. Verified against this machine's trust store unless a CA path is set below.                 |
   | **CA certificate path**    | no                  | Absolute path on the machine running Triagic, for a server certificate signed by a private CA. Only read when TLS is on.      |
   | **Verify TLS certificate** | no, defaults on     | Turn off for a self-signed certificate. Traffic stays encrypted but the certificate is not checked. Only read when TLS is on. |

3. **Verify.** Health check is `dbsize`, a single `DBSIZE`, cheap and read-only. It
   fails on a wrong password where merely starting the server would not.

| If it reports                        | It usually means                                                                                                                         |
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `NOAUTH` / `Authentication required` | The server wants a password and none was sent.                                                                                           |
| `WRONGPASS`                          | Password rejected, or **Username** names an ACL user that doesn't exist. A blank username authenticates as `default`.                    |
| `NOPERM`                             | The ACL user lacks a command category the tools need. Grant `+@read +@keyspace` as above.                                                |
| `Connection refused` / timeout       | Host or port wrong, or the server isn't reachable from that member's machine.                                                            |
| `certificate verify failed`          | The certificate isn't trusted by that machine. Set **CA certificate path**, or turn off **Verify TLS certificate** if it is self-signed. |

## ClickHouse [#clickhouse]

`clickhouse` · runs `mcp-clickhouse` via **uvx** · read-only twice over: writes are
disabled in the server, and each query is sent with ClickHouse's own `readonly`
setting where the user's grants allow it.

Overview, prompts and the tool list: [/integrations/clickhouse](/integrations/clickhouse).

1. **Create a read-only user.**

   ```sql
   CREATE USER triagic IDENTIFIED BY '…' SETTINGS readonly = 1;
   GRANT SELECT ON events.* TO triagic;
   ```

2. **Optionally create a role, and name it on the form.**

   A role is applied with `SET ROLE` before every query, which makes it a per-statement
   bound rather than a description of the account. That is the strongest read-only
   control ClickHouse offers here. It holds even where the user's own grants are wider,
   and you can tighten it later without touching the user.

   ```sql
   CREATE ROLE readonly_analyst;
   GRANT SELECT ON events.* TO readonly_analyst;
   GRANT readonly_analyst TO triagic;
   ```

3. **For a private CA, install its certificate in this machine's OS trust store.** There
   is no CA path to set on the form: the server reads the operating system's trust store
   directly, so a certificate installed there is honoured and one sitting in a file is
   not.

4. **Fill the form.**

   | Field                      | Required        | What to put                                                                                                             |
   | -------------------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------- |
   | **Host**                   | yes             | `abc123.us-east-1.aws.clickhouse.cloud`, or your own hostname.                                                          |
   | **Port**                   | no              | Leave blank and it follows TLS: 8443 with, 8123 without.                                                                |
   | **User**                   | yes             | `triagic`, or `default` on a fresh Cloud service.                                                                       |
   | **Password**               | no              | Blank is allowed for a passwordless user.                                                                               |
   | **Database**               | no              | Default database for queries. Blank browses them all.                                                                   |
   | **Role**                   | no              | `readonly_analyst`, applied with `SET ROLE` on every query. Blank uses the user's default roles.                        |
   | **Use TLS**                | no, defaults on | Turn off only for a plain-HTTP cluster, typically self-hosted on 8123.                                                  |
   | **Certificate hostname**   | no              | Only when the certificate names a different host than the one you connect to: behind a load balancer, or reached by IP. |
   | **Verify TLS certificate** | no, defaults on | For a private CA, install the certificate in the OS trust store instead of turning this off.                            |

5. **Verify.** Health check is `list_databases`.

| If it reports                         | It usually means                                                                                                                 |
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `Code: 516` / `Authentication failed` | The user or password was rejected. Remember ClickHouse Cloud always requires TLS on 8443.                                        |
| `Code: 511` / unknown role            | **Role** names a role that does not exist, or one the user has not been granted.                                                 |
| A certificate hostname mismatch       | The certificate names a different host than the one you connect to. Set **Certificate hostname** to the name on the certificate. |

## Snowflake [#snowflake]

`snowflake` · runs Triagic's own read-only Snowflake server, which ships inside the app
and is started with **node** · it talks to Snowflake's SQL REST API, which runs one
statement per call. Snowflake has no read-only session, so the role's grants are the
real boundary.

Overview, prompts and the tool list: [/integrations/snowflake](/integrations/snowflake).

| Tool             | What it does                                                                                          |
| ---------------- | ----------------------------------------------------------------------------------------------------- |
| `list_databases` | `SHOW DATABASES`. Needs no warehouse, which is why it is the health check.                            |
| `list_schemas`   | Schemas in one database.                                                                              |
| `list_tables`    | Tables and views in one database, optionally one schema, with a case-insensitive pattern on the name. |
| `describe_table` | Columns of one table or view.                                                                         |
| `query`          | One read-only statement: `SELECT`, `WITH`, `SHOW`, `DESCRIBE`, `DESC` or `EXPLAIN`. 500 rows at most. |

Two things stand between the agent and a write before the role does. The statement
must start with one of those keywords and may not define an anonymous procedure or chain
statements with `->>`. And every request tells the REST API to accept exactly one
statement, so a second one is refused.

1. **Create a role that can only read, and a service user for it.**

   ```sql
   CREATE ROLE triagic_reader;
   GRANT USAGE ON WAREHOUSE analytics_wh TO ROLE triagic_reader;
   GRANT USAGE ON DATABASE prod TO ROLE triagic_reader;
   GRANT USAGE ON ALL SCHEMAS IN DATABASE prod TO ROLE triagic_reader;
   GRANT SELECT ON ALL TABLES IN DATABASE prod TO ROLE triagic_reader;
   CREATE USER triagic TYPE = SERVICE DEFAULT_ROLE = triagic_reader;
   GRANT ROLE triagic_reader TO USER triagic;
   ```

   Put `triagic_reader` in the **Role** field as well. The form sends it with every
   statement, so the queries run read-only even if someone later changes the user's
   default role.

2. **Pick how it signs in.** An account password does not work: Snowflake's SQL API takes
   a token or a key pair only.

   *Programmatic access token.* Create one for the user under **Settings →
   Authentication → Programmatic access tokens**, scoped to `triagic_reader`. Snowflake
   only accepts a token from a user covered by a network policy, even though creating the
   token needs none:

   ```sql
   CREATE NETWORK POLICY triagic_np ALLOWED_IP_LIST = ('0.0.0.0/0');
   ALTER USER triagic SET NETWORK_POLICY = triagic_np;
   ```

   Scope `ALLOWED_IP_LIST` to the egress range of the machine running Triagic where you
   can.

   *Key pair.* No network policy needed. Generate a PKCS#8 key, register the public half,
   and point the form at the private half:

   ```bash
   openssl genrsa 2048 | openssl pkcs8 -topk8 -inform PEM -out snowflake_rsa_key.p8 -nocrypt
   openssl rsa -in snowflake_rsa_key.p8 -pubout -out snowflake_rsa_key.pub
   ```

   ```sql
   ALTER USER triagic SET RSA_PUBLIC_KEY = '<contents of snowflake_rsa_key.pub without the BEGIN and END lines>';
   ```

3. **Fill the form.**

   | Field                         | Required              | What to put                                                                                                                                       |
   | ----------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
   | **Account identifier**        | yes                   | Just the identifier: `myorg-myaccount`, or a locator plus region like `xy12345.us-east-1`. &#x2A;*Never the full `…snowflakecomputing.com` URL.** |
   | **User**                      | yes                   | `triagic`                                                                                                                                         |
   | **Authentication**            | no, defaults to token | *Programmatic access token* or *Key pair*.                                                                                                        |
   | **Programmatic access token** | with token auth       | The token, not the account password.                                                                                                              |
   | **Private key path**          | with key pair         | Path to the `.p8` file on each machine running Triagic, or paste the file itself.                                                                 |
   | **Private key passphrase**    | no                    | Only for an encrypted key.                                                                                                                        |
   | **Warehouse**                 | yes                   | Queries need one. `list_databases` works without it; `SELECT`s do not.                                                                            |
   | **Role**                      | no                    | `triagic_reader`. Blank means the user's default role.                                                                                            |
   | **Database**                  | no                    | Default database, or leave blank and fully qualify names in queries.                                                                              |
   | **Schema**                    | no                    |                                                                                                                                                   |

4. **Verify.** Health check is `list_databases`.

| If it reports                                                                 | It usually means                                                                                                                                           |
| ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `394400` / `Programmatic access token is invalid`                             | The token was created for a different user, has expired, or the user has no network policy (see above).                                                    |
| `390303`, `Invalid OAuth access token`, or a bare `HTTP 401`                  | An account password in the token field. Create a programmatic access token, or switch **Authentication** to *Key pair*.                                    |
| `390101` / `JWT token is invalid`                                             | The public key is not registered on this exact user, **User** is not the login name, or the machine's clock is off. The signed token is valid for an hour. |
| `fetch failed`, `ENOTFOUND`, `HTTP 404` or `is not a valid Snowflake account` | Almost always the account identifier, not the network. The dotted `myorg.my_account` form is the classic mistake; use `myorg-my_account`.                  |
| `000606` / `No active warehouse`                                              | Set **Warehouse** to one the role has `USAGE` on.                                                                                                          |
| `only SELECT, WITH, SHOW, DESCRIBE, DESC, EXPLAIN statements run here`        | The agent tried a statement that is not a read. Nothing was sent to Snowflake.                                                                             |

## BigQuery [#bigquery]

`bigquery` · runs `mcp-server-bigquery` via **uvx**.

Overview, prompts and the tool list: [/integrations/bigquery](/integrations/bigquery).

> **Warning:** Writes are not blocked here
>
> The query tool runs whatever SQL it is given. IAM is the only control, so the service
> account's roles are the boundary.

1. **Create a service account with exactly two roles**: **BigQuery Data Viewer**
   (roles/bigquery.dataViewer) to read, and **BigQuery Job User** (roles/bigquery.jobUser)
   to run queries. Nothing else.

2. **Download its JSON key** and place it on the machine that runs Triagic, at an absolute
   path readable by the desktop app, e.g. `/opt/triagic/bigquery-sa.json`. Skip this if
   that machine already has Application Default Credentials configured.

3. **For impersonation or workload-identity federation, switch to Application Default
   Credentials.**

   The key-file path accepts a plain service-account key and nothing else. It's read by
   a loader that refuses `impersonated_service_account` and `external_account` JSON
   outright. If your organization issues either of those, choose **Application Default
   Credentials** and give the same file under **Credentials file path**, where it is read
   by the general Google credentials loader that understands all three formats.

   Leaving that path blank uses whatever `gcloud auth application-default login` already
   left on the machine.

4. **Decide which datasets the agent may see.** Naming them is the one least-privilege
   control this server offers that does not mean minting a narrower service account:
   nothing outside the list is visible, whatever the account could otherwise reach.
   Leave it blank and every dataset in the project is exposed.

5. **Fill the form.**

   | Field                        | Required              | What to put                                                                                                                               |
   | ---------------------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
   | **Project ID**               | yes                   | `my-project-123`                                                                                                                          |
   | **Location**                 | yes, defaults `US`    | Where the datasets live: a multi-region (`US`, `EU`) or a region like `europe-west9`. A mismatch returns no results rather than an error. |
   | **Authentication method**    | no, defaults key file | Application Default Credentials for impersonation or workload-identity federation.                                                        |
   | **Service account key path** | no                    | Absolute path to the JSON above. Plain service-account keys only. Blank falls back to Application Default Credentials.                    |
   | **Credentials file path**    | no                    | With ADC: a service-account key, impersonation config, or federation config. Blank uses the machine's own gcloud login.                   |
   | **Limit to datasets**        | no                    | `analytics_prod,billing`, comma-separated. Blank exposes every dataset.                                                                   |

6. **Verify.** Health check is `list-tables`.

| If it reports                                   | It usually means                                                                                                                                                   |
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `Could not automatically determine credentials` | No Google credentials on that machine. Set the key path, or run `gcloud auth application-default login` there.                                                     |
| `403` / `Access Denied`                         | A missing role: it needs both Data Viewer and Job User on this project.                                                                                            |
| A type or format error about the key file       | An impersonation or federation JSON given to **Service account key path**, which only takes plain service-account keys. Switch to Application Default Credentials. |
| A dataset you expect is missing                 | **Limit to datasets** is set and does not name it.                                                                                                                 |

## DynamoDB [#dynamodb]

`dynamodb` · runs `awslabs.aws-api-mcp-server` via **uvx**, locked to read-only
operations · every command is matched against AWS's read-only action list before it
runs, local file access is refused outright, and upstream telemetry is off.

Overview, prompts and the tool list: [/integrations/dynamodb](/integrations/dynamodb).

1. **Create an IAM user** with `AmazonDynamoDBReadOnlyAccess`, or a policy granting
   `dynamodb:List*`, `dynamodb:Describe*`, `dynamodb:Query`, `dynamodb:Scan` and
   `dynamodb:GetItem` on the tables you need. Generate an access key for it.

2. **Or point at a named profile instead.** Choosing *Named profile* reads the credential
   from the `~/.aws/config` of the machine running Triagic, which is the only way to reach
   assume-role, IAM Identity Center (SSO), `credential_process` or MFA. See
   [AWS CloudWatch](/docs/integrations/cloud#aws-cloudwatch) for the profile shapes.

   This server also injects `--profile <name>` into every CLI command it generates, so the
   profile is what the command itself runs as, not only what the SDK signs with.

3. **Fill the form.**

   | Field                     | Required                     | What to put                                                                      |
   | ------------------------- | ---------------------------- | -------------------------------------------------------------------------------- |
   | **Authentication method** | no, defaults **Access keys** | *Named profile* for assume-role, SSO, `credential_process` or MFA.               |
   | **AWS access key ID**     | yes, with *Access keys*      | `AKIA…`                                                                          |
   | **AWS secret access key** | yes, with *Access keys*      |                                                                                  |
   | **Session token**         | no, with *Access keys*       | Only for temporary (STS) credentials.                                            |
   | **Profile name**          | yes, with *Named profile*    | The name inside `[profile …]` in that machine's `~/.aws/config`.                 |
   | **Region**                | yes, defaults `us-east-1`    | DynamoDB tables are per-region, so this has to be the region the table lives in. |
   | **CA certificate path**   | no                           | Only where a TLS-intercepting proxy sits in front of the AWS endpoints.          |

4. **Verify.** No health check runs for this one: the underlying tool returns AWS errors
   as *successful* results with an error field, so a check could not tell a working
   credential from a denied one. It is start-checked only; the first real tool call is
   what proves the credential.

| If it reports                                   | It usually means                                                                                                               |
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `InvalidClientTokenId`, `SignatureDoesNotMatch` | Key ID and secret aren't a matching pair, or the key is inactive. Temporary credentials also need **Session token** filled in. |
| `The config profile (…) could not be found`     | No such profile in the `~/.aws/config` of the machine running Triagic. That machine, not the portal host.                      |
| `Token has expired and refresh failed`          | An SSO profile whose session has lapsed. Run `aws sso login --profile …` on that machine.                                      |
| `AccessDenied`                                  | Credentials are valid, the IAM policy is not.                                                                                  |
| `ResourceNotFoundException`                     | Right credentials, wrong **Region**.                                                                                           |
| `…is a write operation`                         | Expected. This integration refuses writes before they reach AWS.                                                               |

## Supabase [#supabase]

`supabase` · runs `@supabase/mcp-server-supabase` via **npx** with `--read-only` ·
SQL executes inside a read-only transaction, and the migration, branching, storage and
edge-function deploy tools are not offered at all.

Overview, prompts and the tool list: [/integrations/supabase](/integrations/supabase).

The tool surface is deliberately narrowed to three feature groups: **database**
(list tables, extensions, migrations, run SQL), **debugging** (query logs, security
and performance advisors) and **docs**. Account-level tools are excluded on purpose:
they reach across projects, which would undo the per-project scoping below.

1. **Generate a personal access token** at
   [supabase.com/dashboard/account/tokens](https://supabase.com/dashboard/account/tokens).
   It inherits its owner's organization access, so create it from an account that is a
   member of the organization owning the project, ideally a dedicated one.

   The project's `anon` and `service_role` keys are a different credential and are not
   accepted here.

2. **Find the project ref.** Project Settings → General → **Reference ID**, a
   20-character string. It is also the subdomain of the project's `…supabase.co` URL.

3. **Fill the form.**

   | Field                     | Required | What to put                                                                                     |
   | ------------------------- | -------- | ----------------------------------------------------------------------------------------------- |
   | **Personal access token** | yes      | `sbp_…`                                                                                         |
   | **Project ref**           | yes      | The reference ID. One configuration is one project; add a second instance for a second project. |

4. **Verify.** Health check is `list_tables`, which runs real SQL through the platform
   API, so it fails on both a bad token and a project the token cannot reach.

| If it reports                                 | It usually means                                                                         |
| --------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `401` / `invalid token`                       | Not a personal access token. It must start `sbp_` and come from the account tokens page. |
| `403`                                         | The token's owner is not a member of the organization that owns this project.            |
| `404` / `project not found`                   | Wrong ref. Use the 20-character reference ID, not the project's display name.            |
| `cannot execute … in a read-only transaction` | Expected, and deliberate.                                                                |

## Neon [#neon]

`neon` · a **hosted endpoint** at `https://mcp.neon.tech/mcp`, reached with your Neon
API key as the bearer. Nothing runs locally, and the session is opened with
`?readonly=true`, so the server itself refuses writes, migrations and project
management.

Overview, prompts and the tool list: [/integrations/neon](/integrations/neon).

Covers projects, branches, table schemas, read-only SQL, slow queries and query
plans.

1. **Create an API key** at **console.neon.tech → Account settings → API keys**. An
   **organization API key** scopes access to that organization's projects. Prefer it
   over a personal key, which sees everything the account can.

   A database password or connection string is a different credential and is not
   accepted here.

2. **Optionally find the project ID.** Project Settings → General → **Project ID**,
   e.g. `wispy-salad-12345678`. Setting it scopes the session to that one project;
   leaving it blank reaches every project the key can see.

3. **Fill the form.**

   | Field          | Required | What to put                                                   |
   | -------------- | -------- | ------------------------------------------------------------- |
   | **API key**    | yes      | `napi_…`                                                      |
   | **Project ID** | no       | The ID from Project Settings, not the project's display name. |

4. **Verify.** Health check is `list_projects`, a cheap authenticated read that fails
   on a bad or revoked key.

| If it reports | It usually means                                                                                |
| ------------- | ----------------------------------------------------------------------------------------------- |
| `401`         | Not an API key, or a revoked one. It comes from Account settings → API keys and starts `napi_`. |
| `403`         | An organization key pointed at a project outside that organization.                             |
| `404`         | **Project ID** doesn't resolve. Use the ID, not the display name, or leave it blank.            |
| refused write | Expected, and deliberate: the session is read-only.                                             |
