MCP server
The read-only MCP (Model Context Protocol) server exposed by zanei mcp — tools, schemas, errors, and registration.
zanei mcp exposes recorded activity to MCP clients: terminal agents (Claude Code, Codex, opencode, Hermes Agent) and chat clients that cannot run the CLI (Claude Desktop), for which MCP is the integration path.
ChatGPT is not one of them. Its connectors accept only remote HTTPS MCP servers, while zanei mcp is a local stdio process that opens no network connection of its own. Bridging it to a public URL would put the store on the network, so use Codex for OpenAI agents. What an agent does with the results it reads is a separate question — see security boundary.
| Server name | zanei |
| Server version | serverInfo.version in the initialize result is the package version used to build Zanei, matching the version number shown by zanei --version |
| Start command | zanei mcp |
| Transport | stdio only (JSON-RPC 2.0 over stdin/stdout); no network access |
| Capabilities | tools only; resources / prompts are not provided yet (see future) |
Role and boundary. The server is a read-only view over the local store. It runs as an independent process from the recording daemon and cannot start or stop recording, and cannot change configuration, including the capture-time filters. The app / bundle_id arguments of query_events are query-time narrowing over already-stored data, not a privacy boundary.
Shared conventions (time expressions, event types, the event envelope) match the CLI reference and event reference.
Tools
All three tools are read-only and side-effect free.
get_timeline
Returns the LLM-ready timeline for a range. Equivalent to zanei timeline, and the tool agents call most.
Input
| Parameter | Type | Default | Description |
|---|---|---|---|
since |
string | "1h" |
Start of range; relative (15m, 2h, 1d) or RFC3339 |
until |
string | now |
End of range |
format |
"markdown" or "structured" |
"markdown" |
Output shape |
token_budget |
integer | 4000 |
Approximate token cap (minimum 34); content is coarsened to fit |
granularity |
"coarse" or "fine" |
"coarse" |
Session-level or per-interaction |
Output (format: "markdown"). This is an abridged example; the token estimate depends on the complete generated content.
{
"range": { "since": "2026-08-16T08:00:00.000Z", "until": "2026-08-16T09:00:00.000Z" },
"format": "markdown",
"content": "# Zanei timeline\n\nRange: 2026-08-16T08:00:00.000Z — 2026-08-16T09:00:00.000Z\nEstimated tokens: 3810\nTruncated: no\n\n## 2026-08-16T08:12:00.000Z — 2026-08-16T08:31:00.000Z · Safari\n\nTitle: Reviewing PR #42\n- Browsed 3 pages on github.com\n- Edited text in \"Reviewing PR #42\"\n...",
"token_estimate": 3810,
"truncated": false
}
With format: "structured", the result matches the sessions[] structure of timeline --format json in the CLI reference. The result includes skipped_unknown_types. Every session includes content_snapshots, including 0, at most 100 event_ids, and always reports event_ids_truncated; if the token budget still cannot be met, all IDs are omitted before sessions are removed. Markdown adds Content snapshots: N after a session’s activity only when the count is nonzero. Snapshot bodies are never inlined.
query_events
Returns raw events matching the given conditions. Equivalent to zanei query. For fine-grained verification or extracting specific apps/types.
Input
| Parameter | Type | Default | Description |
|---|---|---|---|
since |
string | "15m" |
Start of range |
until |
string | now |
End of range |
types |
string[] | [] |
Event types; wildcards like browser.* allowed. An empty array returns normal activity but excludes content.*; request content.snapshot or content.* explicitly |
app |
string | — | Filter by app name |
bundle_id |
string | — | Filter by bundle ID |
limit |
integer | 200 |
Max events; valid range is 1 through 1000 |
Output
{
"range": { "since": "2026-08-16T08:45:00Z", "until": "2026-08-16T09:00:00Z" },
"count": 42,
"truncated": false,
"skipped_unknown_types": 0,
"events": [
{
"v": 1, "id": "evt_01J...", "ts": "2026-08-16T08:46:12.120Z", "mono_ns": 128374651234,
"source": "macos.applescript", "type": "browser.navigate",
"app": { "name": "Google Chrome", "bundle_id": "com.google.Chrome", "pid": 501 },
"window": { "title": "PR #42", "id": 42 },
"element": null,
"data": { "url": "https://github.com/...", "tab_title": "PR #42", "mode": "normal", "transition": "navigate" },
"truncated": false,
"redaction": { "applied": false, "rules": [] }
}
]
}
truncated: true when limit was exceeded. skipped_unknown_types counts stored rows skipped because this binary does not recognize their type. Event contents are privacy-processed at capture time (secure-field exclusion, redaction); the MCP layer does not add back anything that was excluded.
Content snapshots can be as large as 32 KiB and may contain messages or documents written by other people. Request them only with an explicit type, a narrow since/until range, and a small limit. Do not send their bodies outside the current task unless the user explicitly asks.
get_status
Lightweight check for agents: is recording running? A subset of zanei status.
Input: none ({})
Output
{
"running": true,
"paused": false,
"last_event_ts": "2026-08-16T08:59:58Z",
"events_dropped": 2,
"degraded": {},
"collector_failures": { "eventtap": 1 },
"retention_hours": 48,
"oldest_event_ts": "2026-08-14T09:00:00Z",
"capture": { "sources": ["app", "window", "ui", "input", "browser"], "text_content": false, "content_snapshot": false },
"permissions_ok": true
}
| Field | Meaning |
|---|---|
events_dropped |
Cumulative events intended for recording but actually lost to backpressure, a full queue, disconnection, or a similar delivery failure; inputs outside the recording scope because they cannot be attributed to an app or window, and unflushed data lost to a crash or SIGKILL, are not included |
degraded |
Current collector or runtime degradation by component; removed when the responsible site recovers. AX reasons classify the current failure by stable phase and kind, include the native operation and numeric code when available, and report the number of unresolved PID/operation sites when greater than one. A success clears only its matching site |
collector_failures |
Cumulative collector operation failures by collector; a nonzero value alone does not mean the collector is currently degraded |
capture.content_snapshot |
Whether frontmost-window content snapshots are enabled. Snapshots exist only when this is true; scope details are available through CLI zanei filter show |
permissions_ok |
The running recorder’s own permission result while its heartbeat is fresh; a local probe when no recorder is running |
An agent that sees running: false can tell the user that activity is not currently being recorded, instead of answering from nothing.
Errors
| Situation | Behavior |
|---|---|
| Store not initialized / nothing recorded yet | get_status returns running: false (not an error); get_timeline / query_events return empty results |
| Empty because of missing permissions | Empty results; distinguishable via permissions_ok: false in get_status |
| Encrypted store whose key cannot be obtained (missing, mismatched, login Keychain locked, or access denied) | JSON-RPC -32603 (Internal error) with a store is locked: ... message, for all three tools; never reported as nothing recorded. A missing store file still follows the first row |
Invalid tool parameters, such as a malformed time or range, an invalid event-type pattern, limit outside 1 through 1000, or token_budget below 34 |
JSON-RPC -32602 (Invalid params) |
| Internal processing failure, such as a configuration, store, permission-check, timeline-build, or serialization failure | JSON-RPC -32603 (Internal error) |
The MCP server does not start recording or grant permissions. Use the CLI’s start-first setup: zanei start, grant the requested permissions, zanei stop && zanei start, then verify with zanei doctor.
Registration
zanei setup --agent <name> automates all of the below; see agent setup for per-client walkthroughs. The generic stdio-server JSON shape:
{
"mcpServers": {
"zanei": {
"command": "zanei",
"args": ["mcp"]
}
}
}
Future extensions
Not provided today; all would sit on the same read-only store view, and write access to the recording daemon is out of scope:
- resources — the recent timeline and current configuration as read-only MCP resources (some clients prefer resources over tools).
- prompts — a server-provided “resume my work” prompt template.
- notifications — push on new-session detection, if demand appears.
Security boundary
- The server itself performs no external transmission (stdio only, no network access).
- Once an agent receives tool results, forwarding them to an LLM provider is the agent’s responsibility. Given the sensitivity of activity history, the shipped skill files instruct agents to narrow the requested range before sending.
- Stored content is privacy-processed at capture time. Typed content and content snapshots are separate opt-ins and both default to off.