Recovers missing tool call arguments from Cursor's ACP (Agent Client Protocol) events by reading the local SQLite store.db.
Cursor's ACP stream emits tool_call notifications with an empty rawInput field — you can see that a tool was called but not what arguments it received (which file was read, which command was run, etc.).
Cursor does write the full tool call data — including arguments — to a local SQLite database. Recent Cursor builds use a flat layout:
~/.cursor/acp-sessions/<sessionId>/store.db
Older sessions still live under the legacy hashed layout, which this package also resolves transparently:
~/.cursor/chats/<hash>/<sessionId>/store.db
This package reads whichever database is present for a given session and recovers the missing arguments, using the toolCallId as the join key between ACP events and store.db blobs.
npm install cursor-acp-enrichedimport { CursorToolEnricher } from 'cursor-acp-enriched';
// sessionId comes from the ACP session notification
const enricher = new CursorToolEnricher(sessionId);
// toolCallId comes from the ACP tool_call event
const result = await enricher.enrich(toolCallId);
if (result) {
console.log(result.toolName); // e.g. "Read"
console.log(result.args); // e.g. { path: "/foo/bar.ts" }
console.log(result.result); // e.g. "export default function main() {}"
console.log(result.richResult); // structured data from Cursor (see below)
}
// With a polling timeout (retries until blob appears or timeout expires)
const result = await enricher.enrich(toolCallId, { timeoutMs: 2000 });
// Limit result length (default is 50 000 characters)
const result = await enricher.enrich(toolCallId, { maxResultLength: 5000 });Creates an enricher for the given Cursor session.
| Option | Type | Default | Description |
|---|---|---|---|
cursorDir |
string |
~/.cursor |
Override the Cursor data directory |
defaultTimeoutMs |
number |
1000 |
Default retry window for enrich() |
Looks up the tool call in store.db. Returns EnrichedToolCall | null.
If the blob isn't found immediately and timeoutMs > 0, retries with exponential backoff (50ms → 100ms → 200ms…) until the timeout is reached. Useful when the blob may not be written yet.
| Option | Type | Default | Description |
|---|---|---|---|
timeoutMs |
number |
Overrides defaultTimeoutMs for this call |
|
maxResultLength |
number |
50_000 |
Truncates the recovered result to this many chars |
Clears the cached store.db path. The next enrich() call will re-discover it.
Low-level helper. Scans ~/.cursor/chats/*/ for a subdirectory matching sessionId and returns the full path to store.db. Throws SessionNotFoundError if not found.
interface EnrichedToolCall {
toolCallId: string;
toolName: string;
args: Record<string, unknown>;
result: string | null; // tool output; null when no tool-result blob was found
richResult: Record<string, unknown> | null; // structured data from highLevelToolCallResult; null when absent
}Cursor's store.db stores a providerOptions.cursor.highLevelToolCallResult field on tool-role blobs containing rich structured data that goes beyond the plain-text result. The enricher extracts this as richResult.
Examples of what each tool provides:
| Tool | richResult contains |
|---|---|
| Grep / Glob | workspaceResults — file paths and matching lines |
| WebSearch | references — URLs, titles, and content chunks |
| StrReplace | diffString — before/after diff |
| Write | diffString — full file diff |
| Task | conversationSteps — subagent conversation activity |
| Shell | exit code and full output (when Cursor includes it) |
richResult is null when the tool blob has no highLevelToolCallResult (e.g. older Cursor versions, or tools that don't provide it).
const result = await enricher.enrich(toolCallId);
if (result?.richResult) {
// Grep example
const workspaceResults = result.richResult.workspaceResults;
// WebSearch example
const references = result.richResult.references;
// StrReplace/Write example
const diff = result.richResult.diffString;
}- Path discovery — scans
~/.cursor/chats/<hash>/<sessionId>/store.dbto find the database for the session - Blob correlation — opens the database read-only in WAL mode, iterates all rows in the
blobstable; finds thetype === "tool-call"entry for the giventoolCallId(for args), the pairedtype === "tool-result"entry (for output), and theproviderOptions.cursor.highLevelToolCallResultfield (for structured rich data) - Retry — if the blob isn't present yet (Cursor may not have flushed it), polls with exponential backoff up to the configured timeout
- macOS and Linux only (no Windows support in v1)
- Depends on Cursor's internal
store.dbschema, which may change across Cursor versions - Read-only: does not write to or modify the database
MIT