How Tool History Tracking Works with `get_recent_tool_calls` in DesktopCommanderMCP
DesktopCommanderMCP records every tool invocation in a singleton toolHistory class that maintains an in-memory ring buffer of the last 1,000 calls and persists them to an append-only JSON-Lines file, exposing filtered retrieval via the get_recent_tool_calls endpoint.
DesktopCommanderMCP is a Model Context Protocol (MCP) server that exposes desktop automation capabilities to AI assistants. Understanding how it tracks and retrieves tool invocation history is essential for debugging automation workflows and monitoring system usage. The get_recent_tool_calls functionality is implemented through a coordinated system spanning the core history tracker, the main server loop, and dedicated HTTP handlers.
The Core Architecture of Tool History Tracking
At the heart of the system lies the toolHistory singleton defined in src/utils/toolHistory.ts. This module exports a single instance that manages the entire lifecycle of tool call records, from initial capture to persistent storage and filtered retrieval.
The Singleton Pattern and Data Structure
The toolHistory class maintains an in-memory array this.history that stores ToolCallRecord objects. Each record contains:
- A UTC ISO timestamp (
new Date().toISOString()) - The tool name
- The arguments passed to the tool
- The
ServerResultproduced - An optional execution duration in milliseconds
The in-memory buffer is strictly capped at 1,000 entries (lines 33-38 in src/utils/toolHistory.ts). When this limit is reached, older records are automatically purged to maintain constant memory usage.
Persistent Storage with JSON-Lines
For durability, records are queued in this.writeQueue and flushed to an append-only JSON-Lines file (tool-history.jsonl) located in ~/.claude-server-commander/ (lines 54-60). A background interval writes queued records every second (lines 75-81), ensuring minimal I/O overhead while preventing data loss. To prevent unbounded disk growth, the system automatically trims the history file if it exceeds 5 MiB (lines 112-154).
Recording Tool Invocations
When any tool finishes executing, src/server.ts (line 1542) calls:
toolHistory.addCall(name, args, result, duration);
This method constructs a ToolCallRecord and appends it to the in-memory history. The call is non-blocking for the main execution flow since persistence happens asynchronously via the write queue.
// Internal recording (handled automatically by the server)
toolHistory.addCall(
'search', // tool name
{ query: 'opencode' }, // arguments object
{ success: true, data: [] }, // ServerResult
123 // duration in ms (optional)
);
Querying History with get_recent_tool_calls
The public API exposed through src/handlers/history-handlers.ts provides filtered access to this history via two main methods:
getRecentCalls (Raw Filtering)
The base method getRecentCalls filters the in-memory array according to:
maxResults: Defaults to 50, hard-capped at 1,000 (line 64)toolName: Optional exact-match string filter (lines 52-55)since: Optional ISO date string to retrieve calls after a specific time (lines 57-61)
getRecentCallsFormatted (API Response)
The HTTP handler utilizes getRecentCallsFormatted (lines 71-83), which:
- Invokes
getRecentCallswith the provided filters - Converts UTC timestamps to local timezone strings via
formatLocalTimestamp(lines 18-30) - Returns an array of
FormattedToolCallRecordobjects ready for JSON serialization
// Example: Retrieve the latest 20 search-tool calls from the past hour
const recentSearches = toolHistory.getRecentCallsFormatted({
maxResults: 20,
toolName: 'search',
since: new Date(Date.now() - 60 * 60 * 1000).toISOString()
});
// Result structure:
[
{
timestamp: '2026/07/10 14:23:45', // local time format
toolName: 'search',
arguments: { query: 'opencode' },
output: { success: true, data: [] },
duration: 123
}
]
Integration with History Handlers
The src/handlers/history-handlers.ts file bridges the internal toolHistory API with external HTTP requests. When a client invokes the get_recent_tool_calls tool, the handler:
- Parses request parameters (respecting the 1,000 result hard cap)
- Passes them to
toolHistory.getRecentCallsFormatted() - Returns the formatted records as the JSON response
For long-running history queries that might exceed typical timeout windows, the handlers utilize src/utils/withTimeout.ts to ensure the server remains responsive.
Summary
- DesktopCommanderMCP tracks tool invocations in a singleton
toolHistoryinstance defined insrc/utils/toolHistory.ts. - The system maintains an in-memory ring buffer of 1,000 recent calls while asynchronously persisting to
~/.claude-server-commander/tool-history.jsonl. - Automatic trimming ensures the persistent file never exceeds 5 MiB, preventing disk space exhaustion.
- The
get_recent_tool_callsendpoint, implemented insrc/handlers/history-handlers.ts, exposes filtered retrieval viagetRecentCallsFormattedwith support for tool-name filtering, date ranges, and result limiting. - All tool executions are automatically recorded by
src/server.tsimmediately after completion.
Frequently Asked Questions
How is tool history data physically stored on disk?
DesktopCommanderMCP persists tool history to an append-only JSON-Lines file (tool-history.jsonl) in the ~/.claude-server-commander/ directory. Each line represents a single ToolCallRecord as a JSON object. This append-only structure enables efficient writes without rewriting the entire file, while a background maintenance task automatically truncates the file if it grows beyond 5 MiB.
What is the maximum number of tool calls that can be retrieved in a single query?
The maxResults parameter defaults to 50 but can be increased up to a hard limit of 1,000 entries per query. This limit matches the in-memory buffer size, ensuring that even unlimited queries cannot exhaust system resources. Attempts to request more than 1,000 results are silently capped to this maximum.
Can I filter tool history by specific tool names or time periods?
Yes. The get_recent_tool_calls endpoint accepts two optional filters: toolName for exact-match filtering on the tool identifier, and since for retrieving only records created after a specific ISO 8601 timestamp. These filters are applied to the in-memory history before formatting, ensuring fast response times regardless of the persistent file size.
How does the system prevent history recording from slowing down tool execution?
Recording operates on a fire-and-forget model. The addCall method appends the record to an in-memory array immediately (constant time), then queues the write operation for asynchronous flushing. A background interval processes the write queue every second, batching disk operations to minimize I/O overhead. This ensures tool execution latency is not impacted by filesystem write speeds.
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 →