MastertheMesh
agentregistry · RBAC · access policies · keycloak · UI
How-to Part 2 of 2

Deploying an MCP tool through the AgentRegistry UI, part 2: fine-grained RBAC

TO
Tom O'Rourke
EMEA Field CTO · Solo.io

Part 1 put an MCP server through the registry UI: publish, deploy to kagent, expose through agentgateway. This part covers who is allowed to do any of that: registry access policies, bound to IdP groups, granting scope:verb per resource, with default deny underneath. Built and verified live with an admin and a developer user.

Access Policies principals · IdP groups scope:verb rules default deny arctl apply

Part 1 walked an MCP server through the registry UI end to end. Every click in it was made by one of two Keycloak users: alice in the admins group, and bob in developers. alice could do everything from the start; bob could do nothing at all until a policy said otherwise. This page is that policy: where registry RBAC lives, how a policy binds an IdP group to verbs on resources, and how to build the self-service policy that let a developer publish and deploy without being able to delete, touch secrets, or widen their own access.

Everything here ran live, including one trap that costs real time: a policy whose principal name does not exactly match the IdP group grants nothing, silently.

Two policy layers, one identity

Gateway policy and registry policy answer different questions, and both key off the same IdP groups:

LayerQuestion it answersWhere it lives
Gateway policyWhich tools does this caller see and callagentgateway, CEL over JWT claims (part 1)
Registry access policyWho may publish, edit, deploy and delete catalogue entriesThe registry, UI and arctl alike (this page)

Users live in the IdP

No user database here: users and group membership stay in Keycloak (or Entra, or Okta), and the registry consumes the token's Groups claim. The superuserRole install setting names the admin group; everyone else gets exactly what access policies grant.

Default deny

A user in a group with no policy can authenticate, and sees an empty registry. No error, no partial access: nothing until a policy says otherwise.

The worked example: a self-service policy for the developers group, giving an app team the publish-and-deploy loop from part 1 without runtime administration, secrets, deletion, or policy authorship. Step by step:

1. Create the group and user in the IdP

In Keycloak: the realm's Groups hold admins and developers, the user bob is a member of developers, and a group-membership mapper stamps the membership into each token as the Groups claim. Nothing user-related happens in the registry; if your IdP is Entra or Okta, this step is wherever your groups already live.

2. Start the policy

As a superuser: Access Policies → New Access Policy. Name self-service, description free-text. The Access Policies page is absent from a non-admin's navigation entirely, so a team can never widen its own grants.

3. Principals: bind the IdP group

Add one principal: Kind Role, Name developers, Role developers.

The Name field is the one that matters. The engine matches it byte-for-byte against the values in the caller's Groups claim. A descriptive label here ("developers-binding") produces a policy that grants nothing, silently: authentication succeeds and every list comes back empty. Put the group name in both fields.

4. Rules: grant the verbs

Each rule combines actions (scope:verb strings) with resources (a kind plus a name, exact or *, with optional tool/skill/prompt subresources for finer grain). Three rules make the self-service shape:

RuleActionsResourcesWhat it enables
1registry:read, registry:publish, registry:edit, registry:deployMCP Server / *Browse the catalogue, publish new servers, update their own. deploy is the documented verb for rollouts and is granted for forward compatibility; rule 3 is what the current build checks
2registry:readRuntime / *See the runtimes available to deploy onto
3registry:read, registry:publishAgent / *Create deployments: in the current build a deployment is authorised under the agent resource kind, so this rule is what makes the Deploy button work
The Access Policy rules editor showing three rules: publish, edit, read and deploy on MCP Server, read on Runtime, and publish plus read on Agent
The three rules in the editor. Actions are chips; anything not preset (like registry:deploy) is added through the custom field.

5. Create it and check the list

The Access Policies list showing the self-service policy bound to Role: developers with its actions and resources summarised
One policy, bound to Role: developers, summarising its actions and resources.

