On this page
MCP Logging and Audit Logs: Record What Your AI Agent Did (Nov 2026)

MCP logging means two things: the protocol's own debug logging feature (now deprecated) and an audit log, a record of every tool call an agent made. MCP doesn't keep an audit log for you. An audit log shows who asked, which agent, which server and tool, the arguments, the result and the time. You build it from your client's telemetry, a gateway, and the vendor's own audit log. And if the agent signs in with your token, every one of those logs will say you did it.

This question keeps coming up. On r/LLMDevs, someone asked how to audit what an autonomous agent read; the top answer was an append-only log of every tool call with timestamps, inputs and outputs. On r/LangChain, people asked which agent accessed what, and the answer was one service identity per agent plus one central log. The r/mcp thread "if an AI agent uses my valid credentials to do something…" points at the harder part: an agent using your login looks like you. A Salesforce developer asked what changes once Claude or Slack can drive your org. And one builder of a public agent endpoint keeps an audit log keyed by token, denied attempts included.

This guide covers what to record, how to tell the agent apart from you, the logs Claude Code can already write, vendor audit logs, and what to do when there's no log at all.

MCP logging: two different things

"MCP logging" can mean the protocol's own logging feature or an audit log. They answer different questions.

MCP's logging feature An audit log
What it is Debug messages a server sends to the client during a request A lasting record of who called which tool, with what, and what happened
Who writes it The server Your client, a gateway, or the vendor's service
Kept afterwards Only if the client chooses to store it Yes, that's the point
Status in the 2026-07-28 spec Deprecated Not part of the spec

The protocol feature. A server declares the logging capability and sends notifications/message with a syslog level, from debug up to emergency. In the current spec, 2026-07-28, it is per request: the client asks for messages by putting io.modelcontextprotocol/logLevel in a request's _meta, and the server may only send them on that request's response stream. The same revision marks the whole feature deprecated (SEP-2577). New implementations "SHOULD NOT adopt it". Existing ones should move "to logging to stderr for stdio transports, or to OpenTelemetry for structured observability". It stays in the spec for at least twelve months before it can be removed.

The spec also says log messages "MUST NOT contain" credentials, secrets or personal information. That rule is worth keeping for any log you write.

If you build servers: write debug output to stderr (on stdio, stdout is reserved for protocol messages) and use OpenTelemetry for traces. Our Python MCP SDK guide shows where FastMCP logs go. If a server won't start and you need the client's debug log, see Claude Code MCP server not working.

The rest of this guide is about the audit log.

What an MCP audit log should record

One line per tool call. These are the fields that let you answer "who did what" a month later.

Field Example Why you need it
Time 2026-11-11T09:14:03Z Puts events in order across logs
Person The human who started the session Someone is accountable
Agent and client Claude Code, session ID, which subagent Tells the agent's actions apart from yours
Server and tool github / create_pull_request What was touched
Arguments Repo, branch, search query (secrets removed) What it asked for
Decision Allowed by a rule, approved by you, or denied Shows your guardrails worked
Result Success or error, duration What happened
Output Optional: a hash or the first lines What the agent read
Upstream identity Which token or account the server used Joins your line to the vendor's log

Four rules make the log useful:

  • Log denied calls too. A blocked attempt is often the most useful line in an incident.
  • Append-only, and off the machine. An agent with a shell can edit a file in your home folder. Send lines somewhere it can't write to.
  • Treat it like the data it holds. Arguments and outputs can include customer data. Limit who can read the log and decide how long to keep it.
  • Keep the tool call ID. It lets you join the client line, the gateway line and the vendor line for the same call.

Per-agent identity vs a shared token

A log can only name whoever signed in. If the agent uses your personal token, the vendor's audit log says you did it, and nobody can sort it out later.

Setup What the vendor's log shows What you can tell later
Agent uses your personal access token You Nothing: you and the agent look the same
One shared token for all agents One bot account That an agent did it, not which one or for whom
OAuth per user, through the MCP server You, sometimes tagged as MCP or the app Your actions, and maybe that they came via MCP
One service identity per agent That agent Which agent did it; scope it to that agent's job

