Skip to content

Reading sessions from an assistant

Updated View as Markdown

ambient mcp turns the binary into a Model Context Protocol server so another program — Claude Code, or anything else that speaks MCP — can read your sessions as data instead of scraping transcript.md. It speaks newline-delimited JSON-RPC 2.0 over stdio: no port, no token, no daemon. The client starts the process, and the process exits when the client closes its stdin.

It exposes three session tools and none of them writes. There is no verb here to start a recording, name a speaker or delete a session; a reader can read, and that is the whole surface for sessions. Ambient supplies the ears and nothing else: no lookups, no summaries. The assistant on the other end brings its own.

The same server also carries the live assistant: four tools that let an agent watch the meeting being recorded and speak in it, and a watch prompt that tells it how. Those tools do nothing while the assistant is turned off; see that page for what they write and say.

Access and privacy

The MCP server can read sessions available to the Ambient process. It does not prompt before each read, and read-only access still exposes conversation text. Only register it with a client you trust. An assistant may send tool results to its cloud provider under that client’s settings; local transcription does not make that later sharing local.

To limit access, start the server with AMBIENT_HOME pointing at a separate folder containing only sessions you intend to share. This is a directory scope, not per-session authorization. The server does not authenticate a client over stdio; the process that launches it controls its access.

Registering it

With the binary on your PATH:

claude mcp add ambient -- "$(which ambient)" mcp

The signed bundle is the same binary, so if you launch Ambient as an app and never installed a copy on PATH, point at the executable inside it:

claude mcp add ambient -- /Applications/Ambient.app/Contents/MacOS/ambient mcp

Either path works: the MCP verb reads files and needs no microphone, so it does not care which of the two started it. Only tap and record do — see launching the verbs that need system audio.

Other clients have their own configuration location and schema. For clients that accept an mcpServers object, the example below shows the command and arguments. Give the command an absolute path: a stdio server is spawned directly rather than through a shell, so ~ and $(which …) arrive as literal characters.

{"mcpServers":{"ambient":{"command":"/path/to/ambient","args":["mcp"]}}}

To check the registration without a client, pipe a request in yourself. The server answers one line per request:

printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | ambient mcp | jq -c '[.result.tools[].name]'
["sessions","transcript","status","watch_meeting","wait_for_transcript","speak","stop_watching"]

Without jq, the answer is one JSON-RPC line holding each tool’s name, description and input schema.

The three session tools

Every tool answers in the MCP shape — {"content":[{"type":"text","text":"…"}],"isError":false} — where the text is the tool’s JSON, serialised compactly on one line. The examples below show that text, indented so it can be read. A tool that ran and failed answers isError: true with the reason as plain text, so the assistant sees it rather than the transport swallowing it:

{"content":[{"text":"no session nope","type":"text"}],"isError":true}

status

No arguments. What Ambient is doing this second — cheap enough to call on every turn.

{"awaiting_transcript":0,"live":{"id":"2026-09-05T1608"},"sessions":2}

live is null when nothing is recording. awaiting_transcript counts sessions whose audio is captured but not yet transcribed, and sessions is how many sessions will list.

sessions

No arguments. Every session on this machine, newest first — the same rows ambient sessions --json prints.

[
  {
    "dir": "/Users/…/Documents/Ambient/2026-09-05T1608",
    "duration_s": null,
    "error": null,
    "id": "2026-09-05T1608",
    "live": true,
    "name": null,
    "started_at": null,
    "transcribed": false,
    "transcribing": false
  },
  {
    "dir": "/Users/…/Documents/Ambient/2026-09-05T1412",
    "duration_s": 1184.0,
    "error": null,
    "id": "2026-09-05T1412",
    "live": false,
    "name": "Roadmap sync",
    "started_at": "2026-09-05T14:12:03Z",
    "transcribed": true,
    "transcribing": false
  }
]

That first row is the session being recorded, and its name, started_at and duration_s are null because session.json is written when capture ends — a live row carries its id and live: true and nothing more. A session whose session.json will not parse comes back with error set rather than being hidden. Symlinks are skipped: only real directories under the sessions folder are listed.

transcript

session (required, an id from sessions), since (default 0) and verbatim (default false, which folds naming edits over what the recogniser heard).

{
  "lines": [
    {"end_ms": 3120, "speaker": null, "start_ms": 0, "text": "Right, shall we start with the migration?", "track": "room"},
    {"end_ms": 11040, "speaker": null, "start_ms": 8200, "text": "Did the backfill finish?", "track": "room"},
    {"end_ms": 7980, "speaker": "Priya", "start_ms": 3400, "text": "Yes. I pushed the schema change on Friday.", "track": "call"}
  ],
  "next": 3,
  "session": "2026-09-05T1412",
  "state": "done"
}

track is room for the microphone and call for what the tap heard, and speaker is null until something has named it — the same fields ambient show --json prints. The lines arrive in the order they were written. Room and call lines can interleave, and a later arrival can have an earlier start_ms; sort on start_ms if you want the clock.

Following a session as it is recorded

next is a cursor: the number of lines the session holds. Pass it back as since and you get only what has appeared since, so a poll loop is one call with the previous reply’s next.

{"lines":[],"next":3,"session":"2026-09-05T1412","state":"done"}

state says whether asking again is worth it:

state What it means Ask again?
live Audio is being captured right now. Yes
transcribing Capture finished; remaining text or speaker labels are being processed. Yes
pending Captured, waiting its turn in the queue. Yes
done transcript.md is written. next will not move. No

The cursor counts lines rather than milliseconds because the two tracks’ clocks run independently. A cursor on start_ms would skip a call line that arrived after a room line with a later timestamp. The published line order stays stable when recording stops and finalization completes.

During recording, Ambient processes audio in roughly 10-second blocks and appends completed speech turns to raw.jsonl. A live reply can therefore contain new lines. An empty reply means no new completed turn is available yet:

{"lines":[],"next":0,"session":"2026-09-05T1608","state":"live"}

This is completed-turn transcription, not word-by-word streaming. Model loading, ongoing speech and other transcription work can delay the first lines. Continue polling through Stop: the final speech tail and speaker separation finish afterward. To refresh speaker labels on earlier lines, read again with since: 0 once the session is done.

When it will not answer

Nothing but JSON-RPC ever goes to stdout; diagnostics go to stderr, so a client that shows you the server’s log is showing you stderr. A malformed line gets a JSON-RPC error back and the loop keeps going — the server does not exit because a client sent it rubbish.

A session argument is one path segment, and it must name a real directory inside the sessions folder. ../something, an empty string, . and a symlink are each refused, and no file outside the folder is ever read. If a tool answers refusing symlink …, something inside the session directory is a link rather than a file; Ambient will not follow it.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close