# whosaid (pypi · whosaid)

Local speaker-attributed transcription and meeting search for Apple Silicon. Audio stays on the Mac.

- Trust score: 64/100 (medium)
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-10-23

## Components

- pypi · `whosaid`: 64/100 (this document), [markdown](https://verifymcp.io/servers/sblattj-whosaid/whosaid.md), [page](https://verifymcp.io/servers/sblattj-whosaid/whosaid)

## Channel facts

- Registry: `pypi`
- Package: `whosaid`
- Version: `1.15.1`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-10-23.

- **Supply Chain Security**: 50/100
  - Malware scan not yet available for this package.
  - No known CVEs affecting this package version or its production dependencies.
  - Runs hatchling.build at install time, a recognised build step with no custom scripting around it.
  - 2 of 72 dependencies flagged as unhealthy.
- **Provenance & Transparency**: 97/100
  - Source repository is publicly reachable at the declared URL.
  - Cryptographically verified build provenance (signed, bound to sblattj/whosaid).
  - Clear OSI-approved license (MIT).
  - Actively maintained (last published 1 days ago).
  - Disclosure check failed: no security disclosure policy was found in the source repository.
- **Schema Quality & AI Usability**: 72/100
  - 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).
  - AI-judged instruction clarity (good).
  - Context-footprint check failed: tool/resource definitions use about 4432 tokens (~211/item across 21 items; 17 tools + 4 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 10/100
  - Stability observed for 3 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 67/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 0% of tool parameters carry a description.
- **Tool Safety**: 100/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - We read all 17 captured tool definition(s), and no name or description among them implies an irreversible operation.
  - An AI judge read all 19 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 100/100
  - Implements a current MCP spec version (2026-07-28).

## Install

### How do I install the whosaid MCP server?

whosaid runs locally as a PyPI package, launched with uvx whosaid. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add sblattj-whosaid -- uvx whosaid
```

### Cursor

```json
{
  "mcpServers": {
    "sblattj-whosaid": {
      "command": "uvx",
      "args": [
        "whosaid"
      ]
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "sblattj-whosaid": {
      "command": "uvx",
      "args": [
        "whosaid"
      ]
    }
  }
}
```

### Codex

```bash
codex mcp add sblattj-whosaid -- uvx whosaid
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "sblattj-whosaid": {
      "type": "local",
      "command": [
        "uvx",
        "whosaid"
      ],
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add sblattj-whosaid --command uvx --arg whosaid
```

### Hermes

```yaml
mcp_servers:
  sblattj-whosaid:
    command: "uvx"
    args: ["whosaid"]
```

### Netclaw

```json
{
  "McpServers": {
    "sblattj-whosaid": {
      "Transport": "stdio",
      "Command": "uvx",
      "Arguments": [
        "whosaid"
      ]
    }
  }
}
```

### Vellum

```bash
assistant mcp add sblattj-whosaid -t stdio -c uvx -a whosaid
```

### Other

```json
{
  "mcpServers": {
    "sblattj-whosaid": {
      "command": "uvx",
      "args": [
        "whosaid"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-10-22 (score 64, +1)

- [functional improvement] Stability: unverified → 0.07
- [functional] Package version: 1.14.0 → 1.15.0

### 2026-10-20 (score 63)

First indexed and scored.

## MCP tools (17)

### `whosaid_transcribe` (~354 tokens)

Transcribe an audio file and label each turn with WHO said it — 100% locally on the Apple-Silicon GPU (no cloud, no API keys). Writes <base>.txt, <base>.speakers.txt (speaker-labeled), <base>.speaker-cards.txt and a <base>.diarization.json sidecar next to the input (or `outdir`). The FIRST run downloads ~1.5 GB of models. Speakers you haven't named come back as SPEAKER_00/01…; the result includes the speaker cards — read them, then call whosaid_relabel to name them (names then auto-apply to future transcripts). If you already know who was in the room, pass `expected_speakers` (names of voices you have enrolled) — clustering is then anchored to their voiceprints, absentees are dropped, and nobody has to guess a count. Any speaker role tags (self/boss/…) come back under `roles`. If diarization fails the result is ok=false, partial=true with `diarization_error`; the plain transcript is still written. Keywords: transcribe, diarize, speaker diarization, who spoke, meeting notes, call recording, whisper, voice attribution, subtitles, srt, vtt.

Input parameters:

- `accuracy` (string)
- `anchor_threshold`
- `audio` (string, required)
- `diarize` (boolean)
- `expected_speakers`
- `format` (string)
- `lang` (string)
- `match_threshold`
- `max_speakers`
- `min_speakers`
- `outdir`
- `speakers`

### `whosaid_relabel` (~445 tokens)

Put real names on SPEAKER_NN clusters and REMEMBER them: each name is saved to your local speaker registry and auto-applied to EVERY future transcript. Reads the cached <base>.diarization.json sidecar and rewrites <base>.speakers.txt + <base>.speaker-cards.txt in place — no re-transcription, no re-diarization. Run whosaid_transcribe first, read its speaker cards to tell who is who, then map clusters to names. Pass auto=true (with an empty assignments map) to instead re-apply registry matching + the absorb pass over the cached sidecar, which folds phantom cluster splits into their real speaker. With auto=true, pass fold_unknown=true to repair anonymous phantom clusters in a cached sidecar. auto=true also matches the enrollment clips in voices/ and drops auto-assigned names whose registry entry and clip are gone; pass forget=['Name'] (with auto=true) to clear a name from this meeting's clusters first (it can come back if still enrolled and matching). Optionally pass `roles` ({Name: role} — self/boss/peer/report/external) to role-tag speakers in the registry, cards, and transcripts. Pass no_save=true for a transcript-only label: sidecar + outputs are renamed but the registry is untouched (composes with auto=true); note adds a provenance string stored with each local label; force=true overrides the guard that refuses replacing a registry print whose similarity to the cluster is below match_threshold; blend=true averages the cluster into the existing registry print instead of replacing it (same guard), to strengthen a voiceprint from a new recording. Keywords: rename speaker, label speaker, assign name, identify voice, who is SPEAKER_00, correct labels.

Input parameters:

- `assignments` (object, required)
- `auto` (boolean)
- `base` (string, required)
- `blend` (boolean)
- `fold_unknown` (boolean)
- `force` (boolean)
- `forget`
- `match_threshold`
- `no_save` (boolean)
- `note`
- `outdir`
- `roles`

### `whosaid_list_speakers` (~109 tokens)

List the voices whosaid can already auto-name: enrolled clips in `voices/` plus voiceprints saved in the local speaker registry (~/.config/whosaid/speakers.json). Read-only. Call this before whosaid_relabel so you reuse an existing name instead of creating a duplicate. Each registry entry carries its role tag (self/boss/…) when set. Keywords: known speakers, enrolled voices, registry, recognized voices, who can it identify.

### `whosaid_doctor` (~92 tokens)

Read-only readiness check: Apple-Silicon/arch + macOS, ffmpeg and uv, Whisper and diarization model-cache state, enrolled voices, and the mic/audio device list. Run this FIRST when a transcribe fails — each missing prerequisite prints the exact fix (usually `whosaid setup`). Keywords: health check, prerequisites, diagnose, models cached, setup status, is it ready.

### `whosaid_enroll_from_file` (~146 tokens)

Enroll a named voice from an EXISTING audio clip (≥15 s of one person speaking, reasonably clean) so future transcripts auto-label them — converts and writes voices/<Name>.wav. This does NOT record from the mic: for mic enrollment use the interactive `whosaid enroll` CLI (it needs a terminal and Microphone permission). Fails clearly if the clip is too short or silent. Keywords: enroll voice, add speaker, register voice, voiceprint, teach a new voice from a file.

Input parameters:

- `audio` (string, required)
- `name` (string, required)
- `ss`
- `t`
- `to`

### `whosaid_samples` (~213 tokens)

Export ONE short representative WAV clip per SPEAKER_NN cluster, cut from the ORIGINAL recording, so a human can quickly LISTEN and confirm an identity before trusting an auto-label or enrolling that voice — replaces cutting clips by hand with ffmpeg. Picks each cluster's longest diarized segment, clamped to `seconds` (default 8), and writes <base>.samples/<SPEAKER_NN>[-<Name>].wav (mono 16kHz). Needs the sidecar's 'source' metadata (present since GitHub issue #1 parts 2-3); an older sidecar without it must be re-transcribed, or use the `whosaid samples --audio FILE` CLI flag directly. Keywords: sample clip, listen to speaker, verify identity, confirm voice, spot check, per-speaker snippet, sanity check a label.

Input parameters:

- `base` (string, required)
- `outdir`
- `per_speaker` (integer)
- `seconds` (number)

### `whosaid_search` (~225 tokens)

Search every speaker-labeled transcript in a meeting workspace and get back the matching TURNS (meeting folder, timestamp, speaker, text, score), not whole files. mode: exact (SQLite FTS5 full text; quoted phrases and prefix* work), meaning (local Ollama embeddings; falls back to exact when the index has none), hybrid (both, the default). Filter by `speaker` label or `meeting` folder; `k` caps the hits (1-100). Then call whosaid_context on a hit to read the verbatim minute around it. Read-only: the index is built by `whosaid index <ws>` from the CLI, never here. Workspace = the `workspace` argument or WHOSAID_WORKSPACE. Keywords: search transcripts, who said, find a quote, when did we discuss, meeting search, full text, semantic search.

Input parameters:

- `k` (integer)
- `meeting`
- `mode` (string)
- `query` (string, required)
- `speaker`
- `workspace`

### `whosaid_context` (~159 tokens)

Read the verbatim transcript turns around ONE moment of ONE meeting: `meeting` is the workspace folder name from a search hit, `at` its timestamp (HH:MM:SS or MM:SS), `before`/`after` the window in seconds (default 60 s before, 120 s after). Call this after whosaid_search instead of loading a whole transcript: it is the cheap way to confirm what was actually said and by whom. Read-only. Keywords: context, surrounding turns, what was said before, exact quote, verify a hit.

Input parameters:

- `after` (integer)
- `at` (string, required)
- `before` (integer)
- `meeting` (string, required)
- `workspace`

### `whosaid_items` (~117 tokens)

List action items from the workspace's entity graph, filtered by `owner`, `requester`, `status` or `type`. Each item carries its id (AI-NNN), text, owner, status, the meetings it was raised in and any commitments. Built by `whosaid index`; read-only. Keywords: action items, todos, commitments, who owes what, open items, follow-ups.

Input parameters:

- `owner`
- `requester`
- `status`
- `type`
- `workspace`

### `whosaid_item` (~79 tokens)

One action item by id (AI-NNN) with its full history: every meeting it came up in, the timestamped turns where it was discussed, commitments and status changes, related PRs. Use whosaid_context on those timestamps for the verbatim discussion. Read-only.

Input parameters:

- `id` (string, required)
- `workspace`

### `whosaid_person` (~109 tokens)

Per-person commitments view (GitHub issue #13): what `name` owns, what they asked others for, deadlines they gave, and the meetings they were in. Omit `name` to list every known person with counts. Names are speaker labels exactly as they appear in the transcripts (see whosaid_speakers). Read-only. Keywords: my action items, what does X owe, commitments by person, who promised what.

Input parameters:

- `name`
- `workspace`

### `whosaid_meetings` (~247 tokens)

List the meetings in the workspace as the graph knows them: folder, date, duration, attendees (speaker labels), action-item count, and kind (source origin: "teams-chat" for a Microsoft Teams chat folder, "outlook-mail" for an Outlook mail thread, "audio" for a recorded meeting; pass kind to filter). The folder names are what whosaid_search hits carry, what whosaid_context takes, and what the whosaid://workspace/meeting/{folder}/... resources use. Newest first. Filter with kind, since/until (YYYY-MM-DD, inclusive, on the meeting date) and query (case-insensitive substring of folder or source name), then page with offset (skip N) and limit (top N); compact=true keeps only folder, kind, created and minutes. A call with neither limit nor offset returns the newest 50 plus total/omitted and a paging hint. Read-only.

Input parameters:

- `compact` (boolean)
- `kind`
- `limit`
- `offset` (integer)
- `query`
- `since`
- `until`
- `workspace`

### `whosaid_prs` (~94 tokens)

List pull-request mentions found in the transcripts: PR numbers or links, who mentioned them, in which meeting and when. Read-only. Keywords: PR, pull request, code review, merged, deployed. Page with offset/limit; a call with neither returns the first 50 plus total/omitted and a paging hint.

Input parameters:

- `limit`
- `offset` (integer)
- `workspace`

### `whosaid_speakers` (~106 tokens)

List every speaker label in the search index with turn counts and the meetings they appear in. Use the exact labels as the `speaker` filter of whosaid_search or the `name` of whosaid_person. Busiest first; page with offset/limit; a call with neither returns the top 50 plus total/omitted and a paging hint. Read-only.

Input parameters:

- `limit`
- `offset` (integer)
- `workspace`

### `whosaid_workspace_status` (~124 tokens)

Read-only health check for a meeting workspace: whether the search index (_search.db) exists and what it holds (meetings, turns, embeddings), whether the rendered _WIKI.md, _ACTION-ITEMS.md and _INDEX.md exist, how many meeting folders there are, and the configured owner and group names from whosaid.toml. Run this first when a search returns nothing: it tells you whether `whosaid index <ws>` has been run. Keywords: workspace status, is it indexed, index health.

Input parameters:

- `workspace`

### `whosaid_worklist` (~327 tokens)

Ranked personal worklist (GitHub issue #13): what `owner` signed up for across every meeting, from the roll-up corpora (dev-commitments CM-NNN plus the action items AI-NNN they own), each item tiered P1/P2/P3 with a numeric score and short why strings. P1 = boss-requested, a blocking/urgency cue, a deadline cue, or seen in 3+ meetings; P2 = seen in 2 meetings, requested by anyone, or a strong cue in the latest meeting; P3 = the rest; negated items are never P1. owner is a speaker label or 'me' (the self-roled speaker, else the whosaid.toml [workspace] owner). Filter and page with tier (P1/P2/P3, case-insensitive) first, then offset (skip N), then limit (top N); compact=true keeps only id, source, text, tier, score and why per item; a call with neither limit nor offset returns the top 50 items plus total/omitted and a paging hint (GitHub issue #30). Deterministic, no LLM, same view as _WORKLIST-<Owner>.md. Read-only. Keywords: my worklist, what did I promise, what did I sign up for, my priorities, ranked commitments.

Input parameters:

- `compact` (boolean)
- `limit`
- `offset` (integer)
- `owner` (string)
- `tier`
- `workspace`

### `whosaid_reprocess` (~424 tokens)

Re-diarize meetings in a workspace whose speaker sidecars predate the v1.10 diarizer fixes (wraps `whosaid reprocess`). DEFAULT IS A DRY RUN (dry_run=true): it prints the scan report and the plan and changes nothing; pass dry_run=false to apply. When applied it rewrites each meeting's speaker-labeled transcript and diarization sidecar from the existing Whisper <base>.json, so ASR is NOT re-run, carries over transcript-only (no_save) labels, regenerates per-meeting action items and commitments (skip with no_items=true), then re-runs the workspace roll-up so corpus item ids and hand edits survive. Every touched meeting is first backed up to <workspace>/_reprocess-backups/<meeting>/<UTC timestamp>/. With no `meetings` and all_meetings=false it takes only the meetings the detector flags; all_meetings=true takes every meeting; `meetings` is a list of folder names. speakers/min_speakers/max_speakers/engine/hook override the diarization settings. expected_speakers is a list of known voice names (the roster, sent as one comma list) that anchors diarization to those enrolled voices. match_threshold (above 0, at most 1) is the cosine a known voice must reach to claim a cluster. Not read-only and slow when applied (minutes per meeting); run the dry run first. Workspace = the `workspace` argument or WHOSAID_WORKSPACE. Keywords: reprocess, re-diarize, redo speakers, fix speaker labels, old meetings, backfill.

Input parameters:

- `all_meetings` (boolean)
- `dry_run` (boolean)
- `engine`
- `expected_speakers`
- `hook`
- `match_threshold`
- `max_speakers`
- `meetings`
- `min_speakers`
- `no_items` (boolean)
- `speakers`
- `workspace`

## Diagnostics

Captured diagnostic sections: Provenance, Install scripts, Dependencies. The full working is on the page: https://verifymcp.io/servers/sblattj-whosaid/whosaid#diagnostics

## Score history

- 2026-10-23: 64
- 2026-10-22: 64
- 2026-10-21: 63
- 2026-10-20: 63

## Common questions

### What is the whosaid MCP server?

whosaid is an MCP server listed in the public MCP registry as io.github.sblattj/whosaid. Local speaker-attributed transcription and meeting search for Apple Silicon. Audio stays on the Mac. This page covers its PyPI package (whosaid).

### Is the whosaid MCP server safe to use?

whosaid scores 64 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 23 October 2026. Its build provenance is signed and verified. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the whosaid MCP server expose?

whosaid exposes 17 tools: whosaid_transcribe, whosaid_relabel, whosaid_list_speakers, whosaid_doctor, whosaid_enroll_from_file, and 12 more. Their descriptions and schemas cost roughly 3,370 tokens of context every time the server is loaded.

### Is the whosaid MCP server still maintained?

whosaid is still listed as active in the MCP registry. We last reached this channel on 23 October 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

### What licence is the whosaid MCP server under?

whosaid declares the MIT licence, which is OSI-approved. That covers the source only, and says nothing about the cost of any service it calls.

## Links

- PyPI project: https://pypi.org/project/whosaid/
- Socket report: https://socket.dev/pypi/package/whosaid
- Repository: https://github.com/sblattj/whosaid
- Changelog RSS feed: https://verifymcp.io/servers/sblattj-whosaid/whosaid.xml
- Changelog JSON feed: https://verifymcp.io/servers/sblattj-whosaid/whosaid.json
- HTML version of this page: https://verifymcp.io/servers/sblattj-whosaid/whosaid
