DEV Community

Radoslav Tsvetkov
Radoslav Tsvetkov

Posted on

MCP governance for an AI coding agent without breaking the audit chain

The Model Context Protocol gave AI agents a clean way to reach into systems. In a year it has become the default tool surface for serious agents. That is mostly good news. The mostly is the operative word.

Without care, MCP servers fragment the audit story. Tool calls land in three places: in the agent's runtime, in the gateway, and in the MCP server's own logs. None of them line up. By the time a regulator asks what happened, you have three formats and zero answers.

This post walks how I wire MCP into Akmon. The result is a single audit chain that captures every MCP tool call as a ToolCall event, with deterministic linkage in the journal, and a reviewable AGEF bundle at the end.

The commands and flags here are real Akmon v2.0.0 surface. I will note where Phase 4 features apply.

Why MCP audit logging is harder than it looks

Three reasons.

First, MCP is a fan out. A single agent session can call three MCP servers, each with its own tools, its own version, and its own logging story. If you log per server, you have three formats. If you log only at the agent level, you lose tool-level detail.

Second, tool inputs are user data. Tool arguments are not metadata; they are the conversation. Logs leak content unless you redact. AGEF makes content addressing explicit, so the bundle can carry the inputs in a way that survives review.

Third, the chain matters. A flat list of tool calls does not let you say "this file change was caused by this tool call which was caused by that model response". You need the chain. Akmon's audit chain and AGEF events are designed for that.

Solve those three and your audit is fast. Skip them and your audit is a forensic project.

Wiring MCP into Akmon

Akmon registers MCP servers with the --mcp-server flag, repeatable.

akmon --mcp-server https://mcp.tools.internal/orders \
      --mcp-server https://mcp.tools.internal/calendar \
      --task "Open a ticket for the failing tests and link the related order"
Enter fullscreen mode Exit fullscreen mode

Every MCP tool the agent calls becomes a ToolCall event in the audit chain. The event records tool_id, input_hash, output_hash, and an optional side_effects_hash. The hashes resolve to objects in the AGEF bundle, so the inputs and outputs are recoverable, content-addressed, and verifiable.

For interactive runs, the same flag works:

akmon chat --mcp-server https://mcp.tools.internal/orders
Enter fullscreen mode Exit fullscreen mode

For headless CI, combine MCP servers with a budget cap and JSON output:

akmon --yes --output json \
      --mcp-server https://mcp.tools.internal/orders \
      --mcp-server https://mcp.tools.internal/calendar \
      --max-budget-usd 2.50 \
      --task "summarize failing tests and create a ticket"
Enter fullscreen mode Exit fullscreen mode

If you have an organization that prefers a TOML manifest for MCP servers, encode them in a policy pack and pull from there. The pack belongs to the team. The merge order keeps it deterministic.

What gets recorded for each MCP tool call

A ToolCall event in AGEF v0.1 carries:

  • tool_id, a stable identifier for the tool.
  • input_hash, hash of the canonical CBOR encoding of the input.
  • output_hash, hash of the output.
  • side_effects_hash, optional, when the tool changed the world.

The full input and output bytes live in objects/<hex> files. The hash links the event to the bytes. A reviewer can recover the exact input and output by walking the hash to the file.

A small example, viewed through akmon inspect:

$ akmon inspect <session-id> --resolve
SessionStart  cwd=hash:8a91...
UserTurn      prompt=hash:7a91...
ProviderCall  provider=anthropic attempts=1 status=Success
AssistantTurn message=hash:b3c1...
ToolCall      tool=mcp.orders/lookup_order input=hash:9c1f... output=hash:c2a8...
PermissionGate policy=write_safe decision=allowed
ToolCall      tool=mcp.calendar/create_event input=hash:e7d4... output=hash:f1a3...
AssistantTurn message=hash:5b62...
SessionEnd    summary=hash:0a13...
Enter fullscreen mode Exit fullscreen mode

This is what an MCP-rich session looks like in evidence form. Every event has a hash linkage. Every input and output resolves to a file in objects/.

Policy at the MCP boundary

