guide
guide8 min read

Does an MCP Server Give Your AI Agent Governed Data Context, or Do You Still Need Row-Level Security and Masking?

MCP standardizes how an agent connects to data, and the access rules still have to live somewhere else

No: an MCP server gives your agent a standard way to connect to data, but it does not decide which rows, columns or tenants that agent may see. Knowi's write-up on MCP data layers puts it bluntly, describing MCP as a transport layer rather than an intelligence layer, and noting that row-level security, tenant isolation and data masking are left to the builder. We agree with that framing, and the rest of this post covers what the protocol does specify, where the gap sits, and how to close it.

Key Takeaways

  • •MCP standardizes how an agent connects to a data source. It does not define row-level security, column masking or tenant isolation, so those controls must be enforced in the warehouse or in a governance layer in front of it.
  • •The MCP authorization spec is optional: the 2025-06-18 revision states that authorization is OPTIONAL, and stdio servers are told to read credentials from the environment instead.
  • •Governed context means the agent's identity, not a shared service account, determines which rows and columns come back from every query.
  • •Masking alone is not governance. You also need per-agent identity, tenant boundaries, write review and an audit trail you can read after the fact.
  • •The safest order of operations is to enforce policy at the data layer first, then expose it over MCP, so no client can route around it.

What the MCP Spec Actually Covers

The MCP authorization specification (2025-06-18 revision) defines how a client proves who it is to an MCP server. It states that authorization is OPTIONAL, builds on the OAuth 2.1 draft and three supporting RFCs (8414, 7591 and 9728), and defines three error codes for authorization failures: 401, 403 and 400. That is an authentication and token-handling contract. It tells you the caller is who they claim to be.

What it does not do is describe your data. The spec says servers must validate that a token was issued for them, and that they must "take all necessary steps to ensure no data is returned to unauthorized parties." The mechanism for deciding who is unauthorized for a given row or column is not in scope. It cannot be, because the protocol has no idea what your tables contain or which columns hold personal data.

This distinction matters because teams often hear "MCP has OAuth" and conclude that data access is handled. OAuth answers whether this client may call this server. It does not answer whether this agent, acting for this user, may see the email column of the customers table. Those are two different questions, and only the first one has a standard answer today.

There is a second subtlety. The spec forbids token passthrough: an MCP server must not forward the token it received to upstream APIs, and must use a separate credential for them. That is the right security call, but it has a side effect. Once the server uses its own credential against the warehouse, the warehouse sees the server, not the end user, unless you deliberately carry identity across that hop.

Where the Gap Sits: Four Things Left to the Builder

Knowi's article names three controls that MCP leaves to you: row-level security, tenant isolation and masking. We would add a fourth, which is the audit trail. In practice, a team that connects an agent to a warehouse over MCP has to supply all four, and each lives at a different layer of the stack.

Row-level security decides which rows a given principal sees. Masking decides which column values are shown, hashed or hidden. Tenant isolation decides whether one customer's agent can ever touch another customer's data. The audit trail records what was asked, under which identity and what came back. If any one of these is missing, the agent either overexposes data or cannot prove what it did.

The failure mode we see most is the shared service account. The MCP server connects to the warehouse with one powerful role, every agent inherits it, and the warehouse's own row and column policies evaluate against that single identity. The policies exist and are technically working, and they are enforcing the wrong person's permissions.

Row-Level Security Belongs Below MCP, Not Inside It

The strongest place to enforce row filtering is the data platform itself. In Snowflake, for example, the row access policy documentation describes a schema-level object that controls which rows a SELECT, UPDATE, DELETE or MERGE can see, and notes that a single policy can be set on different tables and views at the same time. Because the policy sits on the object, every client that reaches the table inherits it, including an MCP server.

The same page carries a caveat that applies directly to agents: policies are evaluated using the policy owner's role context and the querying role's attributes. If your MCP server queries as one role, the policy sees one role. To get per-agent filtering you must connect with a role, or pass a session context, that actually reflects the agent and the task. Row-level security only works as well as the identity you feed into it.

Nested policies add another consideration. Per the same documentation, when a table and a view both carry policies, the table's policy runs first and then each view's policy. That composes well for humans writing SQL, but an agent generating queries can reach views you forgot about. Inventory the objects an agent can reach before you decide the filter is complete.

Not every store supports this natively. For sources without row policies, the filter has to be applied in a layer in front of the source, which is where a governance agent earns its place. Either way, the rule is the same: write the policy once, close to the data, and let the MCP layer inherit it rather than re-implementing it per tool.

Masking Is Necessary and Not Sufficient

Column masking is the control most teams add first, because it is easy to explain: the agent sees a hashed email instead of the real one. It protects against the obvious leak. It does not protect against joins that re-identify a masked value, against free-text columns that contain personal data nobody tagged, or against an agent that summarizes a masked column in a way that reveals it.

Masking also says nothing about who asked. A regulator reviewing an AI-assisted analysis will want to know which identity ran the query, which policy applied and whether you can reproduce the result. We covered this gap in our post on whether masking PII in MCP responses is enough for GDPR and HIPAA, and the short answer is that masking hides values while compliance asks for accountability.