The saved policy also downloads as JSON, and that export is exactly what arctl apply takes, so policies can be authored in the UI and versioned in git:

{
  "description": "Self-service developers",
  "name": "self-service",
  "principals": [
    { "kind": "Role", "name": "developers", "role": "developers" }
  ],
  "rules": [
    { "actions": ["registry:read", "registry:publish", "registry:edit", "registry:deploy"],
      "resources": [{ "kind": "MCPServer", "name": "*" }] },
    { "actions": ["registry:read"],
      "resources": [{ "kind": "Runtime", "name": "*" }] },
    { "actions": ["registry:read", "registry:publish"],
      "resources": [{ "kind": "Agent", "name": "*" }] }
  ]
}

6. Verify as the developer

Log in as the developer user in a private window (the browser otherwise reuses the admin's Keycloak session). Before the policy exists, or while its principal name is wrong, an authenticated developer sees this:

bob's view of the registry with no effective policy: an empty catalogue and a trimmed navigation with no Access Policies, Secrets, Tracing or Gateways
Default deny: bob is authenticated, the catalogue is empty, and his navigation carries no Access Policies, Secrets, Tracing or Gateways. Lists return 200 with no items rather than errors.

With the policy in place the same login shows the catalogue and runtimes, and Create MCP Server and Deploy both work. Publish and edit are distinct verbs: creating a new entry checks publish, changing an existing one checks edit, so publish-only and edit-only roles are both expressible.

What the developer can do

Browse the catalogue, publish servers, edit their own entries, deploy to the runtimes they can see. The whole loop in part 1 ran under this policy.

What stays forbidden

Delete anything, touch secrets, add runtimes, author policy. A delete attempt gets a 403 that names the exact scope:verb it wanted, which makes policy debugging pleasantly literal.

The claim doing all the work

To show the identity underneath, decode the two users' tokens side by side. Same issuer, same audience, one differing claim, and both policy layers hang off it:

mint() { curl -s -X POST http://keycloak.localtest.me/realms/agentregistry/protocol/openid-connect/token \
  -d "grant_type=password&client_id=ar-cli-password&username=$1&password=$2" | jq -r .access_token; }
claims() { local p; p=$(mint "$1" "$2" | cut -d. -f2 | tr '_-' '/+'); while [ $((${#p} % 4)) -ne 0 ]; do p="${p}="; done
  printf '%s' "$p" | base64 -d 2>/dev/null | jq '{preferred_username, Groups, aud, iss, exp}'; }

claims alice alice
claims bob bob

alice returns "Groups": ["admins"] and bob returns "Groups": ["developers"]. That one value decides bob's registry rights on this page and his gateway tool list in part 1.

Scoping teams tighter than this

Resource names in rules are exact or * in the current release, so a strict per-team catalogue means enumerating that team's resource names in its policy; pair it with the registry's approval workflow (teams submit, platform approves) for the guardrails half. For the gateway side of scaling, per-server grants by name prefix and policy generated from registry state, see the scaling section in part 1.

Where it can go wrong

SymptomCauseFix
A user's registry is empty despite an access policy granting read on * (lists return 200 with no items) The policy's principal Name is a descriptive label rather than the IdP group name; the engine matches principals by Name against the Groups claim Set the principal Name to the group value itself, e.g. developers
The developer can publish but Deploy fails with a 403 naming registry:publish on an agent resource The policy lacks the rule for the agent kind; the current build authorises deployments under it Add rule 3 from the table above
The developer's browser opens the registry already signed in as the admin The Keycloak SSO cookie from the admin session is still live in that browser Use a private window or a separate browser profile for the second user
Group changes in the IdP do not show up in the registry The change lands in tokens minted after it; an existing session carries the old claim Sign out and back in, or wait for token refresh

That closes the pair: part 1 is what a team can do self-service; this page is how the platform decides which team can do it, from one policy object and one IdP claim.