The MCP spec makes the same point when it bans token passthrough (a server forwarding the client's token unchanged). In its security best practices, such a server "will be unable to identify or distinguish between MCP Clients", and the downstream "logs may show requests that appear to come from a different source with a different identity". The rule is plain: "MCP servers MUST NOT accept any tokens that were not explicitly issued for the MCP server."

Broad tokens blur the log too. The spec warns that poor scope design "obscures audit trails", and that a single catch-all scope "masks user intent per operation". A narrow token per agent fixes both: smaller blast radius, clearer log. Our authentication guide compares OAuth, API keys and personal access tokens, and limit MCP tools explains why the token is the real limit.

Log MCP tool calls in Claude Code with OpenTelemetry

Claude Code can send events to any OpenTelemetry (OTel) collector, the standard pipe most log tools accept. The monitoring docs give this quick start; the last line is ours and it matters for MCP:

export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_LOGS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
export OTEL_LOG_TOOL_DETAILS=1

Why OTEL_LOG_TOOL_DETAILS=1: for MCP servers you added yourself, the docs say "the event's tool_name is always the literal "mcp_tool"". You'd see that an MCP tool ran, not which one. With the flag on, mcp_server_name and mcp_tool_name appear in tool_parameters, along with the tool input.

Two events make up the audit trail:

  • claude_code.tool_decision: accept or reject, plus the source of the decision (config, hook, user_permanent, user_temporary, user_abort, user_reject). This is where refused calls show up.
  • claude_code.tool_result: sent after the tool ran, with success, duration_ms, error_type and, for MCP tools, mcp_server_scope.

Events carry session.id and, when signed in, user.email, user.account_uuid and organization.id. That's the "person" column.

Outputs and prompts are off by default. OTEL_LOG_TOOL_CONTENT=1 adds tool output to a tool.output span event, but it needs tracing (CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 plus OTEL_TRACES_EXPORTER) and is cut at 60 KB by default. Prompts stay redacted unless you set OTEL_LOG_USER_PROMPTS=1.

For a team, put these variables in the env block of the managed settings file. For the OTEL_EXPORTER_OTLP_* variables, Claude Code "removes conflicting developer-set variables at startup". Set the exporter selectors there too: otherwise a developer can still switch a signal off or to the console. Our Claude Code settings guide explains which file wins.

A Claude Code hook that writes an audit line

No collector yet? A hook can append one JSON line per MCP call. MCP tools are named mcp__<server>__<tool>, so the matcher mcp__.* catches all of them. PostToolUse fires after a tool succeeds and receives tool_input and tool_response. PostToolUseFailure fires when a call fails and receives an error. Add this to .claude/settings.json (it uses jq):

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "mcp__.*",
        "hooks": [
          {
            "type": "command",
            "command": "jq -c '{at: (now|todate), session_id, tool_use_id, tool_name, tool_input, ok: true}' >> \"$HOME/.claude/mcp-audit.jsonl\"",
            "async": true
          }
        ]
      }
    ],
    "PostToolUseFailure": [
      {
        "matcher": "mcp__.*",
        "hooks": [
          {
            "type": "command",
            "command": "jq -c '{at: (now|todate), session_id, tool_use_id, tool_name, tool_input, error}' >> \"$HOME/.claude/mcp-audit.jsonl\"",
            "async": true
          }
        ]
      }
    ]
  }
}

"async": true runs the hook "in the background without blocking", so it doesn't slow the session. This logs arguments, not outputs. Add tool_response if you need what the agent read, and accept that the file gets bigger and more sensitive.

Know the limits:

  • It only sees this client. Calls from Cursor, VS Code or your own agents need their own logging, or a gateway.
  • A local file isn't tamper-proof. The agent's shell can edit it. Admins can set allowManagedHooksOnly in managed settings, which blocks user, project and local hooks so only the managed ones run. The log file itself still needs to leave the machine.
  • It only sees MCP calls. If the agent runs curl with your token through its shell, no MCP log will show it. OTel's Bash events will, with tool details on.

Every hook also receives transcript_path, the session's conversation file. It records tool calls too, but it lives on your machine and Claude Code deletes transcripts after 30 days by default (cleanupPeriodDays).

Vendor audit logs: the record the agent can't edit

The service's own audit log lives on the vendor's side, out of the agent's reach. Some vendors now list MCP activity on its own:

  • Atlassian (Rovo MCP): every tool call is recorded. Find them under Insights → Audit log in Atlassian Administration, filtered for Rovo MCP User Actions. See our Atlassian MCP guide.
  • Slack: Slack says MCP activity shows up in its audit logs, and admins approve MCP client integrations. See our Slack MCP guide.
  • Miro: the repo's Enterprise guide says MCP actions are logged in your audit trail, and its FAQ says audit controls are still evolving. See our Miro MCP guide.

Check two things in any vendor log:

  1. Does it log reads, or only changes? If only changes, your client log is the only record of what the agent read.
  2. Does it show the agent, or only the person? That depends on the identity you picked in the section above.

Many servers, local stdio ones especially, wrap a single API key and keep no log of their own. Then the vendor's API logs show only that key.

