Skip to main content
Back to blog
AI & MCP

How to map Okta groups to MCP tool access

Sagar Batchu

Sagar Batchu

Published · 26 min read

How to map Okta groups to MCP tool access

TL;DR

Group-to-tool mapping derives which Model Context Protocol (MCP) tools a person may call from the groups they belong to in the identity provider, and has the MCP gateway enforce that decision on every tool call. Okta stays the source of truth: groups arrive over SCIM directory sync, each group maps to a role, and each role carries an mcp:connect grant narrowed to specific servers or specific tools. Blocks handle the exceptions, audit logs and authorization challenges show what was decided, and removing someone from a group in Okta takes the tools away on the next sync.

If you're the Okta admin who has been asked to set up MCP, the hard part is probably done already. The groups exist, they track the org chart, and joiners and leavers flow through them. What's missing is a way to make those groups decide something finer than app access. A Model Context Protocol (MCP) server for a billing system exposes get_invoice and create_refund through one URL, and the support team should have the first without the second. The group should decide the tool, and the gateway should enforce it, so that Okta remains the only place anyone changes access.

This post explains what group-to-tool mapping is, where Okta's own controls stop, how a few other MCP gateways approach the problem, and then walks through the setup in the Speakeasy AI Control Plane as a worked example: connecting Okta, syncing groups, mapping them to roles, narrowing access to servers and tools, handling exceptions, optionally adding Cross App Access, verifying the result, and offboarding. Each step links to the docs for the full UI reference.

What is group-to-tool mapping?

Group-to-tool mapping is the practice of deriving MCP tool permissions from identity provider groups and enforcing them at the gateway on each tool call. It has three parts, and each part has one owner:

  1. The source of truth. Okta groups, and optionally profile attributes such as department, pushed to the gateway over SCIM. Nobody edits membership in the gateway.
  2. The translation. A mapping from each group to a role in the gateway. The role is a named set of permissions, so several groups can share one, and a change to the role reaches all of them.
  3. The enforcement. Grants on each role that name which servers, and which tools on those servers, the role may call. The gateway checks them when a session attaches to a server and again when each tool is called.

The reason to split it this way is the same reason network teams separate the directory from the firewall rule. The directory says who someone is; the rule says what that kind of person may reach. Keeping the rule in the gateway means it can be as specific as a single tool, and keeping membership in Okta means access still follows the org chart.

Isn't Okta app assignment enough?

For some teams it is. Okta app assignment and group-based sign-on policies are mature, auditable, and already owned by the people who will be asked about access. If the question is "who may reach this MCP server at all", assigning the Okta app to a group answers it, and so does a reverse proxy that reads Okta groups at the door. Cloudflare Access, for example, offers an identity provider group selector that reads Okta groups directly in its policies.

The gap is that one MCP server is many tools, and they don't carry the same risk. Okta sees a sign-in to an app. It doesn't see the tools/call request that arrives inside a session Okta has already approved, so it can't distinguish a read from a refund. The decision about which tool a group may call has to be made by something in the path of that request, which is the gateway. Okta still owns who is in the group. The gateway owns what the group may do once it's in.

There is a second, smaller gap. App assignment is binary per app, so exceptions ("everyone in Engineering, except contractors, except this one contractor who is on call") turn into a growing set of Okta groups created only to express gateway policy. Putting the exceptions in the gateway as blocks keeps the Okta group list about people rather than about tools.

How do other MCP gateways map identity provider groups to tools?

