Skip to content

Connect to Snowflake

This guide assumes your team already has a Snowflake MCP server, connected to Claude or another client: that’s the agent you want to evaluate. honest-agent connects to the same server, as its own user, with a programmatic access token (PAT) sent with each request to prove it’s allowed in.

  • OAuth, which your team probably uses in Claude, isn’t an option: it needs a browser to log in.
  • Key-pair authentication (a PEM key) isn’t supported by honest-agent, and Snowflake’s MCP server docs only cover OAuth and PATs.

No MCP server yet? See Don’t have an MCP server yet? at the end.

The SQL below has placeholders in angle brackets. Replace them with yours:

Placeholder What it is
<mcp_database>, <mcp_schema>, <mcp_server> Where your MCP server lives, and its name.
<database>, <schema> Where the tables the agent reads live. Step 3 also puts the network rule there; it can live in another schema.
<warehouse> The warehouse that runs the agent’s queries.

The names the SQL creates, like the role honest_agent_reader and the user honest_agent_svc, you can keep or rename. You’ll need a role that can create roles, users and network policies and grant access to the server, such as securityadmin.

The URL honest-agent needs is the one your team entered when they added the server to Claude. If you don’t have it, find the server in Snowflake:

show mcp servers in account;
describe mcp server <mcp_database>.<mcp_schema>.<mcp_server>;

describe shows the server’s tools. The URL follows this shape:

https://<account>.snowflakecomputing.com/api/v2/databases/<mcp_database>/schemas/<mcp_schema>/mcp-servers/<mcp_server>

Get <account> by running:

select current_organization_name() || '-' || current_account_name();

If it contains underscores (_), write them as hyphens (-): Snowflake’s docs say MCP connections have problems with underscores in the host name.

2. Create a read-only role and a service user

Section titled “2. Create a read-only role and a service user”

honest-agent can’t log in as your team does, so it needs its own user. Give that user a role that reads the same tables your team’s role reads, so the agent sees the same data. show grants to role <team_role>; lists what that role can read.

create role honest_agent_reader;
grant usage on warehouse <warehouse> to role honest_agent_reader;
grant usage on database <database> to role honest_agent_reader;
grant usage on schema <database>.<schema> to role honest_agent_reader;
grant select on all tables in schema <database>.<schema> to role honest_agent_reader;
grant select on all views in schema <database>.<schema> to role honest_agent_reader;
-- the MCP server, and the database and schema it lives in
grant usage on database <mcp_database> to role honest_agent_reader;
grant usage on schema <mcp_database>.<mcp_schema> to role honest_agent_reader;
grant usage on mcp server <mcp_database>.<mcp_schema>.<mcp_server> to role honest_agent_reader;
create user honest_agent_svc
type = service
default_role = honest_agent_reader
default_warehouse = <warehouse>;
grant role honest_agent_reader to user honest_agent_svc;
  • type = service makes it a user for programs: it can’t log in with a password.
  • default_warehouse matters: the server’s SQL tool runs queries on the user’s default warehouse unless the server’s spec names one (describe in step 1 shows it).
  • Several schemas? Repeat the usage and select grants for each one your team’s role reads.

If the schema has semantic views, the agent can query them too, but the grants above don’t cover them. Add:

grant select on all semantic views in schema <database>.<schema> to role honest_agent_reader;

Objects created later: these grants cover what exists today. To also cover tables, views or semantic views created later, repeat them with future instead of all, e.g. grant select on future tables in schema ....

Snowflake issues and accepts a PAT for a service user only if the user has a network policy. Allow only the addresses honest-agent runs from: your machine, or your CI’s (for GitHub Actions, see below).

create network rule <database>.<schema>.honest_agent_ips
type = ipv4
mode = ingress
value_list = ('<your_ip>/32');
create network policy honest_agent_policy
allowed_network_rule_list = ('<database>.<schema>.honest_agent_ips');
alter user honest_agent_svc set network_policy = honest_agent_policy;

GitHub-hosted runners get a new address on almost every run, so the rule above can’t list them. There are two ways around that.

Easy: Snowflake’s GitHub Actions rule. Snowflake maintains a network rule with GitHub Actions’ addresses and keeps it up to date. Add it to the policy, next to your own addresses:

