The tool list a security reviewer sees first
When an AI client connects to Elaichi, its tool list holds Elaichi's own elaichi__ operations. Beside them sit search_tools and execute_tool, plus run_code where Code Mode is available. There is no Salesforce tool in that list, and no Jira tool, because connected tools are never listed one by one, however few there are. The first question a reviewer asks is whether that wrapper is a way around the rules. MCP execute_tool indirection security is the answer. execute_tool resolves a string to a tool and decides nothing else, and the call it unwraps to meets the gates a direct call meets.
What does execute_tool do with the name it is handed?
It unwraps it. execute_tool takes name, which is required, and arguments. There is no separate execution path behind it, and it carries no privilege of its own. A connection argument picks the connected account a tool runs against. It belongs inside arguments. One sent at the top level beside name is refused, with an error showing where to move it. Nothing else is guessed.
The name can also be one of Elaichi's own operations. execute_tool will unwrap to an elaichi__ operation, which then has to clear that operation's gates rather than the wrapper's. Nothing about passing through execute_tool changes what the named thing is allowed to do.
Why MCP execute_tool indirection security has to mean no extra privilege
Because the alternative is a bypass with a friendly name. Suppose the wrapper held authority of its own. Every role check, every restriction and every scope decision could then be sidestepped. A caller would route the call through the wrapper instead of naming the tool. A restriction, in Elaichi, is a rule on a role or on one member saying which connectors and tools they may reach. A rule that holds for a direct call and not for a wrapped one is not a rule.
So the wrapper does one job. It turns a name into a resolved tool, then hands the call to the same checks. The honest test of any meta-tool is not what its description promises. It is whether both routes land in the same place.
Which gates one connected-tool call passes, in order
Four gates, in this order. A connected-tool call passes them whether or not it was wrapped.
- The caller's role must hold
tool:execute. A role without it gets an empty tool list. The in-band refusal says re-authorizing cannot grant it, because it is a role and not a scope. - The name must resolve on this grant's surface. A grant is the OAuth authorization the person approved for that client. Without the
mcp:toolsscope the answer is a scope refusal. A tool a restriction withholds gets a restriction refusal. Anything else gets "Unknown tool". - The gate re-derives the tool's tier, read, write or delete, at call time. It charges no further scope for that tier. A custom tool edited from a GET to a DELETE is dispatched as a delete. If the tool no longer resolves, the call is refused rather than run unclassified.
- Execution re-resolves the toolbox and requires use access to it. It then applies the caller's own restrictions, resolves the
connectionargument, and writes an audit row.
One detail matters for evidence. A call refused at the scope check writes no audit row, because the refusal happens before any executor runs. Rows exist for calls that reached execution and then ran, failed or were refused there.
What the gates read at call time, not at list time
Every authorization is taken again when the call arrives. A grant's connected-tool surface is what search_tools ranks and execute_tool resolves. It is kept in worker memory for up to 60 seconds. That cache only affects what is advertised. Each tool call re-reads toolbox access, the caller's restrictions and the scope tier from a fresh resolution. A stored toolbox is re-resolved entry by entry. That includes whether the person who pinned each connection still holds access to it.
The timing differs by change. Revoking a grant fails the client's very next request. Revoking a share or disconnecting an account takes effect on the next call. Role, team and restriction changes reach every surface within about two minutes.
What happens when the name is one of Elaichi's own operations
It meets that operation's own gates, which are stricter than a connected tool's. The call passes tool:execute, then the app-only check, then the step-up check. Next comes the requirement that the name exists, is not forbidden and is offered to this client. After that comes the organization's assistant-operations setting. The scope ladder runs last: a read needs mcp:read, a write needs mcp:write, a delete needs mcp:destructive. The executor then re-checks the role's permission, the plan feature and the operation's own access rules.
Some names cannot be reached at all. Six operations are listed when role and scope allow, then refused on every MCP call. They are creating and deleting invitations, changing a member's roles, deleting a member, deleting a role and deleting a team. The Elaichi app asks the person to confirm it is them first. The refusal names the console screen to use. Three operations classified forbidden, covering credential creation or rotation, security changes and authentication flows, never run on any surface. Wrapping a name does not conjure a path that does not exist.
Can a client skip the wrapper and call the tool name directly?
Yes, and that is the point. The server accepts a connected tool's exact name in tools/call, even though it is never listed. The call meets the same gates it would meet through execute_tool. Over MCP a connected tool can also be run through run_code, where Code Mode is available, and through the elaichi__toolbox__execute and elaichi__synthetic_tool__execute operations. Each of those routes ends at the same restriction check, evaluated for the person the call acts as.
A name that resolves to nothing returns an in-band tool error rather than a protocol error. When the caller has connected tools, it suggests the three closest names, ranked as tool search ranks them. A name with one typo still finds its target. The error also says the gap may be a role, a scope or a restriction rather than a spelling mistake. The mechanics of that ranking are covered in how tool search picks one tool from hundreds.
What the indirection costs
One tool name carries every connected-tool call made through execute_tool, whatever the app. A client control that allows or gates individual MCP tool names therefore sees them as one tool. It cannot tell a Salesforce read from a Gusto write. A permission set on execute_tool also misses calls made through run_code or elaichi__toolbox__execute. Per-app and per-tool control has to live in Elaichi, as restrictions on a role or on one member. The case for each shape is in restrict one AI tool or the whole app.
The annotations compound this. Elaichi marks execute_tool destructive, open-world and not idempotent. The real tier of whatever it is handed is not knowable before the call. Elaichi treats annotations as untrusted hints for clients, not as gates.
Client-side approval settings read those hints. Codex sets approval behavior per server with default_tools_approval_mode. Its writes setting prompts for any tool not marked read-only (OpenAI's MCP docs, checked October 2026). Cursor asks for approval before using MCP tools by default (Cursor's MCP docs, checked October 2026). Claude's connector tool permissions take one of three settings: Always allow, Needs approval or Blocked. They apply to a whole connector, a category of its tools or a single tool (Anthropic's connectors article, checked October 2026). A per-tool setting of that kind can only reach the tools Elaichi lists, so on Elaichi it lands on execute_tool rather than on one app. Because execute_tool is never marked read-only, a client that prompts for anything but reads asks before every connected-app call, reads included.
Scopes do not narrow this either. mcp:tools alone runs every tier of connected tool, deletes included. Leaving "Create and change data" unticked does not make a connected app read-only. To hold a client to reads, pick toolboxes of read tools at consent. Or block the app's write and delete tools with a restriction.
When you do not need any of this
If one team uses one app's own MCP server, and that server lists its tools individually, the indirection question never arises. The client shows a dozen named tools. Per-tool approval there means what it looks like it means, and nothing sits between the client and the vendor. Adding a layer there buys governance you may not need yet, and the honest case for waiting is worth reading before you shop.
The wrapper earns its place when one address fronts many apps for many people. At that point a tool list long enough to be useful stops fitting in a model's context. Naming becomes indirection whether you choose it or not. The question left is whether the indirect path is checked as tightly as the direct one. That is what an MCP control plane is for. It is also the thing to verify in any product that offers one. To see what sits behind the wrapper on Elaichi's side, browse the connector catalog.