Role-based access control for AI agents
An MCP server is a target system and its tools are entitlements. From there the role model falls out: four kinds of role, and your IGA sees each of them as a role it already knows how to certify.
I come from identity governance (IGA), so that was my starting point for Straza's role model. An MCP server is a target system. Its tools are the entitlements. I wanted the IGA to assign and review an agent's access using the roles it already understands.
Straza manages agent identities and controls what agents can do. It connects to your IGA for role assignments, enforces access and approval policies on MCP tool calls, and uses hooks in the agent’s harness to apply policies to local shell, file, and network actions.
Out of that I ended up with four kinds of role.
- Application roles carry the tools of an MCP server.
- Business roles compose other roles.
- Approver roles grant no tools and decide approval requests.
- Straza roles grant administration and self-enrollment permissions.
Application roles
The obvious move is one role per tool. I didn't want that. GitHub's official MCP server exposes about 80 tools in 20 toolsets; give an agent five servers like it and you're at a few hundred roles, and nobody is going to certify those one by one.
So in Straza the application role, the one kind that carries tools, is a pack of them, and I cut a server into a few packs by risk: github-code gets the read and pull-request tools, github-admin the rest. One GitHub server ends up as two or three roles.
github-code, the rest to github-admin.A role can name its tools, or it can take every tool of a server, including ones the server adds later. The console warns you when you pick the second. I name them.
Nothing forces one server per role. An application role can bind tools from several servers if you want it to. I keep to one server because a few entitlements per target system is the shape your IGA already expects.
Roles grant access. Policies set conditions.
Holding the role is the grant. If an agent holds github-code and no policy says otherwise, the call runs. A policy never grants anything. It names an application role and says which of its tools have to wait for a human, or which shell commands are refused outright. Shell and file calls on the agent's own machine are decided by the same policies; no role carries them.
A policy can only match application roles, and Straza refuses to activate one that names any other kind. That's because the application role is the one that actually carries the tools, so matching it covers everyone who holds it, directly or through a role that implies it. What a policy can say is its own post.
Business roles
Business roles compose other roles, and they're optional. For agent access, a business role composes application roles: java-agent-developer implies github-code, jira-tickets and artifactory-read. It can't bind a tool itself. Try it and Straza refuses, with the reason: a business role composes application roles and reaches tools through them. Straza resolves the whole chain when the agent connects, so an agent holding java-agent-developer reaches every tool of the three underneath.
Business roles can also compose Straza roles. For example, a team role can imply the administration roles of several MCP servers. Each member then administers those servers without receiving platform-wide administration rights.
You don't have to compose in Straza at all. Every Straza role is an entitlement your IGA can assign, so you can do the composition on the IGA side instead: a business role in your IGA that includes the imported application roles, and no business role in Straza. The midPoint setup I ship as a reference does exactly that (midPoint is an open-source IGA). Its access bundles are midPoint business roles that include imported Straza roles.
One direction is fixed: roles are born in Straza. Your IGA imports them and masters who holds them. It can't create one over SCIM.
On the wire a Straza role is a SCIM Group resource. That's SCIM's name for it, and it's still a role. Your IGA reads each one as an entitlement on the Straza system and writes membership back the way it does for any SCIM target. The export carries the role's kind, its servers and its tools, so a reviewer certifying github-code sees which tools that role grants. The sponsor is an attribute on the user.
Approver roles
The third kind grants no tools. An approver role is a set of people whose job is to decide. When a policy holds a call for a human, the rule names the approver roles that decide it. That can go one of two ways.
A hold is for a decision that takes minutes: the agent waits. An MCP call sits in the gateway until someone answers or the hold times out, and a hold nobody answers is a refusal. A shell command isn't held. It's refused with a reference, and when the agent retries it after a human has said yes, it runs.
A ticket is for anything slower. The agent gets a reference right away and moves on. It comes back later, day scale, and the grant covers one exact call. I prefer the ticket for anything that isn't a quick confirmation.
If the rule names no approver role at all, the agent's sponsor decides (the human your IGA names as accountable for that agent). If there's no approver role and no sponsor either, the request is refused.
Say you run an MCP server that can redeploy production over SSH. Bind its tools to an application role, require approval through a policy, and name prod-deployers as the approver role. When the call comes in, every member of that role gets it on their phone, and the first signed decision releases or refuses it. One signature is enough; there's no two-approver rule today. Each decision is signed on the phone by a key that was generated there and never leaves it.
Straza's own administrators are not automatically in that pool. Running Straza and approving a deployment are two different permissions. If you want admins deciding, the policy has to name them.
Straza roles
The last kind covers permissions inside Straza itself, including platform administration, MCP server administration, and enrolling an approval device. These roles grant no tools to an agent.
straza-admin administers the whole platform. Straza MCP Global Admin (straza-global-mcp-admin) is narrower: it administers every MCP server, including registering servers, changing their configuration and credentials, and managing which application roles can access their tools. It does not grant platform-wide administration or the right to decide approval requests.
You can also delegate one server. When you register jira, Straza automatically creates its administration role, mcp-admin-jira. Your IGA imports that role and assigns it to the people who should maintain that server. Creating the role does not assign it to anyone.
A holder can maintain that server’s connection and credentials, enable or disable it, inspect its health and logs, test calls, and define the roles that reach it: application roles named after the server, each with an explicit list of its tools, which your IGA assigns like any other role. Holding that role alone does not let them register or remove a server, give an existing role access to it, assign roles, or edit policy. Changes to the command or container runtime also require broader MCP administration rights because they affect what runs on the gateway host.
For example, Petra can create jira-readers and select the Jira tools it includes. This is an application role owned by jira, not a fifth kind of role. It names tools explicitly; it cannot include other servers or automatically include tools added later. Your IGA assigns the role and can include it in a business role.
Once the role has holders, directly or through a business role, Petra cannot change its tool list or delete it. She can define a new role for changed access and let the IGA assign and review it. Server administration alone still grants neither permission to edit approval policies nor authority to approve a request.
jira-readers; the IGA assigns them. Approval authority remains separate.The role follows the server’s lifecycle. Remove the server and Straza removes its generated administration role, the roles the server owned, and their assignments, each on the audit chain. A team that maintains several servers can receive their administration roles through a business role in Straza or a bundle in the IGA.
The self-enrollment roles remain separate: straza-enroll-mobile lets a person enroll their own phone as an approval device; straza-enroll-browser does the same for a signed-in browser. Enrolling a device does not itself grant approval authority.
To your IGA, all of these are ordinary entitlements it can import, assign, review, and revoke. An access review can distinguish who administers Straza, who administers all MCP servers, and who maintains one server.
Classic RBAC, applied at runtime
This builds on classic RBAC. Your IGA imports, assigns, certifies and revokes all four kinds of role. Approval roles are familiar too: existing IGA workflows already use roles and approver relations to decide who may approve an access request.
In Straza, the approver role decides whether a particular agent tool call may run. The agent already holds the role that grants access to the tool; a policy requires a human decision for that call. Who may make that decision is itself an entitlement your IGA grants and reviews.