guide
guide8 min read

MCP Has No Role Model: How to Enforce Which Team or Agent Can Call Which Data Tool

A five-layer method for restricting MCP tool calls by team, role and agent

The Model Context Protocol has no role model: its authorization specification (revision 2025-06-18) describes how a client proves who it is to an MCP server using OAuth 2.1, but it does not say which team, role or agent may call which tool. That decision belongs to you, and you enforce it in layers: verified identity, a per-role tool allowlist, warehouse-level grants, an audit trail and human approval for writes.

Key Takeaways

  • •MCP authorization (spec revision 2025-06-18) covers authentication of the client to the server; it defines no roles, groups or per-tool permissions, so tool-level policy is your responsibility.
  • •Tool annotations such as readOnlyHint and destructiveHint are hints, not controls, and the MCP tools spec says clients should treat them as untrusted unless the server is trusted.
  • •The strongest control is the one that cannot be bypassed by the agent: warehouse grants tied to the caller's identity, with the MCP layer acting as a second gate.
  • •Every agent is a principal. Give each one its own identity and scope, never a shared service account that inherits a whole team's access.
  • •Log every tool call with caller, tool, arguments and decision, and route write-capable tools through an approval step.

What MCP specifies and what it leaves open

The MCP authorization spec treats an HTTP-based MCP server as an OAuth 2.1 resource server. Servers advertise their authorization server through protected resource metadata as defined in RFC 9728, and clients bind tokens to a specific server using resource indicators from RFC 8707. That is a solid answer to the question of who is calling and which server the token is meant for.

It stops there. The spec says nothing about mapping a token to a team, nothing about which of a server's tools a given principal may invoke, and nothing about argument-level limits such as which schemas a query tool may read. Authorization is also optional, and servers using the stdio transport are told to take credentials from the environment instead, which usually means the credentials of whoever launched the process.

The gap is widely discussed. A Techstrong contributed piece, The MCP AI governance gap: eight problems enterprises are scrambling to solve, frames missing role control as one of several governance problems enterprises hit when MCP servers move from a laptop to shared data infrastructure. We agree with the framing and focus here on the one question: who may call which data tool.

Why data tools make the gap expensive

A typical data MCP server exposes tools such as run_query, describe_table, list_lineage, trigger_pipeline and update_policy. These differ enormously in blast radius. Reading a table description is harmless; running arbitrary SQL against a schema with PII is not; triggering a backfill can burn warehouse credit and overwrite data. A server that registers all of them under one token treats them as equals.

Agents sharpen the problem. A human analyst chooses a tool deliberately, while an agent chooses tools from a list it sees at connection time. If tools/list returns every tool the server has, the model can attempt any of them, including through prompt injection from a column comment or a ticket description. Hiding a tool from the list for callers who should not use it is therefore part of the control, not a nicety.

Shared credentials make it worse. When one service account backs the MCP server, every team that connects inherits that account's warehouse access. The marketing analyst's agent and the finance controller's agent then see identical data, and the audit log shows one user for both. Fixing this means moving the decision from the server's configuration to the caller's identity.

Layer 1: Give every caller a verified identity

Start with identity, because every later layer keys off it. For remote MCP servers, use the OAuth flow the spec describes and take team and role from signed token claims issued by your identity provider, not from anything the client or the model supplies. A tool argument that says team equals finance is a request, not a credential.

Give each agent its own identity. An agent that triages incidents, one that generates documentation and one that runs migrations need different scopes, so register them as separate clients or service principals with their own tokens. Where an agent acts for a person, carry both: the agent as the actor and the human as the subject, so the policy can use the narrower of the two.

For stdio servers, there is no token exchange, so identity comes from the environment. Treat that as a development convenience. In shared or production settings, put the server behind an HTTP transport or a gateway that authenticates the caller before any tool runs.

Layer 2: Filter and gate tools by role

