> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nodaldata.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Connect your database

> Wire your warehouse into your agent over MCP, and grant the query-history access the interview mines — read-only, running locally, never touching Nodal.

Two pieces of warehouse setup, both self-service:

1. **Connect your warehouse to your agent over MCP** — the interview verifies definitions against
   your live warehouse, and the context repo it generates queries that warehouse to answer
   questions. Both need read-only access.
2. **Grant query-history access** — the interview's highest-leverage input is
   [mining what your company actually runs](/open-source/build-your-context#query-history-mining-the-highest-leverage-input),
   and on most warehouses an ordinary read-only user can't see the full history by default.

<Note>
  **This runs entirely in your own agent. Nodal is not in this loop and never sees your database.**
  You wire a warehouse MCP server directly into your agent (Claude Code, Codex, Cursor, …); the
  credentials stay with you. The grants below go to **your own analysts**, used from their own
  warehouse sessions — Nodal never authenticates to your warehouse. It has no credential, no user,
  and no role.
</Note>

## Connect your warehouse over MCP

### Pick your warehouse's MCP server

These are maintained by the warehouse vendors and the MCP community — Nodal just points you to them.

| Warehouse       | MCP server                                                                        |
| --------------- | --------------------------------------------------------------------------------- |
| Snowflake       | [Snowflake MCP](https://github.com/Snowflake-Labs/mcp)                            |
| BigQuery        | [MCP Toolbox for Databases](https://github.com/googleapis/genai-toolbox) (Google) |
| Redshift        | [AWS Labs MCP servers](https://github.com/awslabs/mcp) (Redshift)                 |
| Databricks      | [Databricks MCP](https://github.com/databricks/databricks-mcp)                    |
| Other / general | [Model Context Protocol servers](https://github.com/modelcontextprotocol/servers) |

### Use a read-only role

<Warning>
  Give the MCP server a **read-only role or credential**. The interview and the generated
  `data-question` skill only ever run `SELECT`. A read-only role is the simplest way to guarantee
  that.
</Warning>

### Wire it into your agent

Follow your agent's MCP documentation to add the server. For Claude Code, for example:

```bash theme={null}
claude mcp add <name> -- <command to launch your warehouse MCP server>
```

The [setup video](https://youtu.be/2HTOE24Ms80) walks through this live with BigQuery and Google's
MCP Toolbox.

## Grant query-history access

**These instructions are for your warehouse admin.** The grant goes to **each analyst or domain
expert who will help build context** — a person on your team, not a service account, and not any
outside party. It's a temporary working permission: each section below includes the one-line
removal for when the context repo is built. Each section also ends with a test **the analyst runs
themselves** to confirm the grant landed.

The mining script reads query *metadata* — SQL text, run counts, who ran what — and never table
data.

<Tabs>
  <Tab title="Snowflake">
    **Admin: grant the role.** The full 365-day history lives in
    `SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY`, which an ordinary read-only user cannot see by default.
    Run this once, as `ACCOUNTADMIN`:

    ```sql theme={null}
    USE ROLE ACCOUNTADMIN;

    -- 1. Read access to ACCOUNT_USAGE views (QUERY_HISTORY among them),
    --    via the least-privilege built-in governance role
    CREATE ROLE IF NOT EXISTS QUERY_HISTORY_READER;
    GRANT DATABASE ROLE SNOWFLAKE.GOVERNANCE_VIEWER TO ROLE QUERY_HISTORY_READER;

    -- 2. One role that bundles normal read-only access with query-history access,
    --    so an analyst has a single role to hold and you have a single role to revoke
    CREATE ROLE IF NOT EXISTS ANALYTICS_CONTEXT_READER;

    GRANT ROLE <YOUR_READ_ONLY_ROLE> TO ROLE ANALYTICS_CONTEXT_READER;
    GRANT ROLE QUERY_HISTORY_READER  TO ROLE ANALYTICS_CONTEXT_READER;
    GRANT USAGE ON WAREHOUSE <WAREHOUSE> TO ROLE ANALYTICS_CONTEXT_READER;

    -- 3. Grant it to each analyst or domain expert contributing context
    GRANT ROLE ANALYTICS_CONTEXT_READER TO USER <ANALYST_USER>;
    ```

    `<YOUR_READ_ONLY_ROLE>` is the read-only role your analysts already use; `<WAREHOUSE>` is the
    warehouse they run queries on. Repeat the last line for each contributor.

    Each analyst then activates the role in their session — Snowflake won't use it otherwise:

    ```sql theme={null}
    USE ROLE ANALYTICS_CONTEXT_READER;
    ```

    <Warning>
      Granting a role to a *user* only makes it **available**. It has no effect until the role is active
      in the session — via `USE ROLE`, the role field in the connection config, or
      `ALTER USER <ANALYST_USER> SET DEFAULT_ROLE = ANALYTICS_CONTEXT_READER`. This is the step that most
      often looks like "the grant didn't work."

      Note also that privileges inherit **upward**: `GRANT ROLE A TO ROLE B` means `B` inherits `A`.
      Creating a bundle role *above* the role someone actually connects as changes nothing on its own.
    </Warning>

    **Analyst: test it.** Run this **in your own session** — not from a worksheet as `ACCOUNTADMIN`,
    which would pass regardless:

    ```sql theme={null}
    USE ROLE ANALYTICS_CONTEXT_READER;

    SELECT COUNT(*) FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
    WHERE START_TIME > DATEADD(day, -30, CURRENT_TIMESTAMP());
    ```

    A row count means you're set. An access error means the grant — or the role activation above — is
    missing.

    **Admin: remove it later.** When the context repo is built, hand the permission back:

    ```sql theme={null}
    REVOKE ROLE ANALYTICS_CONTEXT_READER FROM USER <ANALYST_USER>;
    ```

    <Note>
      **If you later point a warehouse MCP server at a service user**, the same role works — but
      Snowflake programmatic access tokens are pinned to one role at creation via `ROLE_RESTRICTION`, and
      that role is fixed for the token's lifetime. A role granted after the fact is invisible to an
      existing token. Either nest `QUERY_HISTORY_READER` into the role the token is already restricted to,
      or issue a new token bound to `ANALYTICS_CONTEXT_READER`. `ROLE_RESTRICTION` is required for service
      users unless an authentication policy says otherwise.
    </Note>
  </Tab>

  <Tab title="BigQuery">
    **Admin: grant the role.** The project-wide history lives in the
    `` `region-<REGION>`.INFORMATION_SCHEMA.JOBS_BY_PROJECT `` view. By default an analyst can only see
    `JOBS_BY_USER` — their *own* jobs — which misses the dashboard refreshes and teammates' queries
    that make mining worthwhile. Seeing everyone's jobs requires the `bigquery.jobs.listAll`
    permission; the least-privilege predefined role that carries it is **BigQuery Resource Viewer**:

    ```bash theme={null}
    gcloud projects add-iam-policy-binding <PROJECT_ID> \
      --member="user:<ANALYST_EMAIL>" \
      --role="roles/bigquery.resourceViewer"
    ```

    Repeat for each contributing analyst — and **for each project**: `JOBS_BY_PROJECT` is scoped to a
    single project, so if your company runs queries across several BigQuery projects, the grant (and
    the mining) happens per project.

    <Note>
      IAM changes usually propagate in seconds, but can take a couple of minutes (worst case \~7). If the
      test below returns *Access Denied* right after granting, that's propagation lag, not failure —
      wait 60 seconds and retry before concluding anything.
    </Note>

    **Analyst: test it.** Run this yourself, authenticated as you (e.g. with the `bq` CLI):

    ```bash theme={null}
    bq query --use_legacy_sql=false --project_id=<PROJECT_ID> \
    'SELECT COUNT(*) AS job_count
     FROM `region-us`.INFORMATION_SCHEMA.JOBS_BY_PROJECT
     WHERE creation_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 DAY)'
    ```

    Replace `region-us` with your data's region. A `job_count` means you're set. Make sure the test —
    and the mining — reads `JOBS_BY_PROJECT`, not `JOBS_BY_USER`: the latter succeeds *without* the
    grant but only shows your own jobs, which silently defeats the point.

    **Admin: remove it later.** When the context repo is built:

    ```bash theme={null}
    gcloud projects remove-iam-policy-binding <PROJECT_ID> \
      --member="user:<ANALYST_EMAIL>" \
      --role="roles/bigquery.resourceViewer"
    ```
  </Tab>

  <Tab title="Redshift">
    **Coming soon.** We're validating the Redshift instructions (system-table access to query history
    for a regular user) and will publish them here — the same shape as the others: an admin grant, an
    analyst self-test, and a one-line removal.
  </Tab>
</Tabs>

<Note>
  Without the grant, mining still works from a shorter, privilege-limited window (e.g. Snowflake's
  7-day `INFORMATION_SCHEMA` fallback) — the interview will tell you it's working from a reduced
  sample, and a later session can re-mine the full history once the grant lands.
</Note>

Once your warehouse is connected, you're ready to build context.

<CardGroup cols={2}>
  <Card title="Build your context" icon="comments" href="/open-source/build-your-context">
    Run the interview against your connected warehouse.
  </Card>

  <Card title="Share with your team" icon="users" href="/mcp/share-with-your-team">
    When you're ready to serve context to everyone, move to the hosted Nodal MCP.
  </Card>
</CardGroup>
