How Desktop Commander Implements Audit Logging for Tool Calls: A Two-Layer Approach

Desktop Commander implements audit logging for tool calls through a dual-layer system that combines lightweight plain-text rotation logs with structured JSON-Lines history, ensuring both human-readable audit trails and queryable data for every tool invocation.

The wonderwhy-er/DesktopCommanderMCP repository provides a Model Context Protocol (MCP) server that records every tool request through a sophisticated audit logging system. By splitting responsibilities between high-performance text logging and rich structured storage, Desktop Commander maintains comprehensive observability without blocking the main execution loop.

The Two-Layer Audit Architecture

Desktop Commander employs complementary logging strategies to serve different observability needs. The lightweight logging layer captures essential metadata in a rotating plain-text file for quick inspection and external log aggregation, while the rich history layer maintains structured JSON records suitable for UI components and programmatic analysis.

Lightweight Logging with trackToolCall

Log Format and Storage Location

Located in src/utils/trackTools.ts, the trackToolCall(name, args) function appends a single-line entry to claude_tool call.log within the user's home configuration directory at .claude-server-commander. Each entry contains an ISO timestamp, the tool name padded to 20 characters, and a JSON-encoded copy of the arguments.

Automatic Rotation at 10 MiB

To prevent unbounded disk usage, the system monitors file size against TOOL_CALL_FILE_MAX_SIZE defined in src/config.ts. When the log exceeds 10 MiB, the current file is automatically renamed with a timestamp suffix and a fresh log file is initiated. This rotation happens transparently during the write operation.

import { promises as fs } from 'fs';
import path from 'path';
import { TOOL_CALL_FILE } from './config.js';

// Executed automatically when file size exceeds 10 MiB
await fs.rename(
  TOOL_CALL_FILE,
  `${path.basename(TOOL_CALL_FILE, '.log')}_${new Date().toISOString().replace(/[:.]/g, '-')}.log`
);

Rich History Storage

JSON-Lines Structure and Content

The src/utils/toolHistory.ts module maintains tools-history.jsonl, storing structured records via toolHistory.addCall(). Each entry preserves the timestamp, tool name, arguments, server response, and optional execution duration. This format enables complex querying and analytics while remaining line-parseable.

Memory Capping and Async Batching

The history implementation caps the in-memory list to the latest 1,000 entries to prevent memory leaks. Writes to disk occur asynchronously in batches once per second, ensuring the main event loop remains unblocked during high-volume tool execution. When the on-disk file exceeds 5 MiB, the system trims older entries while preserving the most recent history.

import { trackToolCall } from './utils/trackTools.js';
import { toolHistory } from './utils/toolHistory.js';

async function handleToolRequest(name: string, args: unknown) {
  const startTime = Date.now();
  
  // Lightweight audit logging to plain-text file
  trackToolCall(name, args);
  
  // Execute the tool operation
  const result = await runTool(name, args);
  
  // Rich structured history with duration tracking
  toolHistory.addCall(name, args, result, Date.now() - startTime);
}

Server Integration and Configuration

Incoming JSON-RPC requests hit the handler in src/server.ts, which immediately invokes trackToolCall(name, args) for every tool invocation. The centralized configuration in src/config.ts ensures consistent file paths and size limits across both logging layers, while src/utils/logger.ts provides the underlying transport infrastructure used by other system components.

Querying the Audit Trail

Developers can retrieve formatted history entries for UI components or debugging interfaces:

// Retrieve recent calls with formatting for display
const recent = toolHistory.getRecentCallsFormatted({ maxResults: 20 });
console.log(recent);

Summary

  • Desktop Commander uses two complementary layers: lightweight text logs in src/utils/trackTools.ts and structured JSON-Lines history in src/utils/toolHistory.ts.
  • Plain-text logs rotate automatically at 10 MiB (TOOL_CALL_FILE_MAX_SIZE in src/config.ts) to prevent disk overflow.
  • Structured history retains the latest 1,000 entries in memory and trims the on-disk file at 5 MiB, writing asynchronously in one-second batches.
  • Every tool invocation flows through src/server.ts, which triggers trackToolCall() for immediate audit capture.
  • Both layers store data in the .claude-server-commander directory under the user's home folder.

Frequently Asked Questions

Where are the audit log files stored?

Desktop Commander stores audit data in the .claude-server-commander directory within the user's home folder. The lightweight log writes to claude_tool call.log, while the structured history maintains tools-history.jsonl in the same location.

What triggers the rotation of audit log files?

The plain-text audit log rotates when it exceeds 10 MiB, as defined by the TOOL_CALL_FILE_MAX_SIZE constant in src/config.ts. The system renames the current file with a timestamp suffix and creates a fresh log automatically.

How does Desktop Commander prevent audit logging from consuming excessive memory?

The src/utils/toolHistory.ts implementation caps the in-memory history to 1,000 entries and writes to disk asynchronously in batches every second. Additionally, the on-disk JSON-Lines file is trimmed when it exceeds 5 MiB, retaining only the most recent tool calls.

Can I query the audit logs programmatically?

Yes, the src/utils/toolHistory.ts module provides getRecentCallsFormatted() to retrieve structured data for UI components or analytics. The function accepts parameters like maxResults to limit the returned entries, making it suitable for building monitoring dashboards or debugging interfaces.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →