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)" mcpThe 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 mcpEither 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.