How Desktop Commander MCP's Tool History Tracking System Works: A Technical Deep Dive
Desktop Commander's tool history tracking system captures every tool invocation in a capped in-memory buffer and persistent JSONL file, enabling LLMs to retrieve recent context through the get_recent_tool_calls MCP tool.
The tool history tracking system in Desktop Commander MCP serves as a critical context bridge for Large Language Models, capturing execution metadata to help AI assistants recover from interrupted sessions and debug command sequences. Implemented as a singleton in src/utils/toolHistory.ts, this system intercepts each tool call dispatched through src/server.ts, persisting arguments, outputs, and timing data to a local JSONL file while maintaining a fast in-memory index for immediate retrieval.
Core Architecture of the ToolHistory Class
The ToolHistory class operates as a singleton that balances durability with performance, using a dual-layer storage approach that protects against memory bloat while ensuring data survives process restarts.
In-Memory Buffer and Entry Limits
At the heart of the system lies a private array that stores the most recent invocations. The ToolHistory class maintains private history: ToolCallRecord[] = [], capped at 1000 entries via the MAX_ENTRIES constant. When this limit is reached, older entries are discarded to prevent unbounded memory growth, ensuring the MCP server remains responsive during long-running sessions.
Persistent Storage and File Management
Every tool call is appended as a JSON-Lines entry to ~/.claude-server-commander/tool-history.jsonl, created automatically during class construction. To prevent disk exhaustion, the system implements aggressive file-size guards: when the history file exceeds 5 MiB (MAX_HISTORY_FILE_SIZE_BYTES), the trimHistoryFile() method truncates it to 4 MiB, preserving only the newest entries at the end of the file.
Output Truncation and Size Guards
To avoid storing massive binary outputs or log dumps, individual tool results are truncated to 4 KB (MAX_STORED_OUTPUT_BYTES). When outputs exceed this threshold, the system replaces the content with a placeholder message indicating truncation, significantly reducing storage overhead while maintaining forensic utility.
Asynchronous Write Batching
Rather than blocking the request dispatcher with synchronous disk I/O, ToolHistory implements an async write queue flushed by a setInterval timer every second. This batching mechanism accumulates pending writes in memory and persists them in bulk, minimizing filesystem overhead during high-frequency tool invocation sequences.
Integration with the MCP Request Flow
The history system hooks into the request pipeline within src/server.ts, where the dispatcher executes tool handlers and subsequently records the invocation. After a tool handler finishes execution, the server checks against an exclusion list before logging:
if (!EXCLUDED_TOOLS.includes(name)) {
toolHistory.addCall(name, args, result, duration);
}
The EXCLUDED_TOOLS array currently contains get_recent_tool_calls and track_ui_event, preventing recursive history entries when the LLM queries its own recent activity or when telemetry events fire.
Querying Tool History via get_recent_tool_calls
The system exposes history retrieval through a first-class MCP tool defined in src/tools/schemas.ts and implemented in src/handlers/history-handlers.ts. The handleGetRecentToolCalls function parses arguments through GetRecentToolCallsArgsSchema and returns formatted records:
export async function handleGetRecentToolCalls(args: unknown): Promise<ServerResult> {
const parsed = GetRecentToolCallsArgsSchema.parse(args);
const calls = toolHistory.getRecentCallsFormatted({
maxResults: parsed.maxResults,
toolName: parsed.toolName,
since: parsed.since,
});
const stats = toolHistory.getStats();
const summary = `Tool Call History (${calls.length} results, ${stats.totalEntries} total in memory)`;
return { content: [{ type: "text", text: `${summary}\n\n${JSON.stringify(calls, null, 2)}` }] };
}
The getRecentCallsFormatted() method converts ISO timestamps to human-readable local time using formatLocalTimestamp(), returning a JSON-stringified array that includes the tool name, arguments, truncated output, and execution duration in milliseconds.
Lifecycle Management and Cleanup
During graceful shutdown or test teardown, the singleton's cleanup() method stops the background flush interval and ensures any queued writes are persisted to disk. This prevents data loss when the MCP server process terminates, maintaining the integrity of the historical record across restarts.
Summary
- Dual-layer storage: Combines a 1000-entry in-memory buffer with a 5 MiB-capped JSONL file at
~/.claude-server-commander/tool-history.jsonl. - Size protections: Enforces 4 KB output truncation per entry and automatic file trimming to prevent disk exhaustion.
- Async performance: Uses a 1-second batched write queue to avoid blocking the request dispatcher.
- Selective logging: Excludes
get_recent_tool_callsandtrack_ui_eventfrom history to prevent recursion and telemetry pollution. - LLM accessibility: Exposes history through the
get_recent_tool_callstool with local timezone formatting and statistical summaries.
Frequently Asked Questions
Where does Desktop Commander store the tool history file?
The system persists tool history to ~/.claude-server-commander/tool-history.jsonl in the user's home directory. This JSON-Lines file is created automatically when the ToolHistory singleton initializes and is trimmed automatically when it exceeds 5 MiB to maintain storage efficiency.
What is the maximum number of tool calls retained by the system?
The in-memory buffer retains 1000 entries (MAX_ENTRIES), while the persistent file maintains entries up to 5 MiB before automatic truncation. Individual tool outputs are capped at 4 KB to prevent storage of excessively large binary data or log streams.
How does the history tracking system prevent the file from growing indefinitely?
When the JSONL file exceeds 5 MiB (MAX_HISTORY_FILE_SIZE_BYTES), the trimHistoryFile() method removes older entries from the beginning of the file until it reaches a target size of 4 MiB, ensuring only the most recent activity persists while preventing disk space exhaustion.
Which tools are excluded from history tracking and why?
The tools get_recent_tool_calls and track_ui_event are explicitly excluded from logging. The former is excluded to prevent recursive history entries when the LLM queries its own recent activity, while the latter is excluded because it represents pure telemetry rather than substantive command execution that requires forensic tracking.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →