# Otter — MCP transcript ingest reference

Companion to [`conversation-archive-mcp`](../../skills/conversation-archive-mcp/SKILL.md). Names the install command, the expected MCP tool surface, the returned shape, and how to map that shape into `memory-ingest` parameters for the conversation-archive writer.

## Install (operator)

Otter ships a remote streamable-HTTP MCP server at `https://mcp.otter.ai/mcp`. Install once per Claude Code host:

```bash
claude mcp add --transport http otter https://mcp.otter.ai/mcp
```

Confirm the current URL against Otter's documentation before relying on this one-liner. First invocation opens an OAuth flow in the operator's browser; the operator authorises which conversations Otter exposes to this Claude Code session. Tokens live in the host, not in maxy.

**Operator-facing verification.** After install, run `/mcp` in admin chat. The expected entry is `otter — connected` with a tool count > 0. If the entry is missing or shows an error, re-run the install one-liner before invoking the ingest skill.

## Expected tool surface

The skill discovers tools by name pattern at run time. Expected tools (names may drift):

- **List conversations.** Pattern: `mcp__otter__list_conversations`, `mcp__otter__search_conversations`, or similar. Accepts a date window and/or text/keyword filter; returns an array of conversation objects with at minimum `id`, `title`, `startTime`, `participants`.
- **Fetch a conversation's transcript.** Pattern: `mcp__otter__get_conversation`, `mcp__otter__read_transcript`, or similar. Accepts a conversation ID; returns the full conversation including the transcript turns.

If the discovered tools diverge, match by description over name. Run `/mcp` to enumerate the live set when in doubt.

## Returned shape (typical)

An Otter conversation object carries the following fields relevant to ingest:

| Otter field | Maps to memory-ingest |
|---|---|
| `id` / `speech_id` | `otterConversationId` (cross-reference property on `:ConversationArchive`) |
| `title` | `archiveTitle` (prefixed with `"otter · "` and suffixed with the ISO date) |
| `startTime` / `start_time` | `lastIngestedMessageAt` fallback when transcript turns lack per-turn timestamps |
| `participants[]` — each with `name`, `email` | Per-attendee resolution against `:AdminUser` / `:Person` (Step 4 of the skill) |
| `transcript[]` / `utterances[]` — each with `speaker`, `start`, `text` | Rendered into turn-attributed `[<ISO>] <speaker>: <text>` lines for the typed-section JSON |
| `calendarEventId` / `meeting_event_id` | `calendarEventId` cross-reference property on `:ConversationArchive` when present (Otter does not always populate it — only for meetings imported from a connected calendar) |
| `summary` / `outline` | NOT used for `:Section` summaries — the in-turn classifier produces them from the transcript |

## Edge cases

- **Speaker labels are pseudonymous** ("Speaker 1", "Speaker 2") when Otter could not identify a participant. Surface them to the operator at attendee-resolution time — the operator either matches "Speaker 1" to a known `:Person` (because they recognise the meeting) or blocks the attendee.
- **Live in-progress conversations** — Otter may expose a conversation that is still being transcribed. Skip these: the skill only ingests finished recordings. Detect by checking the provider's `status` / `is_live` field when present and FAIL with `phase=fetch reason="otter conversation <id> still in progress"`.
- **Attendee with no email** (free-text participant added in Otter): falls through to the per-attendee prompt loop.
- **Highlights / action items** in Otter's UI are provider-authored layers; do not ingest them as separate `:Section` rows. The transcript text is the source of truth; downstream Phase 2 derivation produces typed claims uniformly across sources.

## What this reference is not

- Not an Otter API reference. The MCP surface is the contract.
- Not a token manager. OAuth is in the host.
