ParrotNotes
Search, summarize and save insights on your ParrotNotes in-person meeting notes. Hosted remote server with OAuth sign-in, no API key.
Hosted MCP Server
npx add-mcp 'https://mcp.parrotnotes.app/mcp'Installs into Claude Code, Codex, Cursor and more
Documentation
What it does
ParrotNotes records in-person conversations on your phone or Mac and turns them into transcripts, summaries and action items. This server exposes that library to an AI assistant so you can ask questions of your own notes without opening the app.
Typical requests:
- "Show me my five most recent recordings."
- "Find my notes from the Henderson site visit and summarise the key points."
- "What did this client commit to? Save that back as an action list."
- "List everything tagged project-alpha."
Before you start
- A ParrotNotes account. The free tier works.
- At least one saved note or recording, otherwise every tool returns an empty result.
- Notes are created in the ParrotNotes apps for iOS, Android and macOS. This server reads that existing library and can save an insight back to a note. It cannot create recordings.
No API key, client ID or manual configuration is required. Clients register themselves through Dynamic Client Registration and you sign in through the browser.
Connecting
Claude
- Open Settings > Connectors > Add custom connector.
- Paste the endpoint:
https://mcp.parrotnotes.app/mcp
- You are redirected to
https://parrotnotes.app/auth/authorizeto sign in and grant consent. - After approval the tools are available in any conversation.
Claude Code
claude mcp add --transport http parrotnotes https://mcp.parrotnotes.app/mcp
Then run /mcp inside Claude Code, select parrotnotes and complete the sign-in in your browser.
ChatGPT
Add the same endpoint as a custom connector. The OAuth flow is identical.
MCP Inspector
npx @modelcontextprotocol/inspector
Set transport to Streamable HTTP, URL to https://mcp.parrotnotes.app/mcp, and connect. The inspector completes the OAuth flow in the browser.
Verify discovery
An unauthenticated request returns 401 with a pointer to the protected resource metadata, which is how clients discover the authorization server:
curl -i -X POST https://mcp.parrotnotes.app/mcp \
-H "Content-Type: application/json" \
-d '{}'
HTTP/2 401
www-authenticate: Bearer realm="parrot-notes", resource_metadata="https://mcp.parrotnotes.app/.well-known/oauth-protected-resource", scope="openid email profile"
curl https://mcp.parrotnotes.app/.well-known/oauth-protected-resource
The response lists the resource (https://mcp.parrotnotes.app/mcp), its authorization_servers, scopes_supported and bearer_methods_supported (header).
Tools
| Tool | What it does | Access |
|---|---|---|
get_recent_notes | Lists recent notes and recordings, with pagination and a type filter | Read |
search_notes | Full-text search across your notes by keyword | Read |
get_note | Returns a single note by ID with its full transcript and content | Read |
get_note_insights | Returns AI insights saved against a note: summaries, action items, sentiment | Read |
get_user_labels | Lists every label you have created | Read |
get_note_labels | Returns the labels assigned to a specific note | Read |
get_notes_by_label | Returns all notes carrying a given label | Read |
get_subscription_status | Returns your current subscription status | Read |
save_note_insight | Saves an AI-generated insight against a note | Write |
Eight of the nine tools are read-only and carry readOnlyHint: true. save_note_insight is the only tool that changes data: it upserts an insight on a note you own and can overwrite an existing insight of the same type and target language, so it carries destructiveHint: true. Nothing is ever deleted. Every tool sets openWorldHint: false and idempotentHint: true.
Every tool declares an output schema and returns validated structuredContent, with the same JSON mirrored as text in content for clients that only read text.
Identifiers
Notes carry two IDs. Use the right one for each tool:
| Field | Type | Used by |
|---|---|---|
id | string | get_note (note_id) |
localRecordingId | integer | get_note_insights, get_note_labels, save_note_insight (local_recording_id) |
localLabelId | integer | get_notes_by_label (local_label_id) |
Tool reference
get_recent_notes
Lists notes and recordings, newest first.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer, 1 to 50 | No (default 10) | Number of notes to return |
offset | integer, 0 or more | No (default 0) | Number of notes to skip, for pagination |
note_type | "audio" | "text" | No | Filter to voice recordings or written notes |
Returns { notes: Note[], hasMore: boolean }.
{ "limit": 5, "note_type": "audio" }
search_notes
Keyword search across note titles, transcripts and text content.
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | Yes | Search keyword or phrase |
limit | integer, 1 to 50 | No (default 10) | Maximum number of results |
Returns { notes: NoteWithExcerpt[] }. Each result adds an excerpt around the first match.
{ "query": "Henderson site visit" }
get_note
Returns one note with its complete transcript or text body.
| Parameter | Type | Required | Description |
|---|---|---|---|
note_id | string | Yes | The note's id |
Returns a Note.
get_note_insights
Returns the AI insights saved against a note.
| Parameter | Type | Required | Description |
|---|---|---|---|
local_recording_id | string | Yes | The note's localRecordingId |
insight_type | string | No | Filter, for example summary, action_items, sentiment, translate |
Returns { insights: Insight[] }.
get_user_labels
Lists every label on the account. Takes no parameters.
Returns { labels: { localLabelId: number, name: string }[] }.
get_note_labels
| Parameter | Type | Required | Description |
|---|---|---|---|
local_recording_id | integer | Yes | The note's localRecordingId |
Returns { labels: Label[] }.
get_notes_by_label
| Parameter | Type | Required | Description |
|---|---|---|---|
local_label_id | integer | Yes | localLabelId from get_user_labels |
Returns { notes: Note[] }.
get_subscription_status
Takes no parameters.
Returns { subscription: Subscription | null, isActive: boolean }. isActive is true when the subscription is active or trialing and not expired.
save_note_insight
Saves an insight against a note. Upserts on the combination of note, insight_type and target_language.
| Parameter | Type | Required | Description |
|---|---|---|---|
local_recording_id | integer | Yes | The note's localRecordingId |
insight_type | enum | Yes | One of the values below |
content | string | Yes | The insight text |
target_language | string | When insight_type is translate | Language code, for example es, fr, de |
Allowed insight_type values:
summary, key_points, offer_tips, make_notes, action_items, meeting_report,
translate, linkedin_post, tweet, blog_post, email, sentiment, shopping_list
Returns the saved Insight.
{
"local_recording_id": 1712,
"insight_type": "action_items",
"content": "- Send revised quote by Friday\n- Book second site visit"
}
Data shapes
interface Note {
id: string // use with get_note
localRecordingId: number // use with insight and label tools
displayName: string
noteType: string // "audio" | "text"
transcript: string | null // audio notes
textContent: string | null // text notes
duration: number | null // seconds
dateTime: string // ISO 8601
transcriptionStatus: string | null
}
interface NoteWithExcerpt extends Note {
excerpt: string | null // snippet around the first match
}
interface Label {
localLabelId: number
name: string
}
interface Insight {
id: string
localRecordingId: string
insightType: string
content: string
targetLanguage: string | null
}
interface Subscription {
id: string
provider: string // e.g. revenuecat, lemonsqueezy
productId: string | null
status: string // e.g. active, trialing, expired
expiresAt: string | null // ISO 8601, null if it does not expire
}
Errors
A failed call returns a normal tool result with isError: true and a human-readable message in content. Internal details are stripped from the message. A call without a valid session returns Authentication required.
What it can and cannot access
For IT administrators evaluating this connector.
It can read: your own notes and recordings, their transcripts and content, AI insights saved against them, your labels, and your subscription status.
It can write: one thing, an insight attached to a note you own.
It cannot access: any other user's data, Claude's memory, chat history, conversation summaries, or files you have uploaded to the assistant. It does not collect conversation data beyond what a given tool call needs to run.
Scoping: every database query is scoped to the authenticated user through Postgres row level security. A session cannot reach another account's data even if a tool were called with another account's identifiers.
Security
Authentication uses OAuth 2.0 with Dynamic Client Registration (RFC 7591). The authorization server is Supabase Auth, with a consent page hosted on parrotnotes.app.
Every request to /mcp is verified against the auth server before it reaches the MCP layer, so a token that is expired, malformed, or whose session was signed out elsewhere is rejected immediately.
| Situation | Response |
|---|---|
| No bearer token | 401 with a WWW-Authenticate header pointing at the protected resource metadata |
| Token expired, malformed, or session revoked | 401 with error="invalid_token", which tells the client to re-authenticate |
| Auth server unreachable | 503 with no challenge, so clients retry instead of re-authenticating |
Transport is HTTPS only. Tokens are accepted in the Authorization: Bearer header only.
Privacy and data retention
- No data is used for AI model training.
- Audio auto-delete is available in the app: the transcript is kept and the recording is discarded. This matters in compliance-sensitive industries.
- Full policy: parrotnotes.app/privacy
Troubleshooting
The assistant asks you to reconnect repeatedly. Signing out of ParrotNotes on the web with a global scope ends every session for your account, including connector sessions. Sign in again and reauthorize the connector.
Tools return empty results. The account has no notes yet. Record or import something in the app first.
A tool call fails with a permission error. The note ID belongs to a different account. Tools only return data owned by the signed-in user.
get_note_labels or save_note_insight rejects the ID. These take the integer localRecordingId, not the string id. See Identifiers.
Support
- Email: support@parrotnotes.app
- Privacy policy: parrotnotes.app/privacy
- Terms: parrotnotes.app/terms
- This page as Markdown: parrotnotes.app/docs/mcp.md