Several gateways can do some version of this, and they put the boundary in different places.

  • Cloudflare MCP server portals authenticate users through Cloudflare Access, so the same Okta group selectors apply to who may open a portal. Admins curate the tools each portal exposes, and a turned-off tool can't be called through that portal. The same page documents two boundaries: users blocked from a server by an Access policy can still reach it by its direct URL unless Access is configured as that server's OAuth provider, and independent MFA on a server's policy is not enforced when the server is authorized through a portal. The docs describe tool curation per portal rather than per group, so giving two groups different tools from the same server means running two portals.
  • Kong AI Gateway added ACL tool control to its AI MCP Proxy plugin in 3.13. Allow and deny lists match Kong Consumers and Consumer Groups, deny is evaluated first, the tool list a client discovers is filtered to what it may call, and every attempt is written to an audit log. Identity comes from Kong's authentication plugins, such as OpenID Connect. Two details from the same page are worth planning for: a per-tool ACL replaces the default ACL for that tool rather than merging with it, so each one has to list every allowed subject, and the plugin is part of the AI Gateway Enterprise offering.
  • IBM ContextForge is open source and self-hosted. Its RBAC model has teams that map from SSO groups, team-scoped roles with permissions such as tools.execute, and session tokens that resolve team membership from the database so revoked memberships take effect right away. The Okta tutorial maps groups to teams with an OKTA_GROUP_MAPPING environment variable read from a groups claim at sign-in, followed by a gateway restart. Syncing Okta groups outside sign-in appears there as a script you write against the Okta API.
  • Docker MCP Gateway is open source and runs each MCP server in an isolated container. Its docs organize control around custom catalogs of approved servers and profiles that decide which servers a client sees. The governance edition is invite-only, and the public pages linked here describe catalogs and profiles rather than directory groups as the unit of control.

If you need to run the gateway inside your own infrastructure, ContextForge and Docker are the options in this list that do that, and that's a legitimate reason to choose them. Speakeasy is a managed gateway, so the rest of this post assumes that model.

Mapping Okta groups to tools in Speakeasy, step by step

The steps below follow the order an Okta admin would set this up in the Speakeasy AI Control Plane. Steps 1 to 5 are the core mapping. Step 6, Cross App Access, is optional and solves a different problem. Steps 7 and 8 cover checking the result and taking access away.

Before you start

  • Speakeasy admin. Every step in the dashboard needs the org:admin scope, which only the default Admin role holds.
  • Plan. Single sign-on and directory sync are gated by plan. If the Configure buttons on the IDP and SSO page offer to contact the team instead of opening setup, the features aren't on the current plan yet.
  • Okta admin. Connecting Okta and setting up Cross App Access need an Okta administrator who can create apps, register AI agents, and grant API scopes.
  • Preview. The Identity providers tab used in steps 1 and 6 is in preview and enabled per organization. Group-to-tool mapping itself runs on directory sync, so if the tab isn't there yet, start at step 2.

Step 1: Connect Okta with read-only scopes

Open Organization settings > Team > IDP and SSO, go to the Identity providers tab, select Connect Okta, enter the Okta organization URL, and select Continue. The Setup tab offers two methods: Okta Integration Network (client secret), which installs the Speakeasy integration from the Okta catalog, and Custom API Services app (private key), which authenticates with a public key Speakeasy hosts so there is no secret to copy. The Enterprise Managed Auth docs list every field for both.

Both methods request the same three Okta API scopes, all read-only:

ScopeUsed to
okta.apps.readList applications and their user and group assignments
okta.users.readRead the users assigned to those applications
okta.groups.readRead the groups assigned to those applications

Speakeasy never writes to the Okta org. The connection syncs applications, suggests catalog MCP servers that match apps the organization already uses, and checks Cross App Access readiness. It does not issue tokens or sign anyone in, which is worth telling whoever reviews the Okta change request. When verification passes, the Okta card shows Verified and a Manage Okta button. If it fails, the Setup tab lists what to fix, such as a missing scope, and Re-verify checks again.

Step 2: Turn on single sign-on and directory sync

Sign-in and group membership come from the Single sign-on tab on the same page, which has three sections: Domain verification, Single Sign-On, and Directory Sync. Verify the email domain first, because single sign-on needs it. Single sign-on works with Okta over SAML 2.0 or OpenID Connect, so Okta's MFA, device trust, and conditional access policies apply to every sign-in and the platform holds no separate password.

Directory sync is the step that makes groups count. It uses SCIM: Okta pushes users, groups, group memberships, and profile attributes such as department and job title, and the platform applies each batch of changes in one transaction. The sync worker waits about ten seconds to batch closely spaced changes, so a group change in Okta reaches the gateway in seconds rather than on a nightly schedule. Turning it on changes four things, and the Directory Sync card lists them before you commit: members are provisioned from the directory, roles are assigned from group mappings, members can't be invited by hand, and roles can't be assigned to members by hand. How Directory Sync works shows the event flow.

The last of those is the point. Once directory sync is on, the role editor still lists who holds each role, but the rows read Managed by directory sync, and the only way to change a person's access is to change their groups in Okta.

Step 3: Map Okta groups to roles

With a directory connected, Configure role mappings on IDP and SSO lists every directory group with a role picker. Groups with no role sort first, because their members get nothing from them. For each group, select Assign role and pick a role, or select Create role… to open the role editor with the group name filled in; saving the new role maps it automatically. Select Sync groups to pull in groups that haven't sent any changes yet. Full detail is in Directory sync and roles.

A few rules shape how to design the mapping:

  • Each group maps to one role. When one role should cover a whole department, Map by attribute maps an exact attribute value, such as a department_name, to a role instead of mapping many groups.
  • Roles add up. A person holds every role mapped from their groups and attributes, and their access is the union of all of them. Mappings have no priority order.
  • Member is the floor. Every synced person already holds the Member system role, which includes mcp:connect, so mapping a group to Member adds nothing. While Member can connect to every server, a group-specific role adds nothing for connection either. Editing Member changes access for everyone at once, so decide what the floor should be before mapping groups, and put server access above that floor on custom roles.
  • Admin is a small group. Mapping a group to Admin gives full administration to everyone in it.

With directory sync on, each role's page also lists the directory groups and attribute values that grant it, with member counts, so the mapping can be read from either side.

Step 4: Narrow mcp:connect to specific servers and tools

A role is a set of scopes, and the one that governs tool calls is mcp:connect. In Organization settings > Team > Roles & Permissions, open the role and select Add permissions. Each MCP permission row carries its own narrowing control with four options: All servers, Specific projects, Specific servers, or Specific tools. The role editor docs cover the pickers.

A typical first pass maps a handful of groups to a handful of roles:

Okta groupRolemcp:connect narrowed to
EngineeringDeveloperSpecific servers: GitHub and Linear
SupportSupportSpecific tools: read-only tools on the billing server
FinanceFinanceSpecific servers: the billing server

Behind the UI, each rule is stored as a grant, a scope plus selectors naming the resources it covers. The Support row above stores a disposition on the selector, so get_invoice succeeds and create_refund, which carries the destructive annotation, is denied:

{
  "scope": "mcp:connect",
  "selectors": [
    {
      "resource_kind": "mcp",
      "resource_id": "<billing-server-id>",
      "disposition": "read_only"
    }
  ]
}

Specific tools narrows in one of two ways, and they age differently. Narrowing by tool name is exact, and it has to be revisited every time the server gains a tool, because a new tool is outside the list. Narrowing by annotation (read-only, destructive, idempotent, or open-world) keeps covering new tools, and it trusts the annotations the tools carry. For a server you built, annotations are captured at deployment. For a remote or tunneled server, the platform only uses annotations and tool names an admin has recorded on the server's Inspect tab, so a third party can't change how its tools are evaluated. Until that metadata is recorded, the Specific tools picker has nothing to list for the server and disposition rules can't tell its tools apart. Recording tool metadata explains the reconciliation.

The same rules appear on each server's Team Access tab, which can also name a directory Group or Attribute directly once directory sync is connected. Roles are easier to reason about as the number of servers grows, because one role change reaches every server it names. When a role gets Connect access to a server, automatic MCP distribution also adds that server to the role's plugin and publishes the update to the project's connected marketplace, so people who installed the role's plugin receive the server through their client's update process.

One thing not to use as a permission is tag-based tool filtering. It lets a client connect to a focused subset of a server's tools to keep context small, and the client chooses the tags. Access control belongs in the grants.

Step 5: Block the exceptions

Grants add up and blocks subtract. Each read, write, and connect scope has a blocking counterpart, such as mcp:blocked_connect, that withdraws the scope for matching resources even when another role grants it. That's how to keep a broad grant and carve something out of it without inventing a new Okta group.