The practical rule is to classify first and mask second. Detect which columns hold personal data, attach the classification to the column, and let the masking policy key off the tag. The Governance Agent does this detection and drafts the RBAC policy, so masking follows the data when a new column appears instead of lagging behind a manual review.

Tenant Isolation: The Control You Cannot Add Later

Knowi's article makes a point we share: tenant isolation is "not something you can bolt on after the fact." If you serve multiple customers, or multiple internal business units with legal separation, the boundary has to exist before the first agent connects. Retrofitting it means auditing every query path the agent already uses.

There are three common patterns. Separate databases or accounts per tenant give the strongest wall and the highest operating cost. A shared schema with a tenant column and row policies is cheaper, and it relies entirely on the policy never being bypassed. A hybrid keeps regulated tenants separate and pools the rest. Whichever you choose, the agent's session must carry the tenant, and the tenant must come from your identity system, not from the prompt.

That last point is the one agents make easy to get wrong. If a tenant ID appears in the conversation, a prompt can change it. Treat anything the model can write as untrusted input. The tenant, the role and the allowed tables should be fixed by the session before the model sees its first token.

Five Layers That Make MCP Context Governed

We structure this as five layers, ordered from the identity at the top to the evidence at the bottom. Each one answers a question the MCP spec leaves open, and skipping any layer leaves a hole the others cannot cover.

  • •Layer 1, identity: every agent gets its own named identity and a task-scoped role, never a shared key, so downstream policies evaluate the right principal.
  • •Layer 2, row and tenant policy: filters live on the data objects or in a policy layer in front of them, keyed to the session's identity and tenant.
  • •Layer 3, column classification and masking: personal data is detected, tagged and masked by tag, so new columns inherit protection.
  • •Layer 4, write review: anything beyond a read goes through an approval step, which is why our open-source edition is read-only by default.
  • •Layer 5, audit: every request records the identity, the policy applied and the result, in a trail that cannot be edited after the fact.

The order matters. Identity feeds policy, policy feeds masking, and all three feed the audit record. If you build the audit log first without identity, you get a detailed record of a service account doing everything. We walked through the identity layer in our guide to per-agent identity and least-privilege access over MCP, and the governed-versus-raw comparison in our post on what a raw database MCP server skips.

How to Test Whether Your Setup Is Governed

You can check this in an afternoon with questions your users already ask. Run "Who has access to PII columns in analytics?" and see whether the answer comes from real grants or from a guess. Run "What's the blast radius if I drop this column?" and confirm the agent reads lineage rather than inventing it. Then run the same question under two different identities and confirm the answers differ where they should.

Next, try to break it. Ask the agent for a row you know its role should not see. Ask it to switch tenants in plain language. Ask it to join a masked column to an unmasked one. Each attempt should fail or return filtered data, and each should leave a record. If an attempt succeeds, you have found the layer to fix.

Finally, check the evidence. Pick a query from yesterday and try to reconstruct who ran it, under which policy and what the agent returned. If that takes more than a few minutes, the audit layer is not doing its job, and that is the layer a security reviewer will probe first.

Where Data Workers Fits

Data Workers is fifteen MCP-native agents, and the governance work is a chain rather than a single tool. The Governance Agent detects PII, generates RBAC policy and keeps the audit trail. The Catalog Agent) supplies the semantic context about what each column means. Because both are MCP servers, you invoke them from Claude Code, Cursor or any compatible client without leaving the terminal.

Policy is enforced at the framework level across every agent and tool, not per integration. That is the answer to the shared-account problem: the identity travels with the request, SAML SSO and RBAC apply uniformly, data stays inside your infrastructure, and the same audit trail covers every agent. The open-source edition is Apache 2.0 and read-only by default, so you can test the governance path before any write is possible.

We would rather you verify than trust us. Run the checks in the previous section against any MCP setup, ours included, and judge the result by what the audit trail shows.

Frequently Asked Questions

Does an MCP server enforce row-level security?

Not on its own. MCP defines the connection and, optionally, the OAuth flow that authenticates the client. Row filtering has to come from the data platform's own policies or from a governance layer in front of it. The MCP server simply passes along whatever the underlying source returns for the identity it connects with.

Is OAuth in the MCP spec enough to protect my data?

No. OAuth establishes who the caller is and whether a token was issued for the server. It does not classify columns, filter rows or separate tenants. It is a necessary first layer, and the spec itself marks authorization as optional, so you should not assume a given server implements it.

Should I mask data before or after it passes through MCP?

Before. Apply masking at the data layer so every client, including ones that bypass your MCP server, receives masked values. Masking only in the MCP response leaves the raw data one direct connection away, and it does not give you an audit trail of who saw what.

How do I stop one customer's agent seeing another's data?

Fix the tenant in the session from your identity system before the model runs, and enforce it with row policies or separate databases. Never let the tenant come from the prompt. Then test it by asking the agent in plain language to switch tenants and confirming the request fails and is logged.