Policy in Akmon is a deterministic merge of profile, packs, project policy, and CLI override. For MCP, that means three useful levers.

First, restrict the tool surface in the profile. prod already has explicit-deny posture for side effects. You can layer named MCP tools on top.

Second, write a pack that allows specific MCP servers and named tools.

# .akmon/policy-packs/org-mcp.toml
[mcp]
allowed_servers = [
  "https://mcp.tools.internal/orders",
  "https://mcp.tools.internal/calendar"
]

[mcp.tools]
"mcp.orders/lookup_order" = { allowed = true }
"mcp.orders/refund" = { allowed = true, requires_approval = true }
"mcp.calendar/create_event" = { allowed = true }
"mcp.calendar/delete_event" = { allowed = false }
Enter fullscreen mode Exit fullscreen mode

Third, see what is actually applied:

akmon policy show-effective \
  --profile prod \
  --policy-pack .akmon/policy-packs/org-mcp.toml
Enter fullscreen mode Exit fullscreen mode

Effective policy is print-able. That ends the "I think the rule is" conversation.

Redacting MCP-related content before sharing

Some MCP tool inputs or outputs cannot leave the building. Akmon's redact command produces a derivative bundle in which selected objects are replaced by canonical CBOR sentinels.

akmon redact <session-id> \
  --output sanitized.akmon \
  --object <input-hash> \
  --object <output-hash> \
  --reason "Removed customer name before audit handoff" \
  --format json
Enter fullscreen mode Exit fullscreen mode

The chain still verifies because the sentinel hashes correctly. The redaction reason is recorded. The bundle can be shared with auditors without exposing the redacted content.

Verify before sharing:

akmon bundle import sanitized.akmon --verify-only
Enter fullscreen mode Exit fullscreen mode

Writing an MCP server that plays well with Akmon

If you maintain an MCP server, three small choices make audit life easier downstream.

First, make tool inputs and outputs canonicalizable. Avoid embedding wall-clock times in fields the agent does not control. Avoid random IDs in the response when the response could be deterministic.

Second, expose a stable tool ID and a stable version. The tool_id in AGEF is whatever the runtime emits. Namespacing your tools (mcp.orders/lookup_order) keeps the bundle readable a year from now.

Third, surface side effects explicitly. If a tool has a side effect, return enough metadata for side_effects_hash to make sense (for example, the new resource ID, the affected entity, the change type).

These are small DX choices that compound. They do not require any spec change.

Replay across MCP servers

akmon replay runs a session against the recorded providers and tools. For MCP-heavy sessions, replay surfaces divergence when an MCP server changed behavior in a way you did not intend.

akmon replay <session-id> --mode strict --format json
Enter fullscreen mode Exit fullscreen mode

In strict mode, replay treats mismatches more aggressively. That is the right mode for CI gating after a deployment that touched an MCP server. If the replay diverges, you know about it before the next session.

A practical migration plan

If you are adding MCP to an existing Akmon setup:

  1. Start with one MCP server, registered via --mcp-server. Run a few sessions interactively. Inspect the journal.
  2. Lock the allowed list in a pack. Run akmon policy show-effective to confirm.
  3. Add a CI job that runs the trust pipeline (audit verify, evidence verify, slo verify) on every session.
  4. Add slo trend against a baseline window of recent sessions. Alert on regression.
  5. When you are ready to share with an external reviewer, redact and export with bundle export (Phase 4).

A short progression, weeks, not months.

Where this leaves you

After the migration, three things are true.

First, every MCP tool call is in the audit chain. The chain verifies. The evidence verifies.

Second, the policy is explicit and inspectable. Adding a new MCP server is a pack change reviewed in version control.

Third, you can hand any session to a reviewer with a single bundle. The reviewer needs the AGEF format, not your stack.

The repo is at github.com/radotsvetkov/akmon. The format spec is at github.com/radotsvetkov/agef. The site is at radotsvetkov.github.io/akmon. The next article in the series goes deep on replay and divergence detection, which is where MCP-heavy sessions earn their keep most often.

Top comments (0)