On a server's Team Access tab, setting Connect to No access on a row whose access comes from a role writes a block for that server only, and every other server is untouched. Block destructive tools on the Connect line is a shortcut for the most common restriction. The tab confirms before writing a block on a role or Everyone, because a block on a role outranks grants that come from other roles. The Team access docs list the three blocks the platform refuses: a block on the Admin role, a block on view for an audience the person saving belongs to, and any block on an agent.

The exception to the exception is a named person. Release 1.33.0 changed one precedence rule: a grant made directly to a person for a specific server outranks a block they inherit from a role or from Everyone, and the server's access page shows who keeps access through their own rules before a role is removed. So the on-call contractor in a blocked group can keep access by name, without restructuring group membership in Okta. When a grant and a block do collide, the person appears under Conflicting roles on the server's Team Access tab instead of silently disappearing from the list of people the rules reach.

Exceptions can also apply to the client rather than the person. Shadow AI, generally available under Guardrails for every organization, inventories the coding harnesses and assistants that enrolled devices report, and each MCP-speaking tool can be marked Approved or Blocked. A Blocked tool is refused when it authenticates, on every MCP server in the organization, regardless of the person's groups. The boundaries are documented: a tool on an unenrolled device appears nowhere, a decision can only be enforced for a tool that publishes a client ID metadata document, and decisions about tools that ship no MCP client are recorded rather than enforced.

Step 6 (optional): Add Cross App Access for upstream services

Steps 1 to 5 decide which tools a person may call. They don't change how the tool authenticates to the service behind it. Without more setup, each MCP server that uses User Identity asks each person to connect Linear, Notion, or another service separately, creating per-person OAuth grants the organization can't see or revoke from one place.

Enterprise Managed Auth closes that gap. Speakeasy implements the MCP Enterprise Managed Authorization extension, which Okta supports as Cross App Access (XAA). A person signs in once with Okta, and on each tool call the platform exchanges their Okta ID token for an Identity Assertion JWT Authorization Grant (ID-JAG) addressed to the downstream service, then redeems it for an access token. Okta checks, on every exchange, that the person is assigned and that a resource connection exists for that service. Groups then decide access twice: Okta's assignment decides whether the person may reach the upstream service, and the Speakeasy role decides which tools they may call on it.

Setup happens on the Cross App Access tab under Manage Okta, which follows a Cross App Access setup checklist:

  • In Okta, register an AI agent named Speakeasy Agent under Directory > AI Agents, and let Okta create a linked OIDC app.
  • Activate the linked app and assign the Okta groups who use MCP servers through the platform.
  • Enter the Agent ID, and optionally the linked app's client ID, and select Save agent.
  • Trust Okta on the organization's user session issuer, allow the refresh token grant and the offline_access scope on that Okta app, and select the issuer on each server that should use it.
  • For each eligible server, select Review setup, then Open Okta connections to create the resource connection, enter the Issuer URL of the downstream service, and select Confirm setup.

The Okta Integration Network install from step 1 doesn't cover any of this; Cross App Access is set up separately. A confirmation records the settings an admin reviewed and isn't a live check. A server shows as verified once a real token exchange succeeds, and Sign in as yourself to check runs that exchange as you and reports which stage failed for each service. The limits are in the docs: Okta is the only supported identity provider, delegation needs OIDC sign-in because SAML sign-in doesn't create one, and the downstream service needs its own authorization server that supports the identity assertion grant. Services without that support keep the ordinary connect flow. The Okta IdP gateway use case summarizes the model for readers who don't need the setup detail.

Step 7: Verify with the rule preview, authorization challenges, and audit logs

Check the mapping from three directions before announcing it.

Who the rules reach. Below the rules on each server's Team Access tab, People this reaches resolves every rule to the people it covers today, with their Platform access, their MCP access (how many tools they can call, with names on hover), and Granted by, the rules that gave them access. Pick a person from each Okta group and confirm the tool count matches the table you designed in step 4.

What was denied. The Authorization Challenges tab on Roles & Permissions lists denied requests: who tried what, and which scope was missing. Filter pills switch between Denied, Resolved, and All. Opening a denied challenge leads into the Grant Access flow, which assigns a custom role covering the denied action. With directory sync on, treat a recurring denial as a signal to change the role or the group mapping, so the fix lives in the same place as the rest of the policy. Authorization challenge logging is enabled per organization, so ask Speakeasy to turn it on if the tab is empty.

