You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
title: "Govern agent actions with enterprise identity"
3
-
description: "The full authorization path from the identity provider to a single MCP tool call: how every allow-or-deny decision is made and recorded."
3
+
description: "Connect an identity provider, scope which MCP servers each role can reach, restrict tool calls by disposition, and audit every allow-or-deny decision."
4
4
guideCategory: "Governance"
5
5
---
6
6
7
7
import { Callout } from"@/mdx/components";
8
8
9
-
An agent connects to an MCP server and asks to call `create_refund`. Before that call executes, the platform answers two questions: who is driving this session, and is that person allowed to make this exact call?
9
+
An agent using an MCP server is authorized at two points: when the session is established and when each tool is called. Tying both checks to a real user identity from the identity provider makes every action attributable to a person and allows for better auditing of every allow-or-deny decision.
10
10
11
-
Most gateways answer the first question with a shared service account and the second with a server-level allowlist, which leaves security asking "who issued this refund, and why were they allowed to?" with no better answer than "the agent used the service account." This guide walks the full authorization path on the platform, from the identity provider down to the disposition of a single tool.
11
+
This guide covers:
12
12
13
-
## Authenticate agent sessions through the IdP
13
+
-[Connecting the identity provider](#connecting-the-identity-provider)
14
+
-[Scoping server access with roles](#scoping-server-access-with-roles)
15
+
-[Restricting which tools a role can invoke](#restricting-which-tools-a-role-can-invoke)
16
+
-[Adding deny rules as hard limits](#adding-deny-rules-as-hard-limits)
17
+
-[Auditing and remediating decisions](#auditing-and-remediating-decisions)
14
18
15
-
Private MCP servers reject anonymous traffic. When an agent connects without credentials, the server responds with a `401` and a `WWW-Authenticate` header pointing at OAuth protected-resource metadata, the standard MCP authorization handshake. The agent's client (Claude, Cursor, or a custom harness) starts an OAuth flow, and the person behind the agent signs in through the company's identity provider.
19
+
## Connecting the identity provider
20
+
21
+
In the [dashboard](https://app.getgram.ai), open **Organization settings > Identity > IDP and SSO** and configure Directory Sync. Okta and Entra are supported. [IDP and SSO](/docs/ai-control-plane/org-admin/identity) covers the connection setup in detail.
22
+
23
+

24
+
25
+
Once connected, identity metadata stays current automatically:
26
+
27
+
- Users, groups, and attributes sync from the identity provider.
28
+
- Role assignments track group membership, so access follows team changes.
29
+
- Deprovisioning a user removes their access immediately.
30
+
31
+
No agent-side setup is needed. When an agent connects without credentials, the server responds with a `401` and OAuth metadata (the standard MCP authorization handshake), and the agent's client prompts the user to sign in through the identity provider. Every session then carries the user, their organization, and their roles.
@@ -21,19 +37,19 @@ Private MCP servers reject anonymous traffic. When an agent connects without cre
21
37
/>
22
38
</video>
23
39
24
-
With [Directory Sync](/docs/ai-control-plane/org-admin/identity) connected, the MCP gateway gets a rich set of identity metadata that stays current:
40
+
## Scoping server access with roles
25
41
26
-
- Users, groups, and attributes flow in from Okta or Entra and stay fresh.
27
-
- Role assignments track group membership, so someone joining a team gets the matching access automatically.
28
-
- When someone is deprovisioned, their access disappears with them instead of outliving them as a stale grant.
42
+
A session can only attach to a server when the caller holds the `mcp:connect` scope for it. Both the built-in Member and Admin roles contain this scope. Callers without a matching grant are denied, and the denial is recorded against their identity.
29
43
30
-
Every session established through a supported agent then carries a detailed identity profile: this user, in this organization, holding these roles.
44
+
To limit which servers a role can reach:
31
45
32
-
## Authorize the connection to an MCP server
46
+
- Open **Organization settings > Secure > Roles & Permissions** in the dashboard.
47
+
- Create a role on the **Roles** tab.
48
+
- In the grant drawer, add a rule for the `mcp:connect` scope and pick the servers and projects it applies to.
33
49
34
-
With identity resolved, the first authorization gate fires when the session attaches to a server: the caller must hold the `mcp:connect` scope for that specific server.
50
+

35
51
36
-
Permissions are grants: a scope paired with a selector that says which resources it reaches. Roles are named bundles of grants. The built-in Admin role holds every scope; the built-in Member role holds the read-and-connect subset, and [custom roles](/docs/ai-control-plane/org-admin/roles-and-permissions) narrow further. A grant that lets a role connect to one server in one project looks like this:
52
+
Behind the UI, each rule is stored as a grant: a scope plus selectors naming the resources it covers. This rule allows connection to a single server in a single project:
37
53
38
54
```json
39
55
{
@@ -48,24 +64,21 @@ Permissions are grants: a scope paired with a selector that says which resources
48
64
}
49
65
```
50
66
51
-
Selectors support explicit wildcards (`"resource_id": "*"` reaches every server), so a role can be as broad or as narrow as the team it describes. A grant only constrains the keys it names: pin`project_id`and the grant follows the project; leave it off and the grant applies everywhere the scope does.
67
+
A selector only constrains the keys it names: pinning`project_id`limits the grant to one project, and leaving it out applies the grant in every project. Wildcards are supported (`"resource_id": "*"` matches every server).
52
68
53
-
If no grant satisfies the check, the session never attaches. The agent doesn't see a degraded server; it sees a denied connection, tied to the person whose session it was.
69
+
With Directory Sync enabled, roles come from the identity provider, so access follows directory group membership automatically. [Roles & permissions](/docs/ai-control-plane/org-admin/roles-and-permissions) covers scopes, system roles, and assignment in detail.
54
70
55
-
## Authorize each tool call
71
+
## Restricting which tools a role can invoke
56
72
57
-
Connecting to a server is not the same as being allowed to use everything on it. Each individual `tools/call` passes through a second check (same `mcp:connect` scope, same server) that carries two more dimensions: the tool name and the tool's disposition.
73
+
Connecting to a server does not grant access to every tool on it. Each tool call passes a second check that also considers the tool's name and disposition.
58
74
59
-
Disposition comes from a tool's MCP annotations. Tools declaring `readOnlyHint`evaluate as `read_only`;`destructiveHint`maps to `destructive`; the rest of the vocabulary is `idempotent` and `open_world`. Because the disposition describes what a tool does, policy can target classes of behavior instead of a per-tool list that goes stale every deploy.
75
+
Disposition comes from the tool's MCP annotations: `readOnlyHint`evaluates as `read_only`,`destructiveHint`as `destructive`, and the remaining values are `idempotent` and `open_world`. Targeting a disposition governs a class of behavior instead of a list of individual tools.
60
76
61
-
Where those annotations are read from depends on the backend, and the difference matters for who controls policy:
77
+
To let a role connect but only call read-only tools, add a disposition to its rule in the grant drawer: choose **Specific tools** and select an annotation.
62
78
63
-
- On a built server, annotations are captured on the tool definition when a deployment is processed.
64
-
- On a remote or tunneled server, annotations are the ones an administrator recorded on the server's **Inspect** tab, stored by the Control Plane and read from there on every call. The upstream response is not consulted on the hot path, so a third party cannot change the disposition its tools evaluate under by editing its own annotations. See [Recording tool metadata](/docs/ai-control-plane/distribute/mcp-servers/remote-servers#recording-tool-metadata).
79
+

65
80
66
-
A remote server with no recorded metadata resolves to the empty disposition. Grants naming a disposition still match it, because selector matching skips keys the check does not carry, so recording metadata is what makes disposition-based policy bite on a remote server.
67
-
68
-
A support role that can connect to the billing server but only invoke tools that declare themselves read-only looks like this:
81
+
The stored grant gains a `disposition` key on the selector:
69
82
70
83
```json
71
84
{
@@ -80,40 +93,44 @@ A support role that can connect to the billing server but only invoke tools that
80
93
}
81
94
```
82
95
83
-
With that grant, `get_invoice` succeeds and `create_refund` (a destructive tool on the same server) is denied, for this user, on this call, with no change to the server itself. Add a `tool` key instead to name specific tools rather than classes of them.
96
+
With this grant, `get_invoice` succeeds and `create_refund` (a destructive tool) is denied. To match specific tools by name, use a `tool` key.
84
97
85
-
## How an allow-or-deny decision evaluates
98
+
<Callouttitle="Record metadata on remote servers first"type="warning">
99
+
Built servers capture annotations during deployment. Remote and tunneled servers only use annotations an administrator recorded on the server's **Inspect** tab, so a third party cannot change how its tools are evaluated. Until metadata is recorded, tools on a remote server have no disposition and disposition-based rules will not distinguish them. See [Recording tool metadata](/docs/ai-control-plane/distribute/mcp-servers/remote-servers#recording-tool-metadata).
100
+
</Callout>
86
101
87
-
When a check fires, the engine evaluates it in four steps:
102
+
## Adding deny rules as hard limits
88
103
89
-
-**Load the caller's grants.** Everything the principal holds, through their synced role and any direct grants, is assembled for the organization.
90
-
-**Expand scopes.** Higher privileges satisfy lower ones: `mcp:write` satisfies an `mcp:read` check, and either satisfies `mcp:connect`. The same family never needs to be granted three times.
91
-
-**Match selectors.** For every key a grant constrains, the value must equal the check's value or be `*`. Keys the grant doesn't mention are skipped, which is why a grant scoped to `"disposition": "read_only"` still satisfies the connection-level check that doesn't ask about disposition.
92
-
-**Apply denies.** Deny rules evaluate as a separate pass with stricter matching, and a matching deny always beats a matching allow.
104
+
Allow grants can overlap: higher scopes satisfy lower checks (`mcp:write` covers `mcp:read`, and either covers `mcp:connect`), and selectors only match the keys they name. To keep something blocked regardless of overlapping allows, add an exception, a deny rule, to the role:
93
105
94
-
That last step is the safety property worth dwelling on. When multiple grants touch the same resource, the outcome isn't order-dependent or "last rule wins": deny wins. Suppose the Member role grants `mcp:connect` on every server, and a deny rule exists for destructive tools on the billing server. A member's session connects fine and reads fine, and the moment the agent reaches for `create_refund`, the deny matches and the allow doesn't matter. "Allowed" never happens by accident because two rules overlapped.
106
+
- Open **Organization settings > Secure > Roles & Permissions** and edit the role on the **Roles** tab.
107
+
- Under **Permissions**, expand the scope group and find the scope's allow rule.
108
+
- Select **Exceptβ¦** below the rule. The exception editor opens.
109
+
- Choose the servers, tools, or dispositions to exclude, then go back to save the rule.
95
110
96
-
## Audit every decision
111
+

97
112
98
-
The hardest question in access control is retrospective: not "what can this person do" but "why was this specific call allowed three weeks ago."
113
+
A matching deny always beats a matching allow, regardless of rule order. For example, keep the Member role's broad `mcp:connect` grant and add an exception for destructive tools on the billing server: members connect and read normally, but calls to `create_refund` are rejected.
99
114
100
-
Every check described above can be recorded as an authorization challenge that captures:
115
+
## Auditing and remediating decisions
101
116
102
-
- Who was challenged, as a directory identity rather than a token id.
103
-
- What scope was required, against which server and tool.
104
-
- Whether the outcome was allow or deny.
105
-
- How many grants were evaluated to reach that outcome.
117
+
Each check can be recorded as an authorization challenge. A challenge captures:
106
118
107
-
Because tool-call telemetry is stamped with the caller's directory snapshot (their attributes, groups, and role slugs at the moment of the call), the record composes: this person, in these groups, holding these roles, made this call, and here is the rule that permitted it.
119
+
- The user, as a directory identity rather than a token ID.
120
+
- The required scope, and the server and tool it was checked against.
121
+
- The allow or deny outcome.
122
+
- The number of grants evaluated.
108
123
109
-
Denied challenges are actionable from the [Roles & Permissions page](/docs/ai-control-plane/org-admin/roles-and-permissions): opening a challenge leads into the grant flow, where the missing permission can be added to one of the member's roles, scoped as narrowly as needed.
124
+
Challenges are listed on the **Authorization Challenges** tab of the [Roles & Permissions page](/docs/ai-control-plane/org-admin/roles-and-permissions), and unresolved challenges also surface on the organization home page. Viewing them requires the `org:read` scope, which both built-in roles include. Resolving one by changing roles requires `org:admin`, held only by Admins by default.
110
125
111
-
<Callouttitle="Rolling out"type="info">
112
-
Directory Sync and identity-stamped telemetry are available now. Authorization challenge logging is gated per organization; contact the team to have it enabled.
113
-
</Callout>
126
+

114
127
115
-
## Get started
128
+
Tool call telemetry also records the caller's directory state (attributes, groups, and roles) at the moment of the call, so every call traces back to a user, their roles, and the rule that permitted it.
116
129
117
-
Directory Sync is configured from the [IDP and SSO page](/docs/ai-control-plane/org-admin/identity) through the admin portal. Once connected, roles track the IdP automatically, every session resolves to a real person, and every tool call evaluates against grants that can be read, reasoned about, and audited.
130
+
Opening a denied challenge leads into the grant flow, where the missing permission can be added to one of the member's roles.
131
+
132
+
<Callouttitle="Availability"type="info">
133
+
Authorization challenge logging is enabled per organization. Contact Speakeasy to have it turned on.
134
+
</Callout>
118
135
119
-
For the announcement and product context, see the blog post [From your IdP to a tool call: how agent authorization works](/blog/governing-agent-actions-with-enterprise-identity).
136
+
For background on the authorization model, see the blog post [From your IdP to a tool call: how agent authorization works](/blog/governing-agent-actions-with-enterprise-identity).
0 commit comments