---
title: MCP server
description: 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 shell-less clients (Claude Desktop, ChatGPT connectors), for which MCP is the integration path.

| | |
| --- | --- |
| Server name | `zanei` |
| Start command | `zanei mcp` |
| Transport | stdio only (JSON-RPC 2.0 over stdin/stdout) — **zero network surface** |
| Capabilities | `tools` only; `resources` / `prompts` are not provided yet (see [future](#future-extensions)) |

**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](/guides/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](/reference/cli#shared-conventions) and [event reference](/reference/events).

## 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`. 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; content is coarsened to fit |
| `granularity` | `"coarse"` or `"fine"` | `"coarse"` | Session-level or per-interaction |

**Output** (`format: "markdown"`)

```json
{
  "range": { "since": "2026-08-16T08:00:00Z", "until": "2026-08-16T09:00:00Z" },
  "format": "markdown",
  "content": "## 08:12-08:31 Safari — Reviewing PR #42\n- Viewed 3 files on GitHub\n- Wrote 2 comments\n...",
  "token_estimate": 3810,
  "truncated": false
}
```

With `format: "structured"`, the result matches the `sessions[]` structure of `timeline --format json` in the [CLI reference](/reference/cli#timeline).

### `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](/reference/events); wildcards like `browser.*` allowed |
| `app` | string | — | Filter by app name |
| `bundle_id` | string | — | Filter by bundle ID |
| `limit` | integer | `200` (max `1000`) | Max events |

**Output**

```json
{
  "range": { "since": "2026-08-16T08:45:00Z", "until": "2026-08-16T09:00:00Z" },
  "count": 42,
  "truncated": false,
  "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" },
      "redaction": { "applied": false, "rules": [] }
    }
  ]
}
```

`truncated: true` when `limit` was exceeded. Event contents are privacy-processed at capture time (secure-field exclusion, redaction); the MCP layer does not add back anything that was excluded.

### `get_status`

Lightweight check for agents: is recording running? A subset of `zanei status`.

**Input**: none (`{}`)

**Output**

```json
{
  "running": true,
  "paused": false,
  "last_event_ts": "2026-08-16T08:59:58Z",
  "events_dropped": 2,
  "degraded": { "eventtap": "1 degraded collector operation observed" },
  "retention_hours": 48,
  "oldest_event_ts": "2026-08-14T09:00:00Z",
  "capture": { "sources": ["app", "window", "ui", "input", "browser"], "text_content": false },
  "permissions_ok": true
}
```

| Field | Meaning |
| --- | --- |
| `events_dropped` | Cumulative events dropped by collectors or the recording pipeline |
| `degraded` | Collector or runtime degradation by component; empty when no degradation is known |
| `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` |
| Invalid arguments (bad time expression, ...) | JSON-RPC error (invalid params) |

The MCP server does not start recording or grant permissions. Permission problems are resolved through the CLI: `zanei doctor` (the shipped skill says so).

## Registration

`zanei setup --agent <name>` automates all of the below — see [agent setup](/agents/setup) for per-client walkthroughs. The generic stdio-server JSON shape:

```json
{
  "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 surface).
- 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; with the default `text_content: false`, typed content simply does not exist in the store.