What changed. Audit Logs, under Organization settings > Secure > Audit Logs, records administrative activity in one feed with resource:action badges, the actor, a Show diff control on rows that changed data, and a via badge when a change came through an API key, the Platform MCP, or the Project assistant. Filter by Action or Actor to review the role and access changes from steps 3 to 5. Identity chaining changes from step 6 are audited too. The Audit Log Insights assistant answers questions such as "What changed recently?" over the same feed.

Step 8: Offboard from Okta

Taking access away is where group mapping saves the most work, and the timing differs by layer.

  • Leaving a group. Removing someone from an Okta group removes the role mapped from it as soon as the change syncs. Roles from their other groups stay.
  • Leaving the company. Removing, deleting, or suspending a person in Okta deprovisions them. Their membership and role assignments end right away, and agents they own are blocked until an admin reassigns them. Reactivating the person restores their membership and mapped roles.
  • Upstream tokens. With Cross App Access, deactivating the person or unassigning them from the linked app makes Okta refuse the next renewal or exchange. Downstream tokens already issued stay valid until they expire, so revocation at the upstream service isn't instantaneous. The MCP session itself ends at the server's session length, and ending a session in the platform doesn't revoke anything in Okta.
  • Incidents. For a suspected compromise, a killswitch stops one person's tool calls on all or selected servers right away, without ending their sessions or changing their roles. It covers their signed-in MCP sessions, not API keys or registered agents. Disable the account in Okta as well, and revoke the session if you want a clean reconnect.

Where this setup stops

Group-to-tool mapping governs people. A few things sit outside it, and they're easier to plan for up front.

Agents aren't in Okta's directory. A directory syncs people, so agent identities get roles directly through the Assign Agents section of the role editor, which is never gated on directory sync. An agent's grants are filtered against the agent runtime scope allowlist, so a role can't hand an agent a scope it couldn't be granted directly, and blocking scopes can't be written against agents. See Agent identity.

Network reachability is a separate control. Roles decide who may call a tool; they don't decide whether a server is reachable from the public internet. Speakeasy is a managed gateway, and keeping servers off public routes is done with private network access over a Tailscale tailnet, which requires an Enterprise organization. Each hosted server or gateway can then be set to Public only, Public and private, or Private only. For teams whose security review starts with "can this stay on our network", that requirement belongs in the plan from day one rather than after the rollout. Private MCP gateway explains the model and Tailscale private access covers setup.

Tool-name rules need upkeep. A rule that lists tools by name doesn't cover tools added later. That's the right default for a security boundary and it's still work: someone has to review new tools when a server changes, or the role has to narrow by annotation and accept the annotations as the source of truth.

What to do next

Start with one server and two Okta groups, one with full access and one narrowed to read-only tools, and confirm the result in People this reaches before adding more. The Govern agent actions with enterprise identity guide covers the grant and selector format in more depth, and From your IdP to a tool call explains the authorization path behind each check.

