For the complete documentation index, see llms.txt. This page is also available as Markdown.

Audit logging

Every operation that passes through mcp is logged — CLI commands, proxy requests, tool calls, registry searches. The audit log gives you full visibility into what happened, when, how long it took, and whether it succeeded.

How it works

mcp writes audit entries to an embedded ChronDB database stored locally. Logging happens in a background thread via an async channel, so it never blocks your commands.

mcp <any command>  -->  AuditLogger (mpsc channel)  -->  ChronDB (background writer)
                                                             |
                                                    ~/.config/mcp/audit/

Every entry records:

Field
Description

timestamp

ISO 8601 timestamp

source

Where it came from: cli, serve:http, serve:stdio

method

What was called: tools/call, tools/list, registry/search, etc.

tool_name

Tool name (for tools/call)

server_name

Backend server name

identity

Who called it: local for CLI, user subject for proxy

duration_ms

How long it took

success

Whether it worked

error_message

Error details when it failed

acl_decision

allow or deny when ACL evaluation is performed

acl_matched_rule

Which rule decided: dev[1], alice.extra[0], default, legacy[3], legacy:default, no-acl

acl_access_kind

Effective access evaluated: read, write, or *

classification_kind

Tool classification: read, write, or ambiguous

classification_source

How it was classified: override, annotation, classifier, or fallback

classification_confidence

Classifier confidence (0.00–1.00)

ACL/classification fields are present on entries that perform ACL checks: proxy tools/call, tools/list:filtered, and CLI acl/check. Other entries omit them (the fields are absent, not null).

What gets logged

Everything:

Command
Method

mcp --list

servers/list

mcp search <query>

registry/search

mcp add <name>

config/add

mcp remove <name>

config/remove

mcp update <name>

config/update

mcp <server> --list

tools/list

mcp <server> --info

tools/info

mcp <server> <tool>

tools/call

Proxy: any JSON-RPC request

initialize, tools/list, tools/call, resources/list, resources/read, prompts/list, prompts/get

The only command that doesn't log itself is mcp logs (that would be recursive).

Querying logs

Output formats

Terminal (interactive) — colored table:

JSON (piped or --json) — composable with jq:

Follow mode

Stream new entries in real-time, like tail -f:

Follow mode uses polling (1s interval) on the ChronDB database, so it works even when mcp serve runs in a separate process.

Configuration

Add an audit section to ~/.config/mcp/servers.json:

Field
Default
Description

enabled

true

Enable/disable audit logging

output

unset (→ file for CLI, file+stdout for serve --http, file+stderr for serve stdio)

Output destination: file (ChronDB, queryable), stdout, stderr (JSON lines), file+stdout, file+stderr (ChronDB and JSON lines), or none. Internally Option<AuditOutput> — leaving it unset means "use the per-context default" (CLI safety vs serve visibility). Any explicit value (including "file") bypasses the auto-promotion in mcp serve.

log_arguments

false

Log tool call arguments (may contain PII)

path

~/.config/mcp/audit/data

ChronDB data directory

index_path

~/.config/mcp/audit/index

ChronDB index directory

Logging arguments

By default, tool call arguments are not logged to avoid capturing sensitive data (API keys, personal info, query contents). Enable log_arguments only if you need it:

With this enabled, mcp logs --json will include the full arguments:

Storage

Audit data lives in ~/.config/mcp/audit/ by default:

Each entry is stored as a JSON document with key audit:{timestamp_millis}-{uuid}, which gives natural chronological ordering via prefix listing.

Disabling audit logging

Via config file:

Via environment variable (takes priority over config file):

When disabled, the logger is a no-op and the database is not initialized — zero overhead, no files created, no filesystem writes. This is the default in the Docker image.

Output destinations

The output field controls where audit entries go.

The configuration distinguishes explicit values from absent ones. Internally output is Option<AuditOutput>: missing from the config and unset in MCP_AUDIT_OUTPUT means None (default); a present value means Some(...) (explicit). The distinction is what lets mcp serve auto-promote the default without ever overwriting a deliberate operator choice.

CLI subcommands (mcp roam ..., mcp gh ..., etc.) resolve None to file — entries are persisted to ChronDB and stdout stays clean (so pipelines like mcp ... | jq aren't corrupted by audit JSON interleaved with command output).

mcp serve resolves None to a dual-sink mode:

  • HTTP transport (mcp serve --http ...): Nonefile+stdout. Audit is mirrored on stdout so it's visible in docker logs/kubectl logs without an extra command, while still persisting to ChronDB for mcp logs queries.

  • Stdio transport (mcp serve): Nonefile+stderr. Stdout in stdio mode is the JSON-RPC channel, so the mirror goes to stderr instead.

Any explicit value in the config file or MCP_AUDIT_OUTPUT env var (including "file") bypasses the auto-promotion. To force chrondb-only output in mcp serve, set "output": "file" explicitly — it survives the resolution untouched.

Each stdout/stderr line is a complete AuditEntry JSON object:

The mirror is emitted before the ChronDB write, so entries stay visible even if the persistence layer fails.

Choosing a mode

output

ChronDB

stdout

stderr

mcp logs

unsetfile (CLI default) / file+stdout (serve http) / file+stderr (serve stdio)

varies

varies

varies

when ChronDB is active

file (explicit)

file+stdout

file+stderr

stdout

stderr

none

Pick file explicitly if you want chrondb-only even in mcp serve — explicit values skip the auto-promotion. Pick stdout/stderr only when you can't persist (read-only filesystem, ephemeral containers without a volume). Pick file+stderr if your transport is stdio or you want to keep stdout reserved for application output.

When using stdout or stderr alone (without file), mcp logs queries are not available — there's no database to query. Use your log aggregation pipeline instead.

stdio transport caveat: in mcp serve without --http (stdio mode), stdout is the JSON-RPC channel. The default auto-promotion picks file+stderr. If the operator explicitly picks stdout or file+stdout, it's rewritten to the stderr variant with a warning so the JSON-RPC channel stays clean.

Set output to none to disable audit entirely without touching the enabled flag.

Environment variable overrides

All audit settings can be overridden via environment variables, which take priority over the config file. This is useful for container deployments where editing the config JSON is impractical.

Variable
Overrides
Description

MCP_AUDIT_ENABLED

audit.enabled

Set to false or 0 to disable

MCP_AUDIT_OUTPUT

audit.output

file, stdout, stderr, file+stdout, file+stderr, or none

MCP_AUDIT_PATH

audit.path

ChronDB data directory

MCP_AUDIT_INDEX_PATH

audit.index_path

ChronDB index directory

Example: redirect audit to a mounted volume in Docker:

Example: stream audit to container stdout (no volume needed):

See the full list of variables in the environment variables reference.

Last updated

Was this helpful?