# How Tool History Tracking Works with `get_recent_tool_calls` in DesktopCommanderMCP

> Discover how DesktopCommanderMCP tracks tool history using get_recent_tool_calls. Learn about its ring buffer and JSON-Lines persistence for efficient call retrieval.

- Repository: [Eduard Ruzga/DesktopCommanderMCP](https://github.com/wonderwhy-er/DesktopCommanderMCP)
- Tags: internals
- Published: 2026-07-10

---

**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`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/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 `ServerResult` produced
- 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`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/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`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/server.ts) (line 1542) calls:

```typescript
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.

```typescript
// 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`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/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:

1. Invokes `getRecentCalls` with the provided filters
2. Converts UTC timestamps to local timezone strings via `formatLocalTimestamp` (lines 18-30)
3. Returns an array of `FormattedToolCallRecord` objects ready for JSON serialization

```typescript
// 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`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/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:

1. Parses request parameters (respecting the 1,000 result hard cap)
2. Passes them to `toolHistory.getRecentCallsFormatted()`
3. 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`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/withTimeout.ts) to ensure the server remains responsive.

## Summary

- **DesktopCommanderMCP** tracks tool invocations in a singleton `toolHistory` instance defined in [`src/utils/toolHistory.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/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_calls` endpoint, implemented in [`src/handlers/history-handlers.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/handlers/history-handlers.ts), exposes filtered retrieval via `getRecentCallsFormatted` with support for tool-name filtering, date ranges, and result limiting.
- All tool executions are automatically recorded by [`src/server.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/server.ts) immediately 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.