Frequently asked questions
What is group-to-tool mapping for MCP?
Group-to-tool mapping derives which Model Context Protocol (MCP) tools a person may call from the groups they belong to in the identity provider, and has the MCP gateway enforce that decision on every tool call. Groups arrive from the identity provider over SCIM directory sync, each group maps to a role in the gateway, and each role carries grants narrowed to specific servers or specific tools. Membership changes in the identity provider, and access follows.
Can Okta control which MCP tools a user can call?
Okta controls who is in each group and which apps each group is assigned to, but it does not see individual MCP tool calls, which arrive inside a session Okta has already approved. An MCP gateway in the path of each tools/call request makes the per-tool decision, using roles mapped from Okta groups. Okta stays the source of truth for membership, and the gateway enforces what each group may do.
What is MCP RBAC?
MCP RBAC is role-based access control applied to MCP servers and their tools. A role is a named set of permissions, people or groups are assigned to roles, and the gateway checks the role's permissions when a session attaches to a server and again on each tool call. In the Speakeasy AI Control Plane, the permission that governs tool calls is mcp:connect, which can be narrowed to all servers, specific projects, specific servers, or specific tools.
Which Okta API scopes does Speakeasy need?
Speakeasy uses three read-only Okta API scopes: okta.apps.read to list applications and their assignments, okta.users.read to read the users assigned to those applications, and okta.groups.read to read the groups assigned to them. Speakeasy never writes to the Okta org, and the API service connection does not issue tokens or sign anyone in. Group membership for access control arrives separately over SCIM directory sync.
Do I need Cross App Access to map Okta groups to MCP tools?
No. Group-to-tool mapping runs on single sign-on and SCIM directory sync, with groups mapped to roles and roles narrowed to servers and tools. Cross App Access solves a different problem: it lets the platform get tokens for upstream services such as Linear or Notion from Okta on each tool call, so people don't connect each service separately. It is optional and set up separately from the read-only Okta API connection.
What is Cross App Access (XAA)?
Cross App Access is Okta's support for the MCP Enterprise Managed Authorization extension. A person signs in once with Okta, and on each tool call the gateway exchanges their Okta ID token for an Identity Assertion JWT Authorization Grant addressed to the downstream service, then redeems it for an access token. Okta checks that the person is assigned and that a resource connection exists, so removing access in Okta stops it at every connected service on the next exchange.
How do I restrict an Okta group to read-only MCP tools?
Map the Okta group to a role, add mcp:connect to the role, and narrow it to Specific tools using the read-only annotation on the servers in question. Tools that carry the destructive annotation are then denied for that role. For remote or tunneled servers, record tool metadata on the server's Inspect tab first, because the gateway only uses annotations an admin has recorded for third-party servers.
How do I give one person an exception to a group's access?
Use blocks and direct grants rather than new Okta groups. A block, such as mcp:blocked_connect on one server, withdraws access for a role even when another role grants it. A grant made directly to a named person for a specific server outranks a block they inherit from a role or from everyone, so one person can keep access while the rest of their group is blocked. People caught between a grant and a block appear under Conflicting roles on the server's Team Access tab.
What happens to MCP access when someone leaves an Okta group?
With SCIM directory sync, removing someone from an Okta group removes the role mapped from that group as soon as the change syncs, and roles from their other groups stay. Deactivating or suspending them in Okta deprovisions them, ending their membership and role assignments and blocking agents they own until an admin reassigns them. With Cross App Access, downstream tokens already issued stay valid until they expire, so upstream revocation is not instantaneous.
Do AI agents get roles from Okta groups?
No. A directory syncs people, not agents, so agent identities get roles directly through the Assign Agents section of the role editor, which is never gated on directory sync. An agent's grants are filtered against the agent runtime scope allowlist, so a role cannot give an agent a scope it could not be granted directly, and blocking scopes cannot be written against agents.
How do I check that Okta group mappings are working?
Use three views. People this reaches on each server's Team Access tab resolves every rule to the people it covers and how many tools each can call. The Authorization Challenges tab on Roles and Permissions lists denied requests with the missing scope. Audit Logs records each role and access change with the actor, a diff, and the surface it came from.
Can other MCP gateways map Okta groups to tools?
Several can, with different boundaries. Cloudflare MCP server portals use Access policies that read Okta groups and curate tools per portal. Kong's AI MCP Proxy plugin has allow and deny lists per tool that match Consumers and Consumer Groups. IBM ContextForge, which is open source and self-hosted, maps Okta groups to teams through configuration read at sign-in. Each vendor's docs describe the limits, such as Cloudflare's note that direct server URLs bypass portal policies unless Access is the server's OAuth provider.
Is Speakeasy self-hosted?
No. Speakeasy is a managed gateway. To keep MCP servers off public routes, Enterprise organizations can use private network access over a Tailscale tailnet, with each hosted server or gateway set to public only, public and private, or private only. Teams that need to run the gateway in their own infrastructure should look at self-hosted options such as IBM ContextForge or Docker MCP Gateway.

If you're working through an Okta rollout for MCP and the group design is the part that's stuck, we'd like to hear how you're approaching it. Talk to our team.

AI everywhere.

Control here.