MCP config
Turn on the data MCP server for a project and your team can query it from their AI assistant using your SQLMesh model descriptions, signed in as themselves. Only SELECT statements run, and we recommend a read-only warehouse role. You pick which models are exposed and see every query from the last 90 days. Off until you turn it on.
Supported warehouses
The data MCP server works with Postgres and Snowflake warehouses on SQLMesh projects. BigQuery is coming soon. dbt and DuckDB projects are not supported.
Who can use it
| Role | Can do |
|---|---|
| Owner, admin | Set up, turn off, change models and limits, create tokens, read the query log |
| Developer, analyst | Query the project from their assistant |
| Analyst | Also sees model SQL. Does not see runs, plans, jobs or secrets |
Setting up also needs permission to manage secrets, because it stores a warehouse login. To let a teammate query, invite them as an analyst. Removing someone from the organization stops their access on their next query.
How it works
Your assistant connects to the project's connector URL and signs in as you. What you can query follows your dagctl role. Each query is checked before it reaches the warehouse. Only one SELECT statement runs. On Postgres it runs in a read-only transaction. Snowflake and BigQuery have no read-only mode, so your grants are the second control. The assistant learns about your data from the model descriptions in your project, limited to the models you chose. Every query is logged for 90 days.
To connect an assistant, see Connect AI assistants.
Set it up
You need a deployed project and a login for its warehouse. Open the project's MCP config tab and click Set up MCP config.
- Warehouse login. Pick the warehouse type. It must match the project's SQLMesh gateway, and the wizard refuses a mismatch. Create a read-only login (a Postgres role, a Snowflake role and user, or, when BigQuery is available, a BigQuery service account), allow dagctl's addresses (Postgres and Snowflake only), enter the login.
- Models. Choose which models the assistant can see.
- Review and turn on. Set limits and turn it on. This usually takes a few minutes.
There is no option to copy your jobs' login. Use a dedicated one so you can revoke it without breaking pipelines. The wizard warns if the username matches your jobs' login.
Warning
The wizard shows "Anyone who can deploy code to this project can read this login" only while code running in the project can actually read the stored login. dagctl checks this live, per organization, and the warning goes away on its own when the check passes. While it shows, treat the login as readable by anyone who can deploy code to the project.
Runbook for your warehouse admin
In step 1, click Copy instructions for your warehouse admin. It copies everything below for your warehouse type, with your project's names filled in. Send it to whoever runs your warehouse.
Postgres and Snowflake need dagctl's addresses, which step 1 of the wizard lists. BigQuery has no address list.
1. Create the role
Replace the placeholders. Repeat the USAGE and SELECT lines for each production schema.
CREATE ROLE dagctl_ask LOGIN PASSWORD '<choose-a-strong-password>';
ALTER ROLE dagctl_ask SET default_transaction_read_only = on;
GRANT CONNECT ON DATABASE <database> TO dagctl_ask;
GRANT USAGE ON SCHEMA <schema> TO dagctl_ask;
GRANT SELECT ON ALL TABLES IN SCHEMA <schema> TO dagctl_ask;
ALTER DEFAULT PRIVILEGES FOR ROLE <sqlmesh_job_role> IN SCHEMA <schema>
GRANT SELECT ON TABLES TO dagctl_ask;
REVOKE CREATE ON SCHEMA public FROM PUBLIC; -- no CREATE
REVOKE TEMPORARY ON DATABASE <database> FROM PUBLIC; -- TEMP is a PUBLIC grant
This DDL grants SELECT only. The two REVOKE ... FROM PUBLIC lines affect every role in the database, not just this one. Check with your team before running them. <sqlmesh_job_role> is the role your SQLMesh jobs connect as, so tables they create later are readable too.
2. Allow dagctl's addresses
Step 1 of the wizard lists the addresses dagctl connects from. Read them there. Allow them in your firewall, security group or pg_hba.conf. If the list is empty, contact support.
The host must be reachable from the internet. Private network addresses are refused.
3. Send the login back
Send host, port, database, username and password through your password manager, not email or chat. Choose an SSL mode: verify-full (default), verify-ca or require.
What dagctl checks at setup
It connects, runs SELECT 1 and reads the login's identity.
Snowflake has no read-only mode. The role's grants decide what the login can do, so grant SELECT only. dagctl signs in with a key pair. Passwords are not accepted. PrivateLink accounts and China regions are refused.
Run as ACCOUNTADMIN. Replace the placeholders.
1. Generate a key pair
Keep the private key out of email and chat. Use an unencrypted key:
Or an encrypted key. You enter its passphrase in the wizard:
Then the public key. RSA_PUBLIC_KEY is the .pub file on one line, without the BEGIN and END lines.
openssl rsa -in dagctl_ask_key.p8 -pubout -out dagctl_ask_key.pub
grep -v 'PUBLIC KEY' dagctl_ask_key.pub | tr -d '\n'
2. Create the role, a dedicated warehouse and the grants
The dedicated warehouse keeps the data MCP server from competing with your jobs. CREDIT_QUOTA = 10 is a placeholder. Set your own monthly credit limit. Repeat the schema grants for each production schema.
CREATE ROLE <role>;
CREATE WAREHOUSE <warehouse> WAREHOUSE_SIZE = XSMALL AUTO_SUSPEND = 60 AUTO_RESUME = TRUE INITIALLY_SUSPENDED = TRUE;
CREATE RESOURCE MONITOR <warehouse>_monitor WITH CREDIT_QUOTA = 10 FREQUENCY = MONTHLY START_TIMESTAMP = IMMEDIATELY TRIGGERS ON 100 PERCENT DO SUSPEND_IMMEDIATE;
ALTER WAREHOUSE <warehouse> SET RESOURCE_MONITOR = <warehouse>_monitor;
GRANT USAGE ON WAREHOUSE <warehouse> TO ROLE <role>;
GRANT USAGE ON DATABASE <database> TO ROLE <role>;
GRANT USAGE ON SCHEMA <database>.<schema> TO ROLE <role>;
GRANT SELECT ON ALL TABLES IN SCHEMA <database>.<schema> TO ROLE <role>;
GRANT SELECT ON ALL VIEWS IN SCHEMA <database>.<schema> TO ROLE <role>;
GRANT SELECT ON FUTURE TABLES IN SCHEMA <database>.<schema> TO ROLE <role>;
GRANT SELECT ON FUTURE VIEWS IN SCHEMA <database>.<schema> TO ROLE <role>;
SQLMesh prod models are views. They run with their owner's rights, so grant the prod schemas only. The FUTURE grants cover tables and views your jobs create later.
3. Create the user
CREATE USER <user> TYPE = SERVICE DEFAULT_ROLE = <role> DEFAULT_WAREHOUSE = <warehouse> RSA_PUBLIC_KEY = '<public key on one line>';
GRANT ROLE <role> TO USER <user>;
4. Allow dagctl's addresses
Step 1 of the wizard lists the addresses dagctl connects from. Put them in a network policy on the user. If the list is empty, contact support.
CREATE NETWORK POLICY dagctl_ask_policy ALLOWED_IP_LIST = ('<address>');
ALTER USER <user> SET NETWORK_POLICY = dagctl_ask_policy;
5. Send the login back
Send the account identifier, user, role, warehouse, database and schema (if you use one). Send the private key and passphrase through your password manager, not email or chat. You enter them in the wizard.
What dagctl checks at setup
It runs SELECT 1, then a statement that needs the warehouse to run, then reads the login's identity. The second statement is why a suspended warehouse with auto-resume off, or one blocked by its resource monitor, fails setup.
Coming soon
BigQuery is not yet available to organizations. This is the planned setup.
The login is a service account. Its IAM roles decide what it can do. Run with gcloud and bq signed in as a project admin.
1. Create the service account
gcloud iam service-accounts create dagctl-ask --project <project> --display-name "dagctl data MCP server"
2. Grant read access to each dataset
Repeat for each dataset your production models are published to, and for its sqlmesh__ twin (for example sqlmesh__<dataset>). BigQuery views do not run with their owner's rights, so the physical tables behind them need the grant too.
bq query --nouse_legacy_sql --project_id=<project> \
'GRANT `roles/bigquery.dataViewer` ON SCHEMA `<project>.<dataset>` TO "serviceAccount:dagctl-ask@<project>.iam.gserviceaccount.com";'
3. Allow it to run query jobs
gcloud projects add-iam-policy-binding <project> \
--member=serviceAccount:dagctl-ask@<project>.iam.gserviceaccount.com \
--role=roles/bigquery.jobUser
4. Create a key file
gcloud iam service-accounts keys create dagctl-ask-key.json \
--iam-account=dagctl-ask@<project>.iam.gserviceaccount.com
If key creation fails with iam.disableServiceAccountKeyCreation, your organization blocks key files. dagctl needs one; ask for an exception on this project.
You paste the key file into the wizard.
5. Send the login back
Send the project ID and the key file through your password manager, not email or chat.
BigQuery has no IP allowlist, so there are no addresses to add. dagctl connects to Google's API.
Scan cap
Every query has a scan cap, set in step 3 of setup. See Limits. On projects that use reservations or editions, BigQuery may not enforce it.
What dagctl checks at setup
It runs SELECT 1 and SESSION_USER() as dry-run-first jobs. A dry run estimates the bytes a query would scan before anything is billed.
Find a query in your own logs
Every query carries the dagctl request id. The request id is the Reference in the dagctl query log, so you can match a warehouse entry to the person, token and SQL behind it.
Every query sets application_name to dagctl:<request id>. You can see it in pg_stat_activity while the query runs and in your Postgres logs if you log application_name.
Every query sets QUERY_TAG to dagctl:<request id>. Find it in QUERY_HISTORY, for example in SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY or the QUERY_HISTORY table function, in the QUERY_TAG column.
Every query job has the label dagctl=<request id>. A person with bigquery.jobs.listAll can find it. Replace <location> and <request id>.
SELECT job_id, user_email, total_bytes_billed
FROM `region-<location>`.INFORMATION_SCHEMA.JOBS
WHERE EXISTS (
SELECT 1 FROM UNNEST(labels) WHERE key = 'dagctl' AND value = '<request id>'
);
The dagctl service account cannot read INFORMATION_SCHEMA.JOBS.
Limits
Admins set these in step 3, and later under Settings. The ranges are fixed.
| Limit | Default | Range |
|---|---|---|
| Rows per query | 1,000 | 1 to 10,000 |
| Result size | 2 MB | 512 KB to 10 MB |
| Query timeout | 30 s | 1 to 90 s |
| Queries at once | 4 | 1 to 16, across everyone using the project |
| Daily query budget | 2,000 | 1 to 100,000, resets at midnight UTC |
| Bytes scanned per query (BigQuery only) | 10 GB | 1 GB to 1 TB |
The scan cap is required on BigQuery and uses decimal GB (1 GB is 1,000,000,000 bytes). dagctl estimates each query with a dry run and refuses it before anything is billed if the estimate is over the cap. The real job also runs with the cap as its maximum billed bytes.
What the assistant tells the user when one is hit:
| Limit | Message |
|---|---|
| Rows, result size | "The query or its result is too large. Add filters or a LIMIT." Hitting the row cap returns the first rows, marked truncated. |
| Timeout | "The query exceeded its Ns time limit. Narrow it and retry." |
| Concurrency | "This project is already running its maximum number of queries. Retry shortly." |
| Scan cap (BigQuery, coming soon) | "This query would scan more than this project's limit of N GB. Add filters, select fewer columns or filter on the partition column. An admin can raise the limit." Ends with a Reference id. |
| Daily budget | "This project reached its daily query limit. It resets at midnight UTC. Ask your data team to raise it." |
Each message ends with a Reference: id you can find in the query log.
Tokens for bots
People sign in from their assistant. Bots and scripts use query tokens instead.
- Only owners and admins create them, under MCP config → Tokens.
- Expiry is 7, 30 or 90 days. The default is 30.
- Each person can hold at most 10 active tokens per project.
- A token queries as the person who created it, with their role. It stops working if they lose access, and also if they are no longer an owner or admin.
- Revoking a token takes effect immediately.
- The token is shown once. Store it in an environment variable named
DAGCTL_QUERY_TOKEN.
Client snippets for tokens are in Connect AI assistants.
What dagctl does not do
- It does not inspect your grants.
- It does not run write probes against your database.
- It does not know whether the login can write. The read-only role is our advice, not something we check. The wizard shows "Not checked".
What the Read-only row of the turn-on checks says depends on the warehouse:
- Postgres: dagctl does not check grants. Whether this login can write depends on your warehouse grants.
- Snowflake: dagctl does not check grants. Snowflake has no read-only mode, so the role's grants decide what this login can do.
- BigQuery: dagctl does not check permissions. The service account's IAM roles decide what it can do.
Because the login might be able to write, dagctl's own guard assumes it can. The guard allows one SELECT statement, rejects anything else before it reaches the warehouse, and the driver runs it with a timeout. On Postgres it runs in a read-only transaction. Snowflake has no read-only mode, so there the guard and your grants are the controls. That is dagctl's control. Your role is a second one that only you can provide.
The model list is not a security boundary. Warehouse grants decide what can be read.
BigQuery caveats:
- Wildcard tables (
dataset.events_*) andFOR SYSTEM_TIME AS OFare allowed. A wildcard pattern is not matched by a rule that hides one table, so it can reach tables you did not share. - Time travel can read up to seven days of deleted rows, under the same permissions.
- On a project that uses reservations or editions, BigQuery may not enforce the scan cap. There, only the query timeout and the dry-run estimate limit cost.
The query log
MCP config → Query log shows every query from the last 90 days: time, who (person or token), SQL, status, rows, duration and the warehouse query id. Click a row for the full SQL. The warehouse query id is the backend process id on Postgres, the query id on Snowflake, and project:location.job_id on BigQuery. Your warehouse's own history carries the request id (see Find a query in your own logs) for as long as you keep it.
Turn it off
Click Turn off on the MCP config page. Assistants and tokens stop working right away, and running queries are cancelled. Models, limits and tokens are kept. Tick Also delete the stored warehouse login to remove the login too. To switch warehouse type, turn it off first.