Direct answer

Log each agent tool call as a security event, not as a generic request log. The useful record ties together the human or service identity, agent client, source system, tool name, permission decision, request shape, timing, result class and failure detail, while deliberately redacting secrets and large payloads. For MCP, that means treating tools/list and tools/call as separate events because discovery, authorization and execution answer different audit questions.

Start With The Questions An Audit Must Answer

An agent tool-call log should let a later reviewer reconstruct who caused an action, which system was reachable, why the tool was allowed, what safe argument shape was passed, what happened upstream and what the agent received back. A normal reverse-proxy log rarely answers that. It may show a bearer token, a path and a status code, but it cannot reliably distinguish a read-only Slack search from a Jira issue update.

For MCP, split discovery from invocation. A client sends tools/list to discover tools and tools/call to invoke one. Discovery says what the model could see in context; invocation says what it attempted. If you only log calls, you miss tools exposed but unused. If you only log HTTP requests, you miss the protocol-level tool name, input schema and result state.

Use stable correlation ids: one inbound request id, one logical tool-call id and one upstream request id. During an incident, that lets you move from the chat transcript to the MCP proxy, then to GitHub, Sentry, Postgres or another upstream system without guessing which retry produced the visible side effect.

Record The Decision, Not Just The Outcome

The most important field is often not status: success; it is the permission decision before execution. Record the presented credential id, workspace id, source id, requested tool, required grant, source allowlist, annotation state and final decision. A key with call:tools can be allowed to run trusted read-only tools, while a write-capable operation should require a separate write grant. If a tool lacks a trusted readOnlyHint, log that policy treated it as write-capable.

Tool annotations are useful audit inputs, but they are not proof. The MCP annotation vocabulary includes readOnlyHint, destructiveHint, idempotentHint and openWorldHint; log both the hint and the trust basis. A record that says readOnlyHint=true, trusted_server=false, decision=denied is more useful than 403 because it shows the proxy saw the hint and refused to rely on it.

{
  "event": "mcp.tool.authorization",
  "workspace_id": "wrk_9f3",
  "actor_id": "usr_42",
  "client": "claude-code",
  "credential_id": "key_7c1",
  "source": "github",
  "tool": "create_issue",
  "required_grant": "call:write_tools",
  "presented_grants": ["call:tools"],
  "decision": "deny",
  "reason": "missing_write_grant"
}

This record is intentionally small. It omits the user prompt, full issue body and raw access token, but it explains the policy decision and can join to a separate redacted invocation event.

Capture A Safe Shape Of The Arguments

Arguments are where audit logs become dangerous and useful at the same time. Logging nothing leaves you unable to distinguish SELECT count(*) FROM invoices from SELECT * FROM employees. Logging everything turns the audit system into a shadow data lake full of secrets, personal data and customer content. Store a classified request shape plus carefully bounded samples.

For JSON arguments, store top-level keys, primitive types, sizes and selected non-sensitive identifiers. For SQL, store statement class, tables touched and a normalized fingerprint; keep the raw query only when policy allows it. For API tools, log the method, path template and query key names, not secret headers or full response bodies. Tool definitions include an inputSchema, but schema presence is not safe logging. Treat fields named token, password, secret, authorization and cookie as redaction candidates.

{
  "event": "mcp.tool.call",
  "tool_call_id": "tc_01JZ",
  "source": "postgres_ro",
  "tool": "query",
  "argument_shape": {
    "query": {
      "sql_kind": "select",
      "tables": ["orders"],
      "fingerprint": "select-count-from-orders"
    }
  },
  "redactions": ["query.literal[0]"]
}

Result logging follows the same rule. Store result class, byte count, row count, content types, truncation status and whether the tool returned an error. Do not store exports, source files or customer records in the audit row unless the audit store is governed like the source system itself.

Log Discovery Separately From Execution

Many clients cache tool lists or keep an initial list for a whole session, so discovery is not a minor detail. A useful tools/list log records the client, source set, tool names returned, withheld tools and cache or pagination metadata. This is the evidence for why the model knew a tool existed, why another tool was absent, or whether a new risky tool became visible after an upstream descriptor changed.

