# MCP audit log

Record who called which tool, when and with what outcome - one event per agent request.

When an AI agent calls an [MCP gateway](https://zato.io/docs/ai/mcp/index.html), the audit log records what happened - who called which tool, when, with what outcome and how much data flowed. Payloads are never recorded, only their sizes are.

MCP is one source of the platform-wide [audit log](https://zato.io/docs/admin/audit-log/index.html) - storage, external databases and the dashboard views are shared with REST, IMAP and publish/subscribe, and are described on that page.

## Event types {#event-types}

Each HTTP request an agent makes produces exactly one event, after the request is handled - both POST requests delivering JSON-RPC messages and the DELETE that terminates a session.

| Event type | When |
| --- | --- |
| `mcp-initialize` | An `initialize` request started a conversation |
| `mcp-tools-list` | A `tools/list` request listed the tools |
| `mcp-tools-call` | A `tools/call` request invoked a tool |
| `mcp-prompts-list` | A `prompts/list` request listed the gateway's [prompts](https://zato.io/docs/ai/mcp/prompts.html) |
| `mcp-prompts-get` | A `prompts/get` request read one prompt's instructions |
| `mcp-discover` | A `server/discover` probe of the stateless [protocol revision](https://zato.io/docs/ai/mcp/index.html#endpoint-behavior) |
| `mcp-session-delete` | An HTTP DELETE terminated a session |
| `auth-failed` | A request was refused because its credentials did not authenticate |
| `rate-limited` | A request was refused with a 429 because the [rate limit](https://zato.io/docs/ai/mcp/rate-limits.html) of its security definition was reached |

A method outside this set, e.g. `ping`, is recorded under its literal name, and a request whose body could not be parsed is recorded as `unknown`.

## The columns {#the-columns}

The events live in the same `event` table as every other source. This is how MCP fills it:

| Column | Content |
| --- | --- |
| `source` | Always `mcp` |
| `event_type` | One of the types above |
| `object_name` | The gateway's name |
| `cid` | The correlation ID of the request, the same one that appears in server logs |
| `endpoint` | The tool or prompt name - only `mcp-tools-call` and `mcp-prompts-get` events have one, it is empty otherwise |
| `ext_client_id` | The caller - the name of the [security definition](https://zato.io/docs/ai/mcp/security.html) the agent authenticated with, or, for a bearer definition with an identity claim, that name, a slash and the person's claim value, e.g. `mcp.billing.agents/maria.johnson@example.com` |
| `sub_key` | The session ID the request included or created, empty when there was none |
| `size` | The size of the response, in bytes |
| `outcome` | `ok`, or `error` when the HTTP status was not 2xx, the JSON-RPC response contained an error object, or a tool result reported `isError` |
| `data` | A JSON document with the fields below - never the payload itself |

The `data` document holds `remote_address`, `method` (the literal JSON-RPC method), `duration_ms` (how long handling took) and `request_size` in bytes. Events with an `error` outcome additionally record the JSON-RPC `error_code` and `error_message` the agent received. When [response controls](https://zato.io/docs/ai/mcp/response-controls.html) shaped a `tools/call` response, the document also lists the trace of what shaping did - PII counts, compaction counts, token cuts and the agent filter - one key per finding, with nothing written for stages that did nothing.

In the dashboard, the MCP audit page shows these as Time, CID, Event, Tool, Caller, Outcome, Size and a data preview, with the Identity, Client and Reason columns described below.

## The caller and the auth block {#the-caller-and-the-auth-block}

When the caller presented a bearer token, the `data` document also includes an `auth` block with what the token said, the same fixed keys in every event and never the token itself:

| Key | Content |
| --- | --- |
| `type` | `bearer_jwt` for a JWT, `bearer_static` for a static token, `none` when no bearer token was presented |
| `definition` | The name of the bearer token definition that matched, or was expected to |
| `identity` | The value of the definition's identity claim, e.g. `maria.johnson@example.com`, empty without one |
| `issuer`, `audience` | The token's `iss` and `aud` claims |
| `client` | The OAuth client the person signed in through, from the token's `azp` or `client_id` claim - which AI product it was |
| `scopes` | The scopes the token was granted |
| `token_id`, `expires_at` | The token's `jti` and `exp` claims |
| `claims_matched` | The `name=value` entries of the definition the token satisfied |
| `reason` | On `auth-failed` events, why the token was refused - one of `no_credentials`, `malformed`, `unsupported_algorithm`, `unknown_key`, `expired`, `wrong_issuer`, `wrong_audience`, `bad_signature`, `claim_missing`, `claim_mismatch` and `no_definition_matched` |
| `claim` | On `auth-failed` events with a `claim_missing` or `claim_mismatch` reason, the name of the claim |

`identity`, `client` and `reason` are also attributes of the event, so the MCP audit page shows them as the Identity, Client and Reason columns and the free-text search finds events by a person's name, by the client they used and by the reason they were refused. A refused token that could be read, an expired one or one from a person outside the group the definition lists, leaves the person and the client in the record, a token that could not be read leaves neither.

When the audit log is [exported with OpenTelemetry](https://zato.io/docs/admin/audit-log/opentelemetry.html), each MCP event is one log record and nothing of the above is lost. The columns become `zato.audit.object_name`, `zato.audit.endpoint`, `zato.audit.ext_client_id`, `zato.audit.sub_key`, `zato.audit.size` and `zato.audit.outcome`, the three attributes become `zato.audit.attr.identity`, `zato.audit.attr.client` and `zato.audit.attr.reason`, and the `data` document is flattened with a dot, so `method` is `zato.audit.data.method`, `duration_ms` is `zato.audit.data.duration_ms` and every key of the auth block is `zato.audit.data.auth.<key>`, e.g. `zato.audit.data.auth.identity` and `zato.audit.data.auth.scopes`. Keys that are empty in the event, such as `identity` for a token that could not be read, are left out of the record rather than sent as empty strings.

## Enable and disable the log {#enable-and-disable-the-log}

Each gateway has its own **Record each request in the audit log** checkbox, in the **Gateway options** micro-form under **More options** on the wizard's step 02, **What do they receive?** - on by default for new gateways, and taking effect immediately when saved. The gateway list has an **Audit log** link in each row that opens that gateway's events.

In [enmasse](https://zato.io/docs/admin/enmasse-reference.html), the toggle is the `is_audit_log_active` attribute of an `mcp_gateway` entry.

## Retention and storage {#retention-and-storage}

Events are kept for 30 days and deleted automatically past that window. Storage is the shared audit database - SQLite by default, or MySQL, PostgreSQL and Oracle DB - configured through the environment variables listed on the [audit log](https://zato.io/docs/admin/audit-log/index.html) page.

> **Note:** Payloads are never recorded, so the audit log does not conflict with the obligations described on the [PII and compliance](https://zato.io/docs/admin/security/pii.html) page.

## See also {#see-also}

- [Platform audit log](https://zato.io/docs/admin/audit-log/index.html) - Storage, external databases and the shared dashboard views
- [Security](https://zato.io/docs/ai/mcp/security.html) - The identities the caller column records
- [Response controls](https://zato.io/docs/ai/mcp/response-controls.html) - The shaping whose trace appears in each event's data
- [Alerts](https://zato.io/docs/ai/mcp/alerts.html) - The alerts counted from these events - failing calls, invalid calls, looping agents, response volume

## Learn more {#learn-more}

- [MCP tutorial](https://zato.io/tutorials/mcp/01.html) - Expose Python services as AI tools and connect Claude Code in minutes
- [AI Integrations overview](https://zato.io/docs/ai/) - Both directions of AI traffic in one platform, and where to start
- [MCP gateway reference](https://zato.io/docs/ai/mcp/) - Configuration, endpoint behavior and the governance controls
- [Tool schemas](https://zato.io/docs/ai/mcp/tool-schemas.html) - How a service's docstring and declared I/O become its tool definition
- [MCP gateway security](https://zato.io/docs/ai/mcp/security.html) - API keys, Basic Auth and bearer tokens for AI agents
- [MCP response controls](https://zato.io/docs/ai/mcp/response-controls.html) - PII removal, prompt-injection safeguards and token-denominated size caps
- [MCP alerts](https://zato.io/docs/ai/mcp/alerts.html) - Failing backends, agents stuck in a loop and gateways with too many tools
- [Cost and limits](https://zato.io/docs/ai/cost-and-limits.html) - Every setting that caps AI cost and traffic, with worked examples
- [GitOps for AI integrations](https://zato.io/docs/ai/enmasse.html) - LLM connections and MCP gateways as YAML in version control
- [AI examples](https://zato.io/docs/ai/examples/) - Complete, runnable scenarios - one real-world need per file