MCP observability: traces across client, gateway and server

Observability means following one call through every hop and seeing where it slowed down or failed. OpenTelemetry has semantic conventions for MCP, still at "Development" status. They name a span {mcp.method.name} {target}, for example tools/call get_weather, with attributes such as mcp.method.name and gen_ai.tool.name. Tool arguments and results are opt-in attributes, flagged as "may contain sensitive information".

To join the client's span with the server's, the trace context (traceparent, tracestate, baggage) travels inside each request's params._meta. Call times go into two metrics: mcp.client.operation.duration and mcp.server.operation.duration.

A gateway is the one place every call passes through. It sees all clients, knows who signed in, and can log each call once. That's why we list "audit log of every tool call" as a must when you choose an MCP gateway. For proxy services that start stdio servers, the spec's list of SHOULDs includes: "Log all stdio transport usage for security monitoring".

When there's no log at all

Sometimes you get a local stdio server holding your API key, a client without telemetry and a vendor without audit logs. Then all you have is the session transcript and the server's stderr. If you can't see what the agent does, cut what it can do:

  • Read-only first. Turn on the server's read-only mode and give it a read-only token. See server read-only flags.
  • Ask before writes. Put write tools on "ask" or "deny" in your Claude Code permissions.
  • Box the server in. A separate OS user or a container limits what a local server can touch. See three levels of MCP sandbox.
  • Prefer the remote option when there is one. A remote server with OAuth usually sits behind the vendor's audit log; a local one with a shared key doesn't. Check it first with our 5-minute MCP security checklist.

Set up an MCP audit log in 5 steps

Tick these off as you go. Your progress stays in this browser.

1. List each server and the identity it uses

Write down every MCP server your team runs and which token or account it acts as. Any server running on someone's personal token goes to the top of the list.

2. Give each agent its own identity

Use per-user OAuth where the server supports it, or a service account per agent with only the scopes that agent needs. Retire shared tokens.

3. Turn on client telemetry with tool names

In Claude Code, enable OpenTelemetry with OTEL_LOG_TOOL_DETAILS=1, set through managed settings for a team. No collector yet? Start with the hook above.

4. Turn on the vendor audit logs and test them

Find each vendor's audit log, run one harmless MCP call, and check that it shows up, with the identity you expect.

5. Ship logs off the machine and test a denied call

Send the lines to a store the agent can't edit. Then deny one tool call on purpose and confirm the refusal appears too.

When client logs aren't enough

Need one audit trail across Claude Code, Cursor and your own agents, with per-agent sign-in and the logs in your own stack? We build custom MCP servers and gateways with that built in. Tell us about it.

Frequently asked questions

What is MCP logging?

It can mean two things. The protocol's logging feature lets a server send debug messages to the client during a request. An MCP audit log is a lasting record of which agent called which tool, with what arguments and result. The first is for debugging, the second for accountability.

Is MCP's logging feature deprecated?

Yes. The 2026-07-28 spec marks it deprecated (SEP-2577). New implementations should not adopt it, and existing ones should log to stderr on stdio or use OpenTelemetry. It stays in the spec for at least twelve months before it can be removed.

Does MCP have a built-in audit log?

No. The protocol defines tool calls, not a record of them. The log comes from your client (such as Claude Code's OpenTelemetry events), a gateway, or the vendor's own audit log.

How do I see which MCP tools Claude Code called?

Turn on telemetry with CLAUDE_CODE_ENABLE_TELEMETRY=1 and an OTLP exporter, and add OTEL_LOG_TOOL_DETAILS=1. Without that flag, calls to MCP servers you added yourself show up as mcp_tool, with no server or tool name. A PostToolUse hook with the matcher mcp__.* is a quick local alternative.

How can I tell my actions from my agent's in an audit log?

Give the agent its own identity: per-user OAuth through the MCP server, or a service account per agent. If the agent borrows your personal token, the vendor's log shows you either way.

Should an MCP audit log store tool arguments and outputs?

Store arguments with secrets removed: they show what the agent asked for. Outputs show what it read, but they can hold customer data and grow fast. Store them only if you need them, and protect the log like the data inside it.

How do I log MCP server requests?

For debug output, write to stderr on stdio servers or use OpenTelemetry. For a lasting record of tool calls, log them on the client (Claude Code's OpenTelemetry events or a PostToolUse hook), at a gateway, or in the vendor's audit log.

What is MCP observability?

Following MCP calls across client, gateway and server with traces and metrics. OpenTelemetry's MCP conventions (still in development) name the spans, pass trace context in each request's params._meta, and define duration metrics for clients and servers.