MCP tool names are unique within a server, not across every system in your company. If two upstreams both expose search, a gateway should record the source namespace as part of the audit identity. A log entry that says tool=search is ambiguous; tool=linear/search_issues is reviewable.

{
  "jsonrpc": "2.0",
  "id": 17,
  "method": "tools/list",
  "params": {}
}

{
  "event": "mcp.tools.listed",
  "client": "cursor",
  "visible_tools": ["github/search_issues", "sentry/list_alerts"],
  "withheld_tools": ["github/create_issue"],
  "withheld_reason": "missing_write_grant"
}

That discovery record also supports change detection. If yesterday a source exposed three read-only tools and today it exposes a fourth write-capable tool, the audit trail should show when the descriptor first appeared, which clients saw it and which keys could call it.

Make Failures First-Class Events

Failed tool calls are not noise. They are where bad credentials, missing grants, upstream outages and unsafe agent behavior first become visible. Record enough structure to distinguish client error, proxy policy denial, upstream authentication failure, rate limit, timeout and malformed tool response. A single 500 bucket is too coarse for operations and too vague for security review.

For each invocation, record timing in stages: inbound received, authorization completed, upstream request started, upstream first byte, upstream completed and response returned. That shows whether latency came from the client, proxy, upstream MCP server or source API. It also makes bounded execution auditable: if a tool has a five-second timeout, the log should show whether the proxy enforced it.

Approval semantics deserve their own fields. OpenAI's MCP tool configuration exposes controls such as require_approval. Client-side approval is useful, but it should not be the only line in the record. Log whether the proxy required approval, whether the client reported approval, whether policy allowed automatic approval for read-only tools and whether the call was blocked before upstream execution.

Audit test

Before rollout, run one denied write call, one expired-token call, one upstream timeout and one successful read. If the records are hard to tell apart without raw payloads, the schema is not ready.

How this fits in SPYN3

Because every call goes through one endpoint, SPYN3 can log it in one place: the dashboard's Logs page lists each tool call with the action, whether an API key or a signed-in person made it, the time and whether it succeeded. Calls are bounded and timed as they are proxied, so a slow upstream shows up as a slow call against that source rather than as a vague agent delay. What SPYN3 cannot log is what the agent did with the answer. That part of the trail has to come from the client.

Keep The Log Useful Under Review

The audit store should be queryable by the people who use it. Security asks which write-capable tools were attempted by a credential. Engineering asks which upstream caused timeouts. Compliance asks whether a user-picked Drive file was the only document touched. Support asks why a client saw a tool but could not call it. Those questions point to indexed fields: workspace_id, actor_id, client, source, tool, decision, failure_class, upstream_status, tool_call_id and created_at.

Retention and access control matter as much as schema. Logs should outlive short-lived agent sessions, but not the organization's retention policy. Restrict raw payload access, separate secret-bearing transport logs from normalized audit events and make redaction deterministic enough that reviewers can trust the absence of sensitive values.

The final check is whether the log survives a change of client. Claude Code can add a remote MCP server with claude mcp add --transport http, VS Code uses mcp.json configuration and command-palette server management, and other clients have their own setup flows. Treat the client as one attribute on a protocol event, not as the owner of the evidence.

Built for the next step

Make every call explainable

Use one endpoint so tool calls, failures and withheld access have one audit trail.

Federation guide
About SPYN3

SPYN3 is a consolidated MCP layer: one hosted endpoint in front of every system you connect — MCP servers, Postgres, REST APIs, Google Drive — with tools foldered by source and read/write permissions per key. Switching agents means moving one URL. Free plan available; current capabilities are documented on the product facts page.

Sources

  1. MCP specification: tools — tool discovery, invocation and security considerations. Verified 14 Oct 2026
  2. MCP project: tool annotations — risk hints and their enforcement limits. Verified 14 Oct 2026
  3. OpenAI API: MCP servers and connectors — remote MCP servers, deferred loading, approvals and risks. Verified 14 Oct 2026
  4. Anthropic docs: Claude Code MCP setup — Claude Code MCP commands, scopes, transports and JSON configuration. Verified 14 Oct 2026
  5. VS Code documentation: add and manage MCP servers — VS Code MCP configuration, trust and logging behavior. Verified 14 Oct 2026