guide
guide8 min read

How Do You Govern and Audit AI Agent Access to Snowflake Through an MCP Server?

A five-layer checklist for identity, privileges, query tagging, audit retention and review, with Databricks notes

To govern and audit AI agent access to Snowflake through an MCP server, give every agent call a real user identity through OAuth, grant privileges on the MCP server and on each tool separately, and read the results back from Snowflake's own audit views. The Snowflake documentation for managed MCP servers states that "Permission needs to be granted for each tool," so access to the server alone does not open its tools. That one rule shapes the whole design. The rest of this guide is five layers that turn it into something you can defend in a review.

Key Takeaways

  • •Authenticate agents as real users or dedicated service roles through OAuth, never through a shared long-lived token in a config file.
  • •Treat the MCP server and each tool behind it as separate securables, and grant the narrowest privilege on each one.
  • •Keep direct SQL execution on its own MCP server with its own role, because a SQL tool on an agent's server lets the client step around the governed tools.
  • •Snowflake's ACCESS_HISTORY view covers the last 365 days, so schedule exports if your audit obligation is longer.
  • •Audit evidence only counts if you can answer who, which agent, which tool, which objects and which result for any call.

Why agent access needs its own governance layer

A human analyst opens a worksheet, runs a query and leaves a trail tied to a login. An agent behaves differently. It chains tool calls, retries, and may run dozens of statements to answer one question such as "Who has access to PII columns in analytics?" If all of those statements arrive under one shared service account, the audit log shows a busy account and nothing about intent. You cannot tell which person asked, which agent acted or which tool ran.

The Model Context Protocol itself keeps authorization optional. The MCP authorization specification says authorization is OPTIONAL for implementations, and that HTTP-based servers that support it should follow its OAuth 2.1 flow. In practice that means a server you connect can be wide open unless you or the platform vendor closed it. Governance is a decision you make on purpose, not a default.

There is also a drift problem. Agent setups change quickly: a new tool is added, a role is broadened to make a demo work, a token is pasted into a client config. Each change is small and reasonable. Together they erase least privilege. The five layers below are meant to be checked on a schedule, so drift shows up as a diff instead of as an incident.

Layer 1: Identity, so every call maps to a person or a role

Start with who is calling. The MCP specification requires an MCP server to validate that access tokens were issued specifically for it, and says it must not pass through the token it received from the client to upstream APIs. For Snowflake, the managed MCP server defaults to Snowflake OAuth, with External OAuth as an option, and the OAuth session normally takes the user's DEFAULT_ROLE as its primary role.

Two settings from the Snowflake page matter most. Set ALLOWED_ROLES_LIST on the security integration to the dedicated MCP access role, so a session cannot quietly pick up a more powerful role. Leave OAUTH_USE_SECONDARY_ROLES at NONE, which the docs recommend for least privilege. Without those two, a user with a broad default role hands that breadth to every agent they run.

If you front Snowflake with your own MCP server, follow the spec's audience rule: reject tokens that were not issued for your server, and obtain a separate upstream token instead of forwarding the client's. Passing a client token straight through is the confused-deputy pattern the spec calls out, and it makes your audit trail lie about who did what.

Layer 2: Privileges per server and per tool

Snowflake models the MCP server as an object with its own privileges. According to the same documentation, USAGE lets a user connect and discover tools, MODIFY covers changing the server and invoking tools, and each tool needs its own underlying grant: USAGE on a Cortex Search service, SELECT on a semantic view, USAGE on a Cortex Agent, or USAGE on a UDF or stored procedure.

The pattern the docs describe is a dedicated access role that receives the SNOWFLAKE.CORTEX_AGENT_USER database role plus usage on the warehouse, database, schema, MCP server and agent, then grants on each resource the agent uses. The page advises granting that role only to people who need it and never to PUBLIC. We would add one habit: name the role after the agent and the domain, such as one role for finance questions and another for marketing, so the grant list reads like a policy document.

The sharpest warning in the docs concerns SYSTEM_EXECUTE_SQL. If you expose it on the same server as an agent, the MCP client can bypass the agent's governed resources. Put direct SQL on a separate server with a dedicated role, and give that role to far fewer people. Most governance failures we see in agent setups are this one mistake, a convenient SQL tool sitting beside a carefully scoped one.

Layer 3: Attribution, so a query can be traced to an agent

