Guide
How to Get Audit Logs of Every MCP Tool Call Into Your SIEM
Agents write logs on laptops. MCP servers see tokens, and not people. Only a gateway can make one record for each tool call with the person behind it. This guide shows what a record must contain. It also shows how to stream the records to Splunk, Falcon LogScale, Elastic or Sentinel, and what the delivery guarantees are.
· Erik Jonsson Thorén, Founder, Gatana
Short answer: Route the agents through a gateway that records every tool call with the person, the agent, the server, the tool and the outcome. Stream the records to the SIEM over HTTPS. In Gatana, set the audit level, then configure one destination under Settings, SIEM Streaming. Events arrive as HMAC-signed NDJSON batches within about one minute.
Why agents and servers cannot give you this
Agents write their logs on laptops, one person at a time, each in its own format. An upstream MCP server sees a token, or a shared API key. It does not see the person. Ten servers give ten log formats. None of them names the agent.
One point sees every call with the person, the agent, the server and the tool: the gateway. The audit trail must come from there.
What one record must contain
A record that a security team can use names these fields:
- the event, for example a tool call, a sign-in or a configuration change
- the time, in UTC
- the person, and the agent or token that acted for the person
- the server and the tool
- the outcome, including calls that a rule blocked
- the arguments, or a redacted form of them, when the policy requires it
Gatana sends each event as one JSON object on its own line:
{
"id": "audit_logs:184223",
"version": 1,
"time": "2026-08-04T09:52:04.118Z",
"tenant": "acme",
"source": "audit_logs",
"action": "mcp.tools/call",
"actor": { "type": "user", "id": "usr_8Fq2..." },
"entity": { "type": "mcp_server", "id": "srvr_pQ1..." },
"details": {}
}
| Field | Content |
|---|---|
id |
Stable and unique for the same event. Use it to deduplicate |
time |
When the event happened, ISO 8601 UTC |
source |
audit_logs or audit_logs_credentials |
action |
A dotted name: mcp.tools/call, user.login_success, firewall.deny, credential.update |
actor |
The user, or system for background work |
entity |
What the event is about |
details |
The event-specific payload |
The event catalog lists every action. Most follow {entity type}.{event name}. Write rules against the prefix where you can.
How much a tool call records
The MCP audit log level in the organization settings decides the detail:
| Level | What a tool call records |
|---|---|
off |
Nothing |
terse (default) |
The server, the tool, the duration, and who called it |
detailed |
Adds the call arguments |
detailed-with-error |
Adds the response of failed calls |
verbose |
Adds the response of every call |
Sign-ins and configuration changes are always recorded. A details payload over 64 KB is truncated to a preview. Secret values are replaced with [REDACTED] at every level and at any depth.
The steps in Gatana
- Open the organization settings and set the MCP audit log level. Start with
detailedif your policy needs arguments. - Open Settings, then SIEM Streaming. One destination exists for each organization. The feature is part of the paid plans.
- Enter the HTTPS endpoint of your collector. In Gatana Cloud, the address must be public. A self-hosted installation can stream to a private address.
- Enter the optional authentication header that the collector expects.
- Select the sources:
audit_logs,audit_logs_credentials, or both. - Save the signing secret. It is shown one time and can be revealed again in the dashboard.
- Click Send test event. A
gatana.testevent is delivered. The dialog shows the HTTP status and the start of the response.
Receiver setup
| Collector | Endpoint | Authentication header |
|---|---|---|
| Splunk HTTP Event Collector | https://<host>:8088/services/collector/raw |
Authorization: Splunk <hec-token> |
| CrowdStrike Falcon LogScale | https://<host>/api/v1/ingest/hec/raw |
Authorization: Bearer <ingest-token> |
| Elastic | An HTTP input on your ingest pipeline; split lines, parse each as JSON | Authorization: ApiKey <key> |
| Microsoft Sentinel | A relay that accepts the POST and forwards to the Logs Ingestion API | As the relay requires |
| Anything else | Any endpoint that accepts an authenticated HTTPS POST of newline-delimited JSON | As the collector requires |
For Splunk, set sourcetype on the token or its input.
Verify the signature
Every request carries a signature over the exact body:
X-Gatana-Signature: t=1785825392,v1=6c3b...f19a
t is a Unix timestamp in seconds. v1 is HMAC-SHA256 of "{t}.{raw body}", keyed with the signing secret. Verify against the raw body before any JSON parsing. Reject a request whose timestamp is outside your tolerance.
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const age = Math.abs(Math.floor(Date.now() / 1000) - Number(parts.t));
if (!Number.isFinite(age) || age > toleranceSeconds) return false;
const expected = createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(parts.v1 ?? '', 'hex');
return a.length === b.length && timingSafeEqual(a, b);
}
A secret rotation takes effect immediately. Update the receiver first, or accept a short gap. If the receiver can examine two secrets, add the new one before you rotate.
Delivery guarantees
| Property | Value |
|---|---|
| Batch size | Up to 500 events or about 1 MB, as application/x-ndjson |
| Frequency | One time each minute. Expect an event within about one minute of the action |
| Timeout | 10 seconds. Redirects are not followed |
| Semantics | At least once. Deduplicate on id |
| Order | Events from each source arrive in order |
| Retries | After 1, 5, 15 and then 60 minutes |
| After 1 hour of failures | The destination is marked failing in the dashboard |
| After 24 hours | Administrators receive one email |
| After 7 days | Delivery is disabled and a second email is sent |
| Recovery | The read position is kept. Reactivation continues from where delivery stopped, as far back as audit retention permits |
Rules worth writing first
firewall.deny: a tool call that a rule blocked. Alert on a burst from one actor.siem_destination.*: a change to the streaming configuration itself. Alert on every one.credential.auto_oauth_refresh_failedandcredential.expired_no_refresh: a connection about to break.user.createdanduser.disabled: compare with the identity provider.mcp.tools/callvolume per actor: a baseline, then a threshold.tenant_configuration.update: a change to organization settings, including the audit level.
What is never sent
- Secret values: tokens, passwords, client secrets, private keys and API keys are replaced with
[REDACTED]. - Encrypted secret columns: a change is reported as
{"changed": true}and never decrypted. - Internal events visible only to Gatana staff, such as billing traffic.
The SIEM streaming page in the documentation has the full reference, including a Python verifier.
FAQ
Questions, answered.
What does each audit record contain?
- Each record names the event, the time, the organization, the actor and the entity. For a tool call, it names the server, the tool, the duration and the credential that the call came through. At the detailed level, it adds the arguments. At the verbose level, it adds the response. Sign-ins and configuration changes are always recorded.
How fast do events arrive in the SIEM?
- Within about one minute. The exporter runs one time each minute. It holds events back for a few seconds so that it skips none. Batches contain up to 500 events or about one megabyte. The destination must answer with a 2xx status within 10 seconds.
Can the same event arrive twice?
- Yes. Delivery is at least once. A batch that was accepted but whose acknowledgement was lost is sent again. Thus the same event can arrive twice. Deduplicate on the id field. It is stable, unique and repeatable for the same event. Events from each source arrive in order.
How do I verify that a batch came from Gatana?
- Every request carries an X-Gatana-Signature header with a timestamp and an HMAC-SHA256 value over the raw body, keyed with your signing secret. Verify against the raw body before you parse JSON. Reject a request whose timestamp is older than a tolerance that you select. Five minutes is a reasonable default.
Which SIEMs work with the stream?
- Any collector that accepts an authenticated HTTPS POST of newline-delimited JSON. Splunk HTTP Event Collector and CrowdStrike Falcon LogScale accept the batches on their raw endpoints. Elastic takes them on an HTTP input. Microsoft Sentinel receives them through a relay. In-house pipelines operate in the same way.
Does the stream include tool arguments and responses?
- Only if you increase the MCP audit log level. The default, terse, records the server, the tool, the duration and the caller. Detailed adds the arguments. Detailed with error adds the response of failed calls. Verbose adds every response. Secret values are replaced with a redaction marker at every level.