With identity in place, define a policy that maps roles to tool names. Keep it declarative and version-controlled, for example a table of role, allowed tools and allowed resource patterns. Enforce it in two places: when the server answers tools/list, return only the tools the caller's role allows, and when it receives tools/call, check the same policy again and reject anything outside it.

Do not rely on tool annotations for this. The MCP tools specification defines annotations such as readOnlyHint and destructiveHint, but states that clients must consider them untrusted unless they come from a trusted server. They are useful as inputs to your policy, for instance to default every tool without a readOnlyHint to deny, but they are not enforcement.

Deny by default. A new tool added to a server should be invisible to every role until someone grants it. That single rule prevents the common failure where a server upgrade quietly gives every agent a new write capability.

Layer 3: Push the real decision into the warehouse

Tool allowlists tell you which tools run; they cannot know what a query touches. A run_query tool allowed for analysts can still read a PII column unless the warehouse says no. So run queries under the caller's own role or an impersonated, scoped role, and let native grants, row policies and column masking decide. Snowflake's access control overview describes this role-based model, where privileges attach to roles and roles attach to users.

This is the layer that survives a bug in your MCP gateway. If the gateway mistakenly exposes a tool, the warehouse still refuses the read. Treat the MCP policy as a convenience that shrinks the attack surface and the warehouse policy as the guarantee.

For argument-level control, add narrow checks in the server: a maximum row limit, a denylist of schemas, and a requirement that a query tool takes a read-only connection. Keep these simple and testable; complex SQL parsing in the gateway tends to have gaps that a warehouse grant does not.

Layer 4: Log every decision, not just every call

Record who called what, with which arguments, under which role, and whether policy allowed or denied it. Denials matter as much as successes, because a burst of denied calls from one agent is often the first sign of a prompt-injection attempt or a misconfigured workflow.

Join your MCP logs to warehouse history. Snowflake's ACCESS_HISTORY view retains records for 365 days and shows which columns a query read, so you can answer whether an agent's query touched a sensitive column, not only that it ran. Tag queries with the MCP request ID so the two logs line up.

Make the audit trail tamper-evident and store it outside the agent's reach. An agent that can edit the log that records its own behavior is not governed.

Layer 5: Route writes through human approval

Reads and writes deserve different defaults. Allow read tools by role, but require an approval step for any tool that changes state: pipeline triggers, schema changes, grant changes and deletions. The approval request should show the exact tool, arguments and the diff or cost estimate, not a generic confirmation.

Scope approvals narrowly. An approval for one backfill on one table is not a standing permission to run backfills. Expire it quickly and record who approved it, so the audit trail connects the action to a named person.

Over time you can relax this per domain. Low-risk writes in a sandbox schema can move to automatic approval while production changes stay gated. That per-domain dial is easier to defend to a security reviewer than a global on/off switch.

How Data Workers applies the five layers

Data Workers is built around this layered model. Every agent is an MCP server, and the framework applies identity, RBAC, audit logging and PII redaction around every tool, so individual agents do not each reinvent them. The OSS edition is read-only by default; writes, approval workflows and audit trails come with Pro.

The Governance Agent generates RBAC policy from your catalog and PII classification, then checks proposed tool access against it, so a question like who has access to PII columns in analytics gets answered from the same policy that enforces access. Data stays in your infrastructure, and Claude Code, Cursor or any MCP client connects to it without changing how engineers work.

Frequently Asked Questions

Does MCP support roles or scopes for individual tools?

Not as a protocol feature. The authorization spec covers authenticating a client to a server with OAuth 2.1. OAuth scopes can carry coarse permissions, but the spec does not define a mapping from scopes or roles to individual tools. You implement that mapping in the server or in a gateway in front of it.

Is hiding a tool from tools/list enough?

No. Hiding tools reduces what a model will attempt, but a client can still send a tools/call for a name it guessed. Enforce the same policy on every call, and back it with warehouse grants so a missed check cannot expose data.

Should each agent have its own credentials?

Yes. A distinct identity per agent lets you grant least privilege, revoke one agent without disrupting others, and attribute every action in the audit log. A shared service account makes all three impossible.