Skip to content

Where MCP restriction enforcement points actually sit

The mcp restriction enforcement points in Elaichi run at eight places, not four, and a ninth check inside the AI client would prove nothing.

Nachi Raman 7 min read
A tool call passing through successive checks between an AI client and a connected app

How many MCP restriction enforcement points are there?

More than four. An admin writes one rule and wants to know where it bites. Elaichi checks a restriction at every place a member can reach a connector or tool, including listing, connecting, advertising, executing, the final outbound-request check and opening a stored tool file.

The mcp restriction enforcement points in the deployed product are eight. Listing connectors and their tools, in the console, the REST API and the AI catalog operations. Connecting or reconnecting an account. Forking a connector. Saving a toolbox entry or a synthetic tool step. Advertising tools to a client through tools/list, search_tools and toolbox views. Executing any tool call. The final outbound-request guard. And opening a stored tool file that came out of a now-restricted tool.

A restriction is a rule naming which connectors and tools a role or one member may reach. In Elaichi, restriction targets are role or user only; the organization default is the absence of a rule, which means allow everything. To keep a team off a tool, the rule goes on their role or on each member.

What the resolver decides, and whose access it reads

Every one of those checks asks the resolver the same question. For this person, is this connector or this tool reachable? A member is governed by two layers at once: the rules on their role, and the rules aimed at them personally. Both layers have to admit the tool, and a personal rule can only narrow what the role allows. Inside each layer, all allow rules merge into one allowlist and all block rules into one blocklist. A block always beats an allow. How restrictions sit beside roles and sharing grants is laid out in three separate access layers.

An empty allow rule is the trap. An allow rule turns on the allowlist for its layer just by existing, whatever it names. So an allow rule naming nothing blocks every connector and tool for its target. Its summary says so: "Allows nothing. Every connector and tool is blocked."

The resolver reads the person the call acts as. Not the owner of the connection, and not the toolbox entry's delegator. A tool a restriction blocks for the caller is blocked inside every toolbox entry for that caller, frozen arguments or not. Allow rules match only the operation Elaichi pinned when the rule was saved. Block rules match by name or by operation. The reasoning behind that asymmetry is in why blocks match tool names and allows do not.

Browse and connect: the checks that run before any tool call

These two run before a connection exists. They are the reason a rule can stop something from ever being set up. When an AI client or the console lists a connector's tools, restricted tools are left out. The count of what was left out comes back as restricted_count, and a connector blocked whole is refused rather than listed as empty. In the console's own connector and connection tool lists, every tool comes back with restricted, restricted_by and restricted_scope flags for anyone who can read the connector. So confirming that one tool is restricted for you needs no permission to view restrictions.

Connecting or reconnecting an account is itself checked. That matters more than it sounds. Creating a personal connection is a default Member permission. Absent any restriction, a Member can connect any connector in the catalog with their own account. The connect-time check is what makes an approved-apps list stick when somebody tries to sign in to app number nine.

Authoring checks: forking, toolbox entries and synthetic steps

Three more checks run while somebody is building, before anything is callable. Forking is refused for a connector, or a fork of one, that a restriction blocks for the caller. Saving a toolbox entry or a synthetic tool step is refused when it names a tool restricted for the person saving it. The message reads "Tool () is restricted for your account."

These checks are convenience, not the boundary. An entry saved before the restriction existed is not refused again on later saves. It is withheld when the toolbox resolves, and refused when it is called. That is the point: execution is the layer that has to hold.

Fork lineage runs one way on purpose. A block on a connector also blocks every fork that declares descent from it, walking the lineage toward its root for up to 8 hops. A chain that is longer, loops or is broken is refused outright rather than checked partially. An allow rule does not inherit, so a fork has to be named on the allowlist itself. And a connector's identity is its slug plus its declared lineage, never the host it calls. A connector built from scratch against the same API as a blocked one is not caught by that block. That is why connector:create is the one permission Elaichi flags "High trust".

What an AI client sees when a tool is restricted

A restricted tool is withheld from tools/list and from a toolbox's runnable tools. The check runs for the person using the toolbox, against the catalog tool's own name and operation. An entry's rename, schema override or frozen values change nothing about the outcome.

It is not hidden, though. search_tools ranks the caller's restricted tools separately against the same query. It returns those matches by name, flagged restricted, with no description, no schema and no way to call them, plus a restricted_count. A connector blocked whole shows as one row for the connector. Calling a restricted tool's name gets a restriction refusal rather than "Unknown tool". The refusal tells the caller whether the block comes from their role or from their own account, and that an admin can lift it through an access request. It does not name the rule, who wrote it, or what else it covers.

The advertised surface is cached in worker memory for up to 60 seconds per grant. A stale entry can only mis-advertise. Every authorization is taken again at call time.

The execute clamp: one check for every surface

Every connected-tool call Elaichi runs passes one restriction clamp, evaluated for the member the call acts as. MCP tools/call and execute_tool go through it. So does each tool call inside a run_code program, elaichi__toolbox__execute, the in-app Agent, and every step of a synthetic tool. Restrictions therefore bind whatever client or automation sent the call. They narrow what a member can do through a connection, whatever the credential behind that connection could do upstream.

Two details make the clamp trustworthy. Each tool call through a stored toolbox re-resolves the toolbox at call time from the organization's store, including every entry's delegation standing. The advertised list is never used to authorize a call. And when the resolver gets an answer in a shape it does not recognize, it refuses the call rather than allowing it. That happens, for example, with an organization store still running older code during a deploy. It does not cache that answer either.

The outbound-request guard: the last check before the wire

As a last check before a call leaves, Elaichi resolves the outbound URL in full. It compares that URL, and every redirect hop, with the HTTP operations named by tool-level block rules on the called connector and its declared ancestors, and refuses a match. It never refuses a request merely for reaching a host. A whole-connector block is enforced earlier, before the request is built at all.

Within a declared fork chain, the guard compares the HTTP method, path and host the called tool really sends with those of each blocked upstream tool. Renaming a tool does not escape a block written against the original. Nor does editing the resource and method keys inside a fork's config. This is the check that makes a tool-level block about what a call does rather than what it is labeled.

Why a fifth check at the client would be theater

Because Elaichi's MCP tool list names no connected app. In that list, connected tools are never listed one by one, however few there are; a client finds them with search_tools. So a client control that picks or gates individual MCP tools can act only on execute_tool, run_code, elaichi__toolbox__execute and elaichi__synthetic_tool__execute, each as a whole. It cannot tell a Salesforce call from a payroll call. And a permission set on execute_tool does not cover the other three paths.

Tool annotations will not rescue it either. Elaichi treats annotations as untrusted hints for clients and keeps its own check as the gate. execute_tool and run_code are annotated destructive and open-world. The real tier of whatever they are handed is not knowable before the call. So a client setting that runs read-only tools without prompting still prompts on every connected-app call, reads included. It does not make connected apps read-only.

The client vendors say the same thing about their own controls. Anthropic warns that a serverName entry in Claude Code's allow and deny lists is not a security control, because the name is a label the user assigns (Claude Code managed MCP docs, checked October 2026). It also describes server-managed settings as a client-side control rather than a security boundary, which a user on an unmanaged device can bypass without admin access (Claude Code server-managed settings, checked October 2026). A check a user can edit is not an enforcement point.

What the client should carry instead

Per-call confirmation. Over MCP, the person's OAuth grant is the approval. Elaichi runs every call the grant's scopes cover as already approved, and sends no per-call prompt of its own. A prompt a person sees is their client's. Claude's connector tool permissions offer Always allow, Needs approval or Blocked, per connector, category or tool (Anthropic connectors help, checked October 2026). Cursor asks for approval before using MCP tools by default (Cursor MCP docs, checked October 2026). The layers are compared in human approval for AI agent actions.

Prompt-injection defense belongs there too. Nothing on Elaichi's /mcp path scans tool results for injection. The gate could not run there as built: the endpoint reads only a tool name and arguments from a tools/call. It never has the person's prompt to judge the call against.

And the honest case for skipping all of this. A company running one client against one app can get what it needs from that client's own connector-level policy. Restrictions earn their keep when the same tool is reachable from several clients, surfaces and scheduled jobs at once.

When a restriction change actually bites

Role, team-membership, restriction and access-grant changes reach every surface within about two minutes. A member's resolved restrictions are cached briefly behind a per-organization epoch. Revoking a share or disconnecting an account takes effect on the caller's next call. So does revoking a grant, removing a member or suspending one.

Restriction writes are recorded as restriction.created, restriction.updated and restriction.deleted. Approving an access request records an access grant for that one person rather than editing the role's rules. That new access arrives on the same schedule as a restriction edit.

For how rules map to roles across a whole company, start from MCP governance. For whether to write a rule against one tool or the whole app, read restrict one AI tool or the whole app. The governance use case walks the same model from the admin's side at /use-cases/governance/.

FAQ

Frequently asked questions

Where does Elaichi check a restriction on a tool?

Elaichi checks restrictions at every place a member can reach a connector or a tool. That covers listing connectors and their tools in the console, the REST API and the AI catalog operations. It also covers connecting or reconnecting an account, forking a connector, and saving a toolbox entry or a synthetic tool step. Then come advertising tools to a client through tools/list, search_tools and toolbox views, and executing any tool call. Last are the final outbound-request guard and opening a stored tool file that came out of a now-restricted tool. Saying there are four enforcement points is an undercount.

Does a restricted tool disappear from an AI client's tool list?

It is withheld from tools/list and from a toolbox's runnable tools, but it is not invisible. Elaichi's search_tools still returns a restricted match by name, flagged restricted, with no description, no schema and no way to call it. The result also carries a count of restricted matches, and a connector blocked whole shows as a single row. Calling a restricted tool's exact name gets a restriction refusal, not an "Unknown tool" error. The refusal says whether the block came from the person's role or their own account.

How long does a restriction change take to apply in Elaichi?

About two minutes. Role, team-membership, restriction and access-grant changes are cached per member behind a per-organization version marker. A change reaches the MCP endpoint, the console and the REST API once each location sees the new marker. Some other changes are faster. Revoking a share, disconnecting an account, revoking a grant, and removing or suspending a member all take effect on the next call.

Can a restriction block another connector that calls the same API?

No. For restrictions, a connector's identity is its own slug plus its declared fork lineage. The host it calls is deliberately not part of that identity. A block on a connector also blocks every fork that declares descent from it, walking the lineage up to 8 hops. A connector built from scratch against the same API is not caught. This is why Elaichi flags the permission to create connectors as high trust.

Can an MCP client's per-tool permission replace a restriction on the server?

No. Elaichi never lists connected tools by name in a client's tool list. So a client control that gates individual MCP tools can only act on execute_tool, run_code and the two Elaichi operations that run connected tools, each as a whole. It cannot single out one connected app or one tool, and a permission on execute_tool does not cover the other three paths. Per-app and per-tool control belongs in Elaichi as restrictions. The client's own per-call approval prompt is a separate, useful layer.

Put agents to work on your own systems

14 days on Gold, no credit card. Start with one app and one team.

Works with
Claude ChatGPT Cursor and any other MCP client, or the Elaichi Agent.
When the trial ends
Nothing is deleted. Connections, roles and the audit log stay where they are, so subscribing picks up exactly where you left off.