alter network policy honest_agent_policy add allowed_network_rule_list = (
'snowflake.network_security.githubactions_global'
);

Tight: send the job’s traffic from a fixed address, and allow only that one:

  • a larger runner with a static IP, a paid GitHub feature;
  • a self-hosted runner with a fixed outbound address;
  • a VPN or proxy your team already runs, if it has a fixed outbound address.
alter user honest_agent_svc add programmatic access token honest_agent_token
role_restriction = 'honest_agent_reader'
days_to_expiry = 90;
  • Copy the token now: Snowflake shows it only once.
  • role_restriction is required for a service user: the token can only use that role.
  • It expires. Without days_to_expiry a PAT lasts 15 days. When it expires, honest-agent can’t connect until you create a new one and update .env (or your CI’s secret).

In the folder for your evals:

Terminal window
uv run honest-agent init --server snowflake

init asks for the URL from step 1, then writes a snowflake target to honest_agent_config.yml:

targets:
snowflake:
mcp_url: "{{ env_var('SNOWFLAKE_MCP_URL') }}"
bearer_token_env: SNOWFLAKE_MCP_TOKEN
evals_dir: evals

bearer_token_env names the variable in .env that holds the token; the token itself never goes in the config. Fill in .env: the token from step 4 goes in SNOWFLAKE_MCP_TOKEN, next to your model’s API key. Keep .env out of git.

Terminal window
SNOWFLAKE_MCP_URL=https://<account>.snowflakecomputing.com/api/v2/databases/...
SNOWFLAKE_MCP_TOKEN=...
ANTHROPIC_API_KEY=...
Terminal window
uv run honest-agent debug

The “MCP server” line should connect and count the server’s tools, the same ones describe listed in step 1. If it doesn’t, see When it doesn’t connect.

Pick a question with an answer you can check yourself: run the query in a Snowflake worksheet first, and use its result as expected_answer. Edit evals/first_eval.yml, with your result in expected_answer:

version: 1
evals:
- category: first
grading:
method: extract_match
min_score: 0.8
provenance:
expected_database: <database>
expected_schema: <schema>
min_score: 0.7
tests:
- id: orders_2025
title: Orders in 2025
prompt: How many orders did we get in 2025?
expected_answer: "<your result>"
provenance:
expected_sources: [orders]

expected_database and expected_schema say where orders must live, so a same-named table somewhere else (Snowflake’s own snowflake_sample_data, say) doesn’t count. Then:

Terminal window
uv run honest-agent run

See Writing evals for more questions, and the other grading methods.

  • The token is refused, or Snowflake won’t create it: the user needs a network policy (step 3), and your address must be in it. If your account has an authentication policy, it must allow PROGRAMMATIC_ACCESS_TOKEN.
  • It worked before and now it’s refused: the PAT may have expired (step 4).
  • Can’t reach the host: check for underscores in the account part of the URL (step 1).
  • The server is found, but access is denied: the role needs usage on the MCP server and on its database and schema (step 2).
  • Queries fail with no warehouse: set default_warehouse on the user (step 2), and grant usage on it to the role.
  • Queries fail with “does not exist or not authorized”: the role is missing usage on the database or schema, or select on the table. A bare table name also fails: see the note in step 7.

Still stuck? Open an issue with the output of honest-agent debug. It prints variable names, never their values, but check what you paste anyway: no tokens, keys or private URLs.

honest-agent evaluates the server your agent uses; it doesn’t need one of its own. If you’re setting one up, Snowflake’s MCP server guide covers it. A minimal server with one tool that runs SQL looks like this:

create mcp server <mcp_database>.<mcp_schema>.<mcp_server>
from specification $$
tools:
- title: "SQL Execution Tool"
name: query_warehouse
type: SYSTEM_EXECUTE_SQL
description: "Run read-only SQL queries against the <database> database."
config:
read_only: true
$$;
  • read_only: true makes the tool run only reads. It’s Snowflake’s default; setting it says so in the spec.
  • warehouse: under config picks the warehouse, instead of the user’s default.
  • The tool’s description is what the model reads to decide when to use it.

Then follow the steps above, from step 1.