# How to narrow to least privilege MCP tool calls

> Narrow to least privilege MCP tool calls using the audit rows your pilot already produced, then plan the two-minute wait and the verification call that proves the rule landed.

**TL;DR** Elaichi records one audit row per tool-call attempt, naming the tool, the account reached and the outcome, so the tool set your pilot actually used is measurable rather than guessed. Narrow from that recorded set, not from job titles, and write the rule against a role or a single user. A restriction change takes effect within about two minutes, so write the rule, wait, then re-list tools and make one blocked and one allowed call to confirm it landed.

## What a pilot leaves behind

A six-week pilot ends with traffic, not policy. Twenty or thirty people pointed Claude, ChatGPT or Cursor at MCP ([Model Context Protocol spec](https://modelcontextprotocol.io/specification), checked September 2026, the standard way an AI client calls tools), connected the accounts they needed, and got on with work. Nobody wrote a rule, because the organization default is the absence of any rule, and that means allow-all. Narrowing to least privilege MCP tool calls is a measurement problem before it is a policy problem. The audit log, the append-only record of what happened, already holds the answer.

Guesswork fails in a specific way. It over-permits the tools people discuss in meetings. It under-permits the ones an automation quietly depends on. Asking users does not fix it either. They describe the outcome they wanted, not the operations the model called to get there. The second half of that gap is what breaks a workflow at 3am, and it is why a narrowing pass gets reversed a week later.

## Which audit fields tell you what was actually used

Elaichi write one entry per tool-call attempt, succeeded or failed, and each record names the account reached. That is the raw material for a narrowing decision.

Each record carries the operation and the tool, the connection, the classification, whether the call was approved, the outcome, and an error code. The recorded connection is the account actually reached, taken from the execution rather than from the intent. So "which of our two Notion workspaces did the agent write to" has an answer in the row itself. `actor_kind` is a stored field rather than a guess made afterwards from a user agent, and `ai_assistant` is one of its values, alongside `user`, `system`, `staff`, `scim` and `api_token`. Audit events and application logs share one record shape, so a single query answers what happened instead of two systems being lined up by eye.

Two limits shape the analysis. Argument names and counts are recorded; argument values never are. You can see that a filter argument was passed, not what it filtered on. The audit trail is also eventually consistent, so a row from the last few seconds may not have appeared yet. Neither limit changes the tool inventory you can derive. Both change how you write the query.

Failed attempts are as informative as successes. A tool somebody tried twice and abandoned was discovered, not needed. Audit is newest-first, cursor-paginated, and filterable by free text, category, actor, action kind and time. If you would rather group the rows somewhere else, export forwards to your own destination: Datadog is implemented, and Splunk HEC and Microsoft Sentinel are accepted but not yet delivering.

## How do you derive least privilege MCP tool calls from audit rows?

Work from the recorded set, then subtract, then verify. Elaichi serves one organization-wide MCP endpoint (a single network address that receives every client's calls), so the usage matrix is one query rather than one per team.

1. Filter or export the pilot window, start to end. Include failures.
2. Group by tool, connection and actor. You now have who called what, in which account.
3. Separate the automation traffic. Rows with `actor_kind` of `api_token` or `ai_assistant` behave differently from a person exploring. A synthetic tool runs a graph of steps, and every step goes through the same restriction and audit pipeline as any other call, so each step is its own row.
4. Mark each tool used, tried, or never seen. Never-seen tools are the safe part of the cut.
5. Decide where the rule goes, role or user.
6. Write the rule, wait, then verify with a real call.

Keep the list of tools that were tried and failed with a permission-shaped error. Those are the calls a user will ask you about within a day of the change, and having the list in hand turns a surprise into a reply.

## Role rule or user override?

Restriction targets are role or user only. There is no organization target, because the organization default is the absence of any rule, and the absence of a rule means allow-all.

Put the general shape on the role. Every Elaichi member holds exactly one role, enforced by a unique index, so a role is a complete persona rather than a bolt-on, and a rule written against it covers everyone who holds it. Reserve user targets for genuine exceptions, such as one contractor who needs less than the team.

The cost of a user target is worth knowing before you use one. A user-targeted rule replaces role rules entirely. It does not layer on top of them. Move one person to a user rule and that person stops inheriting the role rule you spent the afternoon writing, including its blocks. If the exception is one extra tool, the user rule has to re-list the base tools as well.

## Why a first allowlist often denies everything

An allow rule that names nothing denies everything. the allowlist stage engages on the presence of an allow rule, not its contents, so an allow rule that names nothing denies everything, so an empty allow list is the strictest thing you can express. That is the most common way a first narrowing pass takes a team offline.

Two precedence rules decide what your written list actually means. Within the winning layer, allow rules union, block rules union, and blocks always beat allows. Across layers, user override beats role rule beats organization default.

Enforcement runs at four points against the same resolver: browse, connect, advertise and execute, plus a final check on the fully substituted outbound URL. A tool withheld by a restriction never reaches the advertised list. Write the rule against a spare role first if you want to see its shape before a production team feels it.

## Does an allow rule survive a tool being renamed?

Yes, because the rule binds the operation rather than the label. A block matches the advertised tool name or the pinned operation. An allow matches the pinned operation only.

A rule is written against a connector and a tool, but the canonical resource and method are pinned against the catalog at write time. The advertised name of a tool can be changed by whoever edits that connector's documentation, so the name is a token the governed party controls. Governance binds the operation, never the label. The reasoning behind the asymmetry is worked through in [why a block matches the name and an allow matches the operation](/blog/block-matches-name-allow-matches-operation/).

## What happens when a restriction lands mid-run

A multi-step run can finish three steps and fail the fourth. A synthetic tool in Elaichi is a graph of steps with no cycles, each step calls a connection's tool, and steps are checked against restrictions independently.

That is the failure mode to plan for. The half-finished state sits in the third-party system, not in Elaichi. A run that created a ticket, posted a comment, then could not attach the file leaves a ticket somebody has to finish by hand. The model cannot undo the calls it already made. Audit shows which step failed and with what error code, so the recovery path is visible. Recovery is still manual.

Three habits reduce it. Read the pilot rows for scheduled or token-driven traffic before you write anything, because those runs have nobody watching them. Cut never-seen tools first and tried-but-unused tools in a second pass. Land the change in a quiet window for the systems involved, not a quiet window for your own calendar.

## Do collapsed tool lists get around a restriction?

No. Past a threshold of 30 tools the connected tools collapse behind two meta-tools, `search_tools` and `execute_tool`, and both run through the same gates as a directly advertised tool.

The threshold counts catalog operations and connected tools together. The Elaichi catalog runs to 450+, and the control-plane catalog alone is dozens of operations, so one connected app is normally enough to trip it. Collapse is the normal case, not an edge case. `execute_tool` is only a naming indirection. It unwraps to the same name and arguments and falls through the identical gates, so there is no separate execution path and no privilege in it. Control-plane operations stay listed individually, and `search_tools` never returns one. Tools withheld by a restriction are excluded from the count, because they were handed to nobody. Narrowing makes discovery quieter as well as execution safer, and the ranking behind that search is covered in [how tool search applies a relevance floor](/blog/search-tools-ranking-floor-idf/).

## Two minutes, then a verification call

A restriction change takes effect within about two minutes. Role membership behaves the same way. Both resolve through a 60-second cache plus edge propagation, on every surface, so the MCP endpoint, the console and the REST API agree inside that window.

Test a minute after saving and you are reading the old state. The test passes, the configuration looks right, and the workflow changes behavior while you are doing something else. Three changes are effective on the next call instead: grant revocation, member removal and member suspension. The OAuth grant, which is the authorization a client holds after signing in, is re-read from the organization store on every single call, and removing or suspending a member revokes every live grant in the same transaction as the membership change. Everything else needs the wait.

Build the wait into the change:

- Write the rule and confirm the allow list is not empty.
- Wait two minutes by the clock.
- Re-list tools in the client the affected people use. A tool gone from the list is gone from the model's view.
- Call one blocked tool and one allowed tool.
- Read the audit rows for those two calls. They name the account reached, so you confirm you narrowed the right connection and not its twin.

A compliance reviewer can watch all of this without a billable seat. The read-only Auditor role is a free seat, and it lacks `tool:execute`, so an auditor cannot make the test calls by accident.

## Freezing an argument instead of removing a tool

Sometimes the tool is needed and one argument is the problem. Frozen parameters cover that case without cutting the tool. A frozen key is stripped from the advertised schema, so the model never sees it. The frozen value is merged over caller arguments at execution, so passing the key cannot un-freeze it. Precedence runs entry defaults, then caller or model arguments, then frozen parameters.

This changes the shape of a narrowing pass. A tool that only had to be pinned to one workspace, one pipeline or one folder does not belong on a block list. Keeping it available with a frozen argument is usually less disruptive than removing it and then handling the ticket.

## When narrowing is not worth doing yet

If the pilot was five people in one team with two connected apps, the audit-driven pass buys little. Read the rows, keep the current list, and revisit when a second team joins or the first automation ships. Restrictions are cheap to add later. The analysis pays off once the usage matrix is wide enough to contain surprises, and [when you don't need an MCP gateway yet](/blog/when-you-dont-need-an-mcp-gateway/) works through the earlier version of the same judgment.

Two cautions hold at every size. OAuth scopes sit on top of any tool list: `mcp:read`, `mcp:write`, `mcp:destructive` and `mcp:tools`, and a tool classified `forbidden` is reachable under no scope. `mcp:tools` does not replace the ladder, so a connected tool whose method is a delete needs `mcp:destructive` as well. The second caution is the prompt-injection write gate in the agent window. It does not apply to `POST /mcp` and cannot, because an MCP server never sees a user prompt. What does hold on the endpoint is RBAC per operation (role-based access control, with one role per member), the `forbidden` classification, output redaction, scope limits and full audit logging. A narrow tool list belongs in that set. It does not replace it.

For a worked single-team example, read [how a sales team gets governed Salesforce access](/blog/sales-team-chatgpt-salesforce-accounts/). More decisions of this shape sit under [governance](/blog/category/governance/), the tools you are narrowing come from the [connector catalog](/connectors/), and [team use cases](/use-cases/) show which departments usually go first.

## FAQ

### How do I find which MCP tools my team actually used during a pilot?

Read the audit trail. Elaichi writes one entry per tool-call attempt, succeeded or failed, and each record names the tool, the operation, the connection reached, the classification, whether the call was approved, the outcome and an error code. Filter to the pilot window, then group by tool, connection and actor. That gives a usage matrix you can subtract from. Argument names and counts are recorded, but argument values never are, so the rows tell you which tools ran and in which account, not what was in the payload.

### How long does a restriction change take to take effect?

about two minutes. Restrictions and role membership resolve through a 60-second cache plus edge propagation, and that applies on every surface, including the MCP endpoint, the console and the REST API. Only three changes are effective on the next call: grant revocation, member removal and member suspension, because the OAuth grant is re-read from the organization store on every call. Plan any narrowing change with a two-minute wait and then a verification call.

### Does an allow rule that names no tools allow everything?

No. It denies everything. the allowlist stage engages on the presence of an allow rule, not its contents, so an allow rule that names nothing denies everything, so an allow rule naming nothing is the strictest rule you can express. Within the winning layer, allow rules union, block rules union, and blocks always beat allows. Check the contents of an allow rule before saving it, because an empty one takes the target down to no tools at all.

### What happens to a multi-step run when one of its tools is blocked?

The run fails at the blocked step and leaves the earlier steps done. A synthetic tool is a graph of steps with no cycles, and every step goes through the same restriction and audit pipeline as any other call, so steps are checked independently. Completed steps have already changed the third-party system. The audit rows show which step failed and with what error code, so recovery is visible but manual. Land restriction changes when scheduled or token-driven runs are not in flight.

### What is the smallest scope a restriction can target?

No. Restriction targets are role or user only. The organization default is the absence of any rule, and that means allow-all. To narrow broadly, write the rule against the role that the affected members hold, since every member holds exactly one role. Use a user target only for exceptions, and remember that a user-targeted rule replaces role rules entirely rather than layering on top of them.

## Read next

- [SOC 2 evidence for AI agents: CC6 and CC7](/blog/soc2-evidence-ai-agents-cc-controls/) — SOC 2 evidence for AI agents maps onto CC6.1, CC6.2, CC6.3 and CC7.2. Here is the artifact for each, plus the criteria a governed MCP endpoint does not touch.
- [MCP tool name vs pinned operation restriction](/blog/block-matches-name-allow-matches-operation/) — MCP tool name vs pinned operation restriction: a block rule matches a tool's label or its operation, and an allow rule matches only the operation. Here is why.
- [Is Composio secure enough for enterprise use?](/blog/is-composio-secure-enough-for-enterprise-use/) — Is Composio secure enough for enterprise use is two questions: one about controls, which its own pages answer, and one about architecture, which no vendor page answers for you.
