Skip to content
Menu

Ship the MCP audit and security events to your SIEM

View .md
You end up withThe Beacon.Audit log stream and the security log events in your SIEM, traces and metrics in your collector, retention and content locks set, and alerts on the signals that matter
Timeabout 30 minutes
You needA running Beacon host (Integrate Beacon or the MCP gateway), a log pipeline that reads container stdout (CloudWatch, a collector or a SIEM agent), and for traces an OpenTelemetry collector
Applies toBeacon 5.0 · Both. The MCP audit (events 9100 to 9103, the UI page, tracing and metrics) applies to the gateway too. The security audit trail (9104 to 9114) and its page are full Beacon: the gateway serves no security audit API or page
DecisionOptionsDefault
Fail closedWithhold a tool result when its audit row was not written (Audit:Required), or return it anywayFail closed where an unaudited answer is not acceptable
MCP audit retentionKeep forever, or purge rows older than N daysForever (null): set it to your policy
Audit contentKeep the question and SQL of every call (RequireContent), let each project decide, or keep none (ForceNoContentRetention)Each project decides. The two locks cannot both be on
Traces and metricsOTLP to a collector, or noneNone: Beacon registers no exporter

Beacon writes its audit events as structured log events under the category Beacon.Audit. JSON console logs carry the event id, category, level and trace ids with no OpenTelemetry needed:

{
"Logging": {
"Console": {
"FormatterName": "json",
"FormatterOptions": { "IncludeScopes": true }
}
}
}

Make sure the log level for the category lets Information through ("Beacon.Audit": "Information" under Logging:LogLevel): the copies of committed events are Information, and a filtered category drops them.

Ship the stream with whatever reads your container’s stdout, such as the awslogs driver into CloudWatch Logs.

Beacon.Audit holds identifiers, counts and digests only: never parameters, SQL, a question or a raw error message.

EventIdNameLevelMeaning
9100McpToolAuditedInformationOne per MCP tool call, with Tool, ProjectId, CallerKind, CallerHash, DurationMs, Rows, ErrorClass, TraceId; Persisted says whether the row was written
9101McpToolResultWithheldWarningA result was withheld because its audit row failed (needs Audit:Required)
9102McpAuditPurgedInformationThe MCP audit retention purge ran
9103McpAuditReadInformationSomeone read the MCP audit
9104SecurityAuditRecordedInformationA security audit event was committed (copy of the row; match by EventGuid)
9105SecurityAuditReadInformationThe security trail was read or verified
9106SecurityAuditPurgedInformation or WarningThe security trail’s retention purge ran
9107SecurityAuditWriteFailedErrorA security event could not be stored
9108SecurityAuditVerificationFailedErrorStored security events failed verification
9109SecurityAuditCheckpointInformationOne per scheduled verification run: keep these, they anchor the trail outside the database
9110SecurityAuditPurgeFailedErrorThe security trail’s purge did not run
9111McpAuditVerificationFailedErrorStored MCP audit rows failed verification
9112McpAuditCheckpointInformationOne per MCP audit verification run: keep these with the 9109 checkpoints
9113AuditTableVerificationFailedErrorStored execution log or settings history rows failed verification (Store names the table)
9114AuditTableCheckpointInformationOne per sealed table per verification run: keep these with the 9109 checkpoints

The full field list is in MCP observability and audit and Shipping to a SIEM.

The structured security log events 7101 to 7123 (membership and role changes, sessions, break-glass sign-ins, refused access, row data sent to an LLM, and more) have their own ids: route them too. The list is in the security model. Alert at least on 7119 (break-glass), 7113 and 7114 (role changes, new users), 7115 (write targets) and 7116 (a statement tried to end SQL Server’s read-only transaction).

The copy is best-effort: the database is the source of truth, and the 9107 lines and the 9109 checkpoints let you notice when the two disagree.

3. Set retention, content and fail-closed audit

Section titled “3. Set retention, content and fail-closed audit”
{
"Beacon": {
"Mcp": {
"Audit": {
"Required": true, // withhold a result whose audit row was not written
"RetentionDays": 400, // purge MCP audit rows older than this; null keeps them forever
"RequestIdHeader": "X-Request-Id",
"RequireContent": false // true pins question and SQL retention on, for everyone
}
},
"Audit": {
"RetentionDays": 400 // security audit trail; at least 365; unset keeps events forever
}
}
}
  • Required: true replaces the result with Result withheld: the audit record for this call could not be written. and logs 9101. The query has already run by then: the guarantee is that no unaudited data leaves Beacon.
  • RequireContent: true keeps the question and the SQL of every statement a call ran until the retention purge. Review that against your privacy policy first. It contradicts Beacon:Mcp:ForceNoContentRetention, and the host does not start with both on. The gateway pins no content retention unless you set RetainToolContent = true.
  • Both purges are jobs you schedule: the MCP purge is IMcpAuditRetentionService.PurgeExpiredAsync, the security trail has ISecurityAuditRetentionService.PurgeExpiredAsync and an hourly ISecurityAuditVerificationService.VerifyNewEventsAsync. Set Beacon:Audit:IntegrityKey from a secret store before you change the encryption key. See Retention and Keys and rotation.

Beacon registers no exporter. Reference the OpenTelemetry packages your platform needs and add Beacon’s sources:

builder.Services.AddOpenTelemetry()
.ConfigureResource(x => x.AddService(serviceName: "your-app"))
.WithTracing(x => x
.AddAspNetCoreInstrumentation()
.AddBeaconInstrumentation() // source: "Beacon"
.AddBeaconMcpSdkTracing() // source: "Experimental.ModelContextProtocol", status descriptions cleared
.AddOtlpExporter())
.WithMetrics(x => x
.AddAspNetCoreInstrumentation()
.AddBeaconInstrumentation() // meters: "Beacon" and "Experimental.ModelContextProtocol"
.AddOtlpExporter());

Set OTEL_EXPORTER_OTLP_ENDPOINT (and OTEL_SERVICE_NAME) in the environment: Beacon reads no endpoint or credential itself. Beacon tags the MCP SDK’s own tools/call span with beacon.project.id, beacon.caller.hash, beacon.error.class and similar, so one MCP call is one trace. Each audit row carries its TraceId, which joins the SIEM, the trace and the row.

Alert on any increment of these counters:

CounterMeaning
beacon.mcp.audit.write_failuresAn MCP audit row was not written
beacon.security_audit.write_failuresA security event was not stored, or a purge did not run
beacon.security_audit.verification_failuresStored security events failed verification

beacon.mcp.tool.calls (tagged gen_ai.tool.name, beacon.outcome) and beacon.mcp.tool.rows give usage and error rates. Tool names on the metric are bucketed (q_*, api_*) so caller-chosen names cannot create unbounded series. beacon.tool.input (the tool input on the span) stays off unless both Beacon:Telemetry:CaptureContent and the project’s content retention allow it.

A collector that receives OTLP and exports to X-Ray and CloudWatch metrics:

receivers:
otlp:
protocols:
http:
grpc:
processors:
batch:
exporters:
awsxray:
awsemf:
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [awsxray]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [awsemf]

The collector holds the cloud credentials, never Beacon.

The MCP Audit page (/beacon/mcp-audit, in the sidebar for people with the Auditor permission) lists tool calls with time, tool, project, caller, user, duration, rows and outcome. Filter by From and To (UTC), Tool, Project id, User id and Caller hash. The gateway’s admin UI serves it too. Reading the audit is recorded: 9103 and, in full Beacon, a security audit event.

The same data is available as a paged JSON export for an Auditor or an Admin (sign in as one):

GET https://beacon.example.com/beacon/api/mcp/audit?from=2026-10-01T00:00:00Z&to=2026-10-21T00:00:00Z&pageSize=100

from and to are required and at most 93 days apart; pageSize is 1 to 500. Rows carry contentHash and their statements, with sql only where the project retained content. Full Beacon also has a Security Audit page for the security trail.

  • Make one MCP tool call. Your SIEM shows a Beacon.Audit event 9100 for it, and its TraceId matches the MCP Audit row.
  • In full Beacon, change a project member. A 9104 event arrives with an EventGuid that matches the Security Audit row.
  • With a collector configured, a tool call shows one trace tagged with beacon.project.id.
  • With Audit:Required on, a tool result is never returned without its row; a failure shows as 9101 and an increment of beacon.mcp.audit.write_failures.
  • The retention jobs run: 9102 (and 9106 in full Beacon) appear after the purge.
SymptomCause and fix
No Beacon.Audit events in the SIEMThe category is filtered below Information, or the log driver does not ship stdout. Check Logging:LogLevel for Beacon.Audit.
9100 events but no 91049104 is full Beacon only and best-effort: it is missing when the category is filtered or the process stops between commit and log.
Host fails to start with an options errorRetentionDays must be greater than 0 (security trail: 365 to 36500), RequestIdHeader must be a valid header token, and RequireContent cannot be on with ForceNoContentRetention.
No CorrelationId on old rowsRows written before it existed carry none; every later row and 9100 event has one Beacon minted.
UpstreamRequestId is emptyThe header is absent, longer than 128 characters, or has characters outside [A-Za-z0-9._:-].
No tracesAddBeaconInstrumentation() is not on the tracer provider, or OTEL_EXPORTER_OTLP_ENDPOINT is unset.
Logs in the dispatched endpoint carry no trace idHost endpoint dispatch does not flow Activity.Current. See Trace continuity.
The audit page is missing from the sidebarThe user lacks the Auditor permission.