# MCP server

> Source: https://whisperer.one/llms/mcp.md · Whisperer machine-readable layer.
> Product overview: https://whisperer.one/llms/product.md · Full corpus: https://whisperer.one/llms-full.txt

Whisperer runs an MCP (Model Context Protocol) server. It lets an external AI
client — Claude, ChatGPT, Cursor and any other MCP-capable client — read and act on the
data of one Whisperer user, with that user's explicit consent.

## Endpoint

| Property | Value |
| --- | --- |
| Transport | Streamable HTTP, JSON-RPC 2.0 over `POST` |
| URL | `https://whisperer.one/mcp` |
| Methods | `POST` only; `GET` is rejected |
| Protocol versions | `2025-11-25`, `2025-06-18` |
| Assumed version when the `MCP-Protocol-Version` header is absent | `2025-03-26` |
| Server name reported by `initialize` | `Whisperer` |
| Rate limit | per token, not per IP |
| Plan required | any paid plan (Start, Pro or Max); Free returns `403 subscription_required` |

`initialize` performs real version negotiation: the server answers with the highest version
it shares with the client rather than a constant. Revision `2026-07-28` clients may call
`server/discover` instead, which returns the full `protocolVersions` list without a handshake.

## Authentication and discovery

Two ways to authenticate, both bearer tokens in the `Authorization` header. Tokens are never
accepted in the query string.

**OAuth 2.1 (recommended).** Discovery follows RFC 9728 and RFC 8414 — an unauthenticated
request returns `401` with a `WWW-Authenticate` header pointing at the resource metadata:

| Document | URL |
| --- | --- |
| Protected resource metadata (RFC 9728) | `https://whisperer.one/.well-known/oauth-protected-resource` |
| Authorization server metadata (RFC 8414) | `https://whisperer.one/.well-known/oauth-authorization-server` |

PKCE with `S256` is mandatory, `plain` is refused. The only response type is `code`; implicit
and password grants do not exist in OAuth 2.1 and are not offered. Dynamic client registration
(RFC 7591) is open, which is what makes the "Add custom connector" dialog in Claude and
ChatGPT work with an empty client ID.

**Personal token.** Issued in the Whisperer account. It carries no scopes, so it cannot call
tools at all — it only reads meetings the user has explicitly shared, through `resources/*`.
This is deliberate: a token minted for read-only sharing must not silently become a key to
billing, tickets and the whole archive.

## What is exposed, and what is not

The MCP tool set is a **projection of the agent's own tool registry**, not a second catalogue.
Of 130 tools in the registry, 91 are projected outward
(48 read, 43 write). The rest are withheld by rules that no setting relaxes:

- **Irreversible operations** (deleting a meeting, a project, a file, an account) are never
  exposed. In the Whisperer interface such actions are guarded by a human confirmation with a
  server-rendered preview of the blast radius; an external client has no such screen.
- **Tools that accept a credential** are never exposed — through MCP they would become a
  channel for injecting secrets into someone else's integration.
- **Actions inside the user's other services** — sending mail, posting to chat, writing to a
  CRM, reading a connected drive — are never exposed. The user granted that access to
  Whisperer, and passing it on to an arbitrary external client would be handing someone
  else's consent down the chain.
- 37 tools sit on an explicit deny list by name, duplicating the flags above on
  purpose: a flag removed once must not silently open deletion to the outside.

Meeting data reached through `tools/call` obeys the same boundary as `resources/*`: only
meetings the user marked as shared, never the whole archive, and never ephemeral ones.

## Scopes

Scopes are `whisperer:<domain>.<read|write>`. There is no `*`, `all` or `full-access` scope —
scope minimisation is a requirement, not a preference. A scope grants the RIGHT; the plan
check is separate, and both must agree before a tool runs.

- `whisperer:account.read`
- `whisperer:account.write`
- `whisperer:calendar.read`
- `whisperer:calendar.write`
- `whisperer:context.read`
- `whisperer:context.write`
- `whisperer:hub.read`
- `whisperer:hub.write`
- `whisperer:integrations.read`
- `whisperer:kb.read`
- `whisperer:leo.read`
- `whisperer:leo.write`
- `whisperer:meetings.read`
- `whisperer:meetings.write`
- `whisperer:prompts.read`
- `whisperer:prompts.write`
- `whisperer:storage.read`
- `whisperer:storage.write`
- `whisperer:tickets.read`
- `whisperer:tickets.write`
- `whisperer:tracker.read`
- `whisperer:tracker.write`

## Tools by domain

| Domain | Tools | Names |
| --- | --- | --- |
| `account` | 12 | `get_analytics`, `get_app_versions`, `get_billing_overview`, `get_hotkeys`, `get_my_model_roles`, `get_referral_stats`, `list_platform_models`, `list_transactions`, `search_help_docs`, `set_hotkey`, `set_interface_language`, `set_model_role` |
| `calendar` | 5 | `create_calendar_event`, `get_calendar_event`, `list_calendar_events`, `move_calendar_event`, `update_calendar_event` |
| `context` | 2 | `append_context`, `get_my_context` |
| `hub` | 11 | `add_hub_material`, `archive_hub`, `create_hub`, `get_hub_loops`, `list_hub_materials`, `list_hubs`, `move_meeting_to_hub`, `remove_hub_material`, `remove_meeting_from_hub`, `rename_hub`, `set_hub_board` |
| `integrations` | 3 | `list_connectors`, `list_integrations`, `list_mcp_connections` |
| `kb` | 2 | `search_knowledge_base`, `search_web` |
| `leo` | 10 | `explain_commitment`, `get_leo_personalization`, `get_leo_settings`, `get_person_context`, `list_commitments`, `list_errands`, `list_leo_memory`, `set_leo_personalization`, `set_leo_settings`, `snooze_reminder` |
| `meetings` | 21 | `create_meeting_folder`, `create_note`, `find_decisions`, `find_positions`, `get_meeting_evidence`, `get_meeting_mindmap`, `get_meeting_stats`, `get_meeting_transcript`, `list_meeting_folders`, `list_meeting_templates`, `list_meetings`, `move_meeting_folder`, `move_meeting_to_folder`, `rename_meeting`, `rename_meeting_folder`, `save_meeting_to_storage`, `search_meetings`, `search_transcript`, `set_meeting_folder_favorite`, `trace_topic`, `update_note` |
| `prompts` | 4 | `archive_prompt`, `create_prompt`, `list_my_prompts`, `update_prompt` |
| `storage` | 7 | `analyze_csv`, `get_storage_usage`, `list_files`, `move_file`, `read_file_text`, `rename_file`, `send_file_to_knowledge_base` |
| `tickets` | 6 | `close_ticket`, `create_ticket`, `list_tickets`, `read_ticket_thread`, `reopen_ticket`, `reply_ticket` |
| `tracker` | 8 | `create_tracker_column`, `create_tracker_project`, `create_tracker_task`, `create_tracker_tasks`, `get_tracker_board`, `list_tracker_projects`, `move_tracker_task`, `update_tracker_task` |

## Connecting a client

**Claude (desktop and web) — custom connector.** Settings → Connectors → Add custom
connector → URL `https://whisperer.one/mcp`. Leave the client ID empty; the client registers
itself, opens the Whisperer consent screen in a browser, and you approve the domains it asked
for.

**ChatGPT — connectors.** Same URL, same OAuth flow. Dynamic client registration is what
makes the empty-client-ID case work.

**Cursor and other clients that read a config file.** Point the client at the streamable HTTP
transport:

```json
{
  "mcpServers": {
    "whisperer": {
      "url": "https://whisperer.one/mcp"
    }
  }
}
```

Clients that cannot do OAuth can use a personal token from the Whisperer account, with the
`resources/*` limitation described above.

## Limits and failure modes

| Situation | Response |
| --- | --- |
| No or malformed bearer token | `401` with `WWW-Authenticate` pointing at resource metadata |
| Revoked, expired or wrong-kind token | `401 invalid_token` |
| Free plan | `403 subscription_required` |
| Tool outside the granted scopes | JSON-RPC `-32602 Unknown tool` — the same answer as for a tool that does not exist, so absence and denial are indistinguishable |
| Tool refused for a business reason | a result with `isError: true` and a readable message, not a protocol error |
| Rate limit exceeded | `429` with `Retry-After` |
| Unknown `Origin` | rejected — DNS-rebinding protection |

Refresh tokens are not accepted at `/mcp`; they are only exchanged at the token endpoint.
`tools/list` and `resources/list` are paginated with an opaque cursor.