Identity tells you the person. Attribution tells you the agent and the task. Where your client or gateway allows it, set a query tag on each session with the agent name, the tool name and a request ID, so every statement in Snowflake carries that context. A tag convention takes an afternoon to agree on and pays off for years, because it lets you split a user's manual work from agent work in the same view.

Log on the MCP side as well. Record the tool name, the arguments after PII redaction, the calling identity, the timestamp, the row count returned and the decision made, whether allowed, denied or sent for approval. Store those events in an append-only location that the agent cannot write to. If the agent can edit its own log, you have a diary, not an audit trail.

Join the two sides on the request ID. When someone asks why a table was read at 02:14, you can show the question that triggered it, the tool the agent chose and the statement Snowflake executed. That chain is what separates "we log everything" from "we can explain any call."

Layer 4: Audit evidence from Snowflake itself

Snowflake already records which objects a query touched. The ACCESS_HISTORY reference says the view covers access history within the last 365 days (1 year). That is your ground truth for column-level reads, and it is independent of anything your MCP layer claims. Query it by user, role and query tag to answer "which agent read the customer email column this quarter."

Because the window is 365 days, plan an export if your obligations run longer than a year. A scheduled job that copies the relevant rows into a table you own, with its own retention and access controls, closes the gap. Do this before an auditor asks, since the oldest data ages out silently and cannot be recovered afterward.

Cross-check the two sources regularly. Every tool call in your MCP log should have a matching query in Snowflake, and every agent-tagged query should have a tool call behind it. Mismatches are the interesting findings: a tagged query with no log entry means something bypassed your gateway, and a log entry with no query means a call failed or was denied.

Layer 5: Review, approval and the autonomy dial

Logs only help if someone reads them. Set a weekly review that lists new tools, new grants, roles that gained privileges and agents whose read volume jumped. Treat any grant to PUBLIC or any use of secondary roles as a finding by default. The goal is to keep the review short enough that it actually happens.

Reads and writes deserve different treatment. Let agents read through scoped roles without ceremony, and route writes, DDL and grants through an approval step with a named approver. Our Governance Agent in Data Workers works this way: it generates RBAC policy, detects PII, maps controls to SOC2, GDPR and HIPAA, and writes audit entries, while a per-domain autonomy dial decides which actions need a human. The open-source edition is read-only by default, which keeps the safe path as the default path.

The Connectors Agent covers the Snowflake side of this, and the Governance Agent consumes what it sees, so a new grant or a new tool shows up in the same review as the access it changes. That shared context is the point of a coordinated set of agents: the policy check, the connection and the audit entry come from one place instead of three tools you reconcile by hand.

What changes on Databricks

The same five layers apply on Databricks, with different names. The Databricks documentation on provided MCP servers describes managed servers in the system.ai schema, each a Unity Catalog securable invoked through Unity Gateway. Grants control who can invoke each server, policies govern individual calls, and the SQL server runs queries with the caller's Unity Catalog and warehouse permissions.

The important caveat is on the same page: legacy workspace endpoints bypass Unity Gateway, so they lack tracing in the gateway tables and the MCP-level grants, policies and guardrails. If your team still uses those endpoints, the gateway's controls do not apply to them. Inventory which endpoints your agents actually call before you assume the governance story covers them.

Whichever platform you run, the test is the same. Pick one agent action from last week and trace it end to end: the person, the token, the tool, the grant, the statement and the objects read. If any link in that chain is missing, that is where to start.

Frequently Asked Questions

Does an MCP server give an AI agent more access than the user has?

It should not. With Snowflake OAuth the session runs under a role the user already holds, and limiting ALLOWED_ROLES_LIST keeps it to the dedicated MCP role. Over-access appears when teams use a shared service account or expose a direct SQL tool beside governed tools. Both are configuration choices you can check and remove.

How long does Snowflake keep the audit trail for agent queries?

ACCESS_HISTORY covers the last 365 days according to the Snowflake reference linked above. If a regulation or contract needs more than a year, export the rows you need to a table you control on a schedule, with its own access rules, before they age out.

Do I need a separate MCP server for direct SQL?

Yes, we recommend it, and so does the Snowflake documentation. Exposing SYSTEM_EXECUTE_SQL beside an agent lets the client bypass the agent's governed resources. A separate server with a dedicated role and a short list of users keeps unrestricted SQL visible and rare.