Why can nobody say which tools AI agents call?
Ask most IT teams which tools AI agents call inside their company apps and the honest answer is a guess. The network layer saw a TLS session to a vendor host. The SaaS app saw an API call from an OAuth app. Neither saw the person, the assistant, or the tool. Elaichi serves one MCP address every client signs in to, and writes one audit row for each tool call.
That gap is structural, not a logging oversight. Somebody adds a vendor's MCP server straight into Claude or Cursor. MCP (Model Context Protocol) is the standard way an AI assistant calls tools in other apps. The specification lives at modelcontextprotocol.io. The call then goes from the client to the vendor directly. Nothing of yours sits in the path, so nothing of yours can log it.
Here is what the two most common clients actually record today, and where each one stops:
| Client | What it logs natively | Where it stops |
|---|---|---|
| Cursor (Enterprise) | mcp_server_config on server setup, mcp_authentication on member sign-in (checked October 2026, cursor.com) |
Undocumented whether edits to a member's local mcp.json produce any event |
| Claude Code | claude_code.mcp_server_connection via OpenTelemetry export (checked October 2026, code.claude.com) |
Server name and tool name are hidden (reported as generic mcp_tool) unless OTEL_LOG_TOOL_DETAILS=1 is set |
| ChatGPT | On Enterprise and Edu, Compliance Platform app-activity records can include an app request and related response information, and app-authentication records can show account connections and disconnections (checked October 2026, help.openai.com) | This is ChatGPT's own record of its own apps, held separately from any log your gateway keeps |
These are connection records, not action records. They tell you a server exists. They do not tell you that a sales rep's assistant updated 40 HubSpot contacts on Tuesday. Engineers use Cursor, marketing uses ChatGPT, support uses Claude. Each one leaves its evidence somewhere else, in a different shape, with different gaps. Left alone, that fragmentation is shadow AI with better branding.
The rest of this post describes one way to close that gap: the audit log in Elaichi's MCP gateway. It is explicit about which claims are checkable against public vendor docs, such as Cursor and Claude Code, and which are implementation details you would verify against Elaichi's own documentation.
How do you get one trail across Claude, ChatGPT and Cursor?
Route every client through one endpoint that can see every call, rather than letting each client talk to each vendor's MCP server directly. That is the general fix for the fragmentation problem above. The endpoint is also where the rest of MCP governance lands, and this is how Elaichi implements the pattern.
Elaichi serves MCP at one endpoint, https://api.elaichi.ai/mcp, the same string for every organization. Claude, ChatGPT, Cursor, Microsoft Copilot and any other MCP client sign in there with OAuth, and each person approves their own grant.
Every call that reaches execution and then runs, fails or is refused there writes one audit row. In Elaichi, one entry for each connected-tool call that reaches execution (a call refused earlier writes none). The row also records the toolbox the call went through and the tool's advertised and catalog names. It records whether approval was required and how it was given, the surface the call came from, the MCP client's id when the surface is mcp, and an error code on failure.
The connection field is the one people underrate. It names the account the execution actually reached, not the one the arguments asked for. A call that could not be matched to one account records no connection at all. That is the difference between knowing somebody wrote to Notion and knowing which Notion workspace received the write.
The surface field separates Elaichi's own Agent from a remote client. A call from Claude, ChatGPT or Cursor is recorded with actor kind user and surface mcp, plus the OAuth client's id. The console resolves the client's name when the log is read. An action taken through Cursor and the same action taken through Claude land in the same shape, with the same fields, in the same place.
Whose identity is on the row?
The person who approved the grant. Every call made with a grant's tokens acts as them, in the organization they picked.
This matters because of the alternative. A shared service account or API key is how most shadow-AI incidents become unattributable after the fact.
Each token carries that user and organization, and the person's permissions are looked up on each request rather than stored in the token. Each member's grant is their own, so accountability is not a convention somebody has to maintain. It is how the call runs.
Actions Elaichi staff take while signed in as a member are recorded with actor kind staff and the staff user's id in the row's metadata. The console shows such a row as "Elaichi support team", naming neither the staff member nor the customer whose identity the session wore. A support visit does not look like the member.
What questions can an admin answer from the audit log?
Four, directly from the stored fields: which apps are in use, whether calls read or changed data, who ran them through which client, and what a departed member did.
Read the log at Governance, under the Audit log tab. It returns entries newest first, 50 a page by default and at most 200, with filters for free text, category, actor, action kind and a start time.
Which apps are actually in use. Each row names the connector and the connection. Filter the Connections category or search a connector name, and the volume per app falls out. This is the inventory question, answered from calls that ran rather than from a list of configured servers.
Reads versus changes. The tool's classification is stored at write time. The console's detail panel shows a "What it did" line reading Read data, Changed data or Deleted data. A quarter of calls being writes is a different conversation from all of them being reads.
Who, and through which client. Each row carries the acting person and, for an MCP call, the client's id. The console marks a client's name verified only when every non-loopback redirect address it registered maps to the same known client. Otherwise it shows the name the client registered for itself, flagged "Name provided by the client itself." Exactly seven clients can be verified: Claude, ChatGPT, Cursor, Grok, Perplexity, Mistral and Devin. A client that registers only loopback addresses, such as Claude Code or Codex CLI, is never verified. That is a property of how OAuth dynamic client registration works, not a gap specific to this gateway.
What a departed member did. Actor names resolve on the server. Someone who has left shows as "Former member" rather than disappearing from the trail.
Every member can read the log at their own scope: their own events, plus events on connections they own or can edit. The organization-wide view needs the audit permission, which Org Owner, Org Admin and the free Auditor role hold.
One caveat on filtering. Connected-tool calls sit in the assistant category, because their action name is assistant.toolbox_tool. The mcp category holds OAuth grant events, not tool calls. Filtering to MCP to find MCP tool calls returns the wrong set.
What does the audit log not capture?
The arguments, the attempts that never reached execution, and discovery. The log is deliberately not a second copy of the data the agent touched, and knowing what is missing matters as much as knowing what is there.
A connected-tool row records no argument values, with one exception. The value of the one URL path argument that names the object is kept as the target id, at most 200 characters. It records neither argument names nor an argument count. A title taken from the response is kept for some objects. It is never kept for a person's record (contact, lead, employee, candidate) or a message (email, thread, comment, calendar event), and an email address is never kept as an id or a title. For a list or search call, the row records only the resource type and how many items came back, nothing about the items. You can see that somebody ran a Jira search; you cannot read the query.
Three more boundaries:
- A call refused before execution writes no row. A tool a restriction withholds, a missing OAuth scope, an unknown tool name or a rate limit all stop the call ahead of the executor. The log is a record of calls that reached execution, not a complete record of attempts.
tools/listandsearch_toolswrite no audit row. Discovery is not logged.- A connected-tool row carries no duration. Duration is recorded on assistant-operation rows, synthetic tool runs and Code Mode runs, not on
assistant.toolbox_tool.
Elaichi keeps 90 days of audit history. That is a vendor-set retention window, not a protocol standard, so confirm the current figure against Elaichi's own documentation before relying on it. For longer, forward it to your own log service. The short version is that export to your own Datadog comes with the Black plan, and Datadog is the destination that delivers today. Splunk HEC and Microsoft Sentinel are labeled coming soon in the console and currently answer HTTP 501 without retrying. A forwarded event carries the event id, the acting user's id, the action, the resource type and id, the full metadata, the time and the log type.
Does a vendor-run MCP server get logged the same way?
Yes, as long as the call goes through the gateway rather than around it. The catalog holds two kinds of connector, and the audit row is the same shape for both.
Most of the catalog is connectors Elaichi authors and runs itself. The rest are native MCP connectors, where the vendor builds and runs its own MCP server and Elaichi handles sign-in, access and audit in front of it. Datadog, Linear, GitLab, PostHog and Miro are among them. For a native MCP connector, restrictions and OAuth scopes are checked before the call leaves Elaichi, and only a tool on that connection's own active list can run. Every call that reaches execution is written to the audit log with the person, the connector, the connection and the MCP client.
A team can also bring its own remote MCP server in as a connector, under the same rules. An admin reading the log does not parse three log formats. A read on a team's own remote MCP server carries the same fields as a read on a catalog Salesforce connector.
The boundary is sharp. Calls a client makes to a vendor's MCP server directly, outside this gateway, do not pass through it and are not governed or logged by it. GitHub is an example. A team that needs it brings GitHub's own remote MCP server in as a remote MCP connector, and only calls that go through that connector are logged. Calls made by pointing a client straight at GitHub's server are invisible to this system, same as the Cursor and Claude Code gaps described above.
How do you turn what the log shows into restrictions?
Write a block rule on the role of the people whose calls you saw. The log tells you what happened; a restriction decides what happens next. In Elaichi, restriction targets are role or user only; the organization default is the absence of a rule, which means allow everything. To hold a team off a tool, write the rule on their role.
A worked sequence from a real finding. The log shows a support member's Claude calling a Stripe tool that creates a coupon. In Governance, New restriction, Applies to Role, Member, Block list, add Stripe, choose Specific tools and tick only that tool. The summary reads "Blocks 1 tool in Stripe." The member's Claude is then refused, with a card offering to ask an admin for access.
Three mechanics decide whether the rule does what you meant. This is the part most governance tools get wrong, so treat it as a checklist before trusting a rule you wrote:
| Mechanic | What it means in practice |
|---|---|
| Block-only layer | With no allow rules present, nothing is denied by default, so everything a block rule doesn't name still passes. Blocking some of an app's write tools leaves every unnamed write tool reachable. |
| Allow rule side effect | Adding any allow rule to a role turns on an allowlist for that entire role, across all connectors. Every connector and tool none of the role's allow rules names becomes blocked, including apps the rule never mentioned. A role held to one app's read tools now also needs explicit allow rules for every other app it uses. |
| Match basis | An allow rule matches a tool only by the operation pinned at save time, never by name. A block rule matches by name or by operation. So renaming a tool gets past neither rule: the block still catches it on the operation, and the allow rule still admits it on the operation. |
The full picture is in how restriction enforcement works end to end, and the three layers are separated in roles, restrictions and ACLs.
A member is governed by two layers at once: the rules on their role and the rules aimed at them personally. A connector or tool is reachable only when both layers admit it, so a personal rule can narrow what the role allows and never loosens it. Tracing the resolver's precedence through one call shows where each layer is applied.
Role, team-membership and restriction changes take effect about two minutes. In Elaichi, removing or suspending a member revokes every live grant in the same transaction as the membership change.
What happens when a blocked call is refused?
The caller is told whether the block comes from their role or from their own account, and that an admin can lift it through an access request. The refusal does not name the rule, who wrote it, or what else it covers. From a client, a grant with the read or tools scope can file the person's own request; nobody can approve their own.
Only a person in the console can approve or deny a request, under Governance, Access requests. The route needs the member-management permission, refuses organization API tokens and asks the admin to confirm their identity first. No AI surface can decide a request, denials included.
Approving a restriction request for a connector, or for a tool that names its connector, creates an access grant for that one requester. The approved request becomes the grant record. No restriction rule is written, and the role's rules are never edited. The grant lifts exactly the approved connector or tool out of the requester's role rules. Every other role rule keeps applying, including ones added later, and nobody else's access changes. A grant lasts until the member is removed or a new approval supersedes it. That is the loop: read the log, write the rule, let the people it blocks ask, decide in the console.
When this is not the tool you want
If the question is latency, token spend or model drift on a model your team runs, an MCP control plane is the wrong instrument. The audit log records tool calls, not inference. A model observability pipeline is what answers that.
If nobody has connected an AI client to a company app yet, there is nothing to read. The first useful step is finding out which MCP servers employees have installed locally. That is a config-file exercise on each device rather than a console query. None of the client admin pages read in October 2026 documents an inventory of local servers.
If you want the field-by-field argument for what a trail has to hold before it is worth anything, start with what an AI agent audit log must capture. To see which apps are available to put behind this in the first place, browse the 650+ connectors in the catalog.