Apple Notes MCP Server
A Model Context Protocol (MCP) server that enables AI assistants like Claude to read, create, search, and manage notes in Apple Notes on macOS.
What is This?
This server acts as a bridge between AI assistants and Apple Notes. Once configured, you can ask Claude (or any MCP-compatible AI) to:
- "Save this conversation as a note called 'Meeting Summary'"
- "Find all my notes about the project deadline"
- "Read my shopping list note"
- "Move my draft notes to the Archive folder"
- "What notes do I have in my Work folder?"
The AI assistant communicates with this server, which then uses AppleScript to interact with the Notes app on your Mac. All data stays local on your machine.
Quick Start
Using Claude Code (Easiest)
If you're using Claude Code (in Terminal or VS Code), just ask Claude to install it:
Install the sweetrb/apple-notes-mcp MCP server so you can help me manage my Apple Notes
Claude will handle the installation and configuration automatically.
Or register it yourself with one deterministic command:
claude mcp add apple-notes -s user -- npx -y apple-notes-mcp
Using the Plugin Marketplace
Install as a Claude Code plugin for automatic configuration and enhanced AI behavior:
/plugin marketplace add sweetrb/apple-notes-mcp
/plugin install apple-notes
This method also installs a skill that teaches Claude when and how to use Apple Notes effectively.
On the first tool call, macOS shows an Automation permission prompt ("Claude" wants access to control "Notes") — click OK. Optionally, grant Full Disk Access to the app that launches the server to enable the checklist-state and note-metadata features; see the Full Disk Access Setup Guide. Everything else works without it.
Using the Codex Marketplace
The same plugin is available for Codex. Add the marketplace and install the plugin:
codex plugin marketplace add sweetrb/apple-notes-mcp
codex plugin add apple-notes@apple-notes-mcp
The Codex plugin runs the published apple-notes-mcp server through npx and ships the same Apple Notes skill, so behavior matches the Claude Code plugin.
Other Hosts (Hermes, Antigravity)
Configuration for two more hosts is included — each registers the same apple-notes MCP server (npx -y apple-notes-mcp):
- Hermes Agent (NousResearch) — Hermes has no plugin/marketplace drop-in. Add the server with
hermes mcp add apple-notes --command npx --args -y apple-notes-mcp, or merge.hermes-plugin/config.yamlinto~/.hermes/config.yaml. Details:.hermes-plugin/README.md. - Antigravity (Google) — add the server entry from
.antigravity-plugin/mcp_config.jsonto~/.gemini/config/mcp_config.json(or via Antigravity's MCP settings).
Using Claude Desktop
1. Install the server:
npm install -g apple-notes-mcp
2. Add to Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"apple-notes": {
"command": "npx",
"args": ["-y", "apple-notes-mcp"]
}
}
}
3. Restart Claude Desktop and start using natural language:
"Create a note called 'Ideas' with my brainstorming thoughts"
On first use, macOS will ask for permission to automate Notes.app. Click "OK" to allow.
Requirements
- macOS - Apple Notes and AppleScript are macOS-only
- Node.js 20+ - Required for the MCP server
- Apple Notes - Must have at least one account configured (iCloud, Gmail, etc.)
Features
| Feature | Description |
|---|---|
| Create Notes | Create notes with titles, content, and optional folder/account targeting |
| Search Notes | Find notes by title or search within note content |
| Read Notes | Retrieve note content and metadata |
| Update Notes | Modify existing notes (title and/or content) |
| Delete Notes | Remove notes (moves to Recently Deleted) |
| Move Notes | Organize notes into folders (supports nested paths) |
| Folder Management | Create, list, and delete folders with full hierarchical path support |
| Multi-Account | Work with iCloud, Gmail, Exchange, or any configured account, including account IDs and default folders |
| Batch Operations | Delete or move multiple notes at once |
| Checklist State | Read checklist done/undone state directly from the Notes database (requires Full Disk Access) |
| Export | Export all notes as JSON or get individual notes as Markdown |
| Attachments | List attachments, save them to disk, or fetch their bytes as base64 |
| Notes.app UI State | Reveal a note in Notes.app or read the current Notes.app selection |
| Sync Awareness | Detect iCloud sync in progress, warn about incomplete results |
| Collaboration | Detect shared notes, warn before modifying |
| Diagnostics | health-check plus a richer doctor (reachability, automation permission, accounts, Full Disk Access), sync status, and statistics |
Read/list/get tools also return structured JSON (structuredContent) alongside the text, so agents can consume results without parsing prose.
MCP resources & prompts
Resources expose read-only context the client can attach without a tool call:
notes://accounts, notes://folders, notes://stats, and the
notes://note/{id} template (returns the note as Markdown). Prompts package
common workflows: find-note, weekly-review, new-meeting-note.
Known limitations
A few Notes UI features are not exposed to AppleScript and therefore cannot be supported. See docs/APPLESCRIPT-LIMITATIONS.md for the investigation and verification behind each:
- Pinned notes — Notes has no scriptable
pinnedproperty via AppleScript. Pin state can now be read with the BETAget-note-metadatatool (from the NoteStore database), but it still cannot be set programmatically. - Note-to-note links — there is no
applenotes://deep link or link property; the only stable handle is thex-coredata://note id.
Tool Reference
This section documents all available tools. AI agents should use these tool names and parameters exactly as specified.
Note Operations
create-note
Creates a new note in Apple Notes.
| Parameter | Type | Required | Description |
|---|---|---|---|
title |
string | Yes | The title of the note. Automatically prepended as <h1> — do NOT include the title in content |
content |
string | Yes | The body content of the note (do not repeat the title here) |
tags |
string[] | No | Returned-only metadata — NOT written to Notes.app. Apple Notes tags can't be set via AppleScript, so values passed here are echoed back in the response but do not appear on the created note. Use inline #hashtags in content instead (Notes.app turns those into real tags) |
folder |
string | No | Folder to create the note in. Supports nested paths like "Work/Clients". Defaults to account root |
account |
string | No | Account name (defaults to iCloud) |
format |
string | No | Content format: "plaintext" (default) or "html". In both formats, the title is automatically prepended as <h1>. In plaintext mode, newlines become <br>, tabs become <br>, and backslashes are preserved as HTML entities |
Example (tagged with inline hashtags):
{
"title": "Meeting Notes",
"content": "Discussed Q4 roadmap and budget allocation\n\n#work #meetings"
}
Example - Create in a specific folder:
{
"title": "Client Meeting",
"content": "Discussed project timeline",
"folder": "Work/Clients"
}
Example - HTML formatting:
{
"title": "Status Report",
"content": "<h2>Summary</h2><p>All tasks <b>on track</b>.</p><ul><li>Feature A: complete</li><li>Feature B: in progress</li></ul>",
"format": "html"
}
Note: The title is automatically prepended as
<h1>in both plaintext and HTML formats. Do not include a<h1>title tag in thecontentparameter, or the title will appear twice.
Returns: Confirmation message with note title and ID. Save the ID for subsequent operations like update-note, delete-note, etc.
search-notes
Searches for notes by title or content.
| Parameter | Type | Required | Description |
|---|---|---|---|
query |
string | Yes | Text to search for |
searchContent |
boolean | No | If true, searches note body; if false (default), searches titles only |
account |
string | No | Account to search in (defaults to iCloud) |
folder |
string | No | Limit search to a specific folder (supports nested paths like "Work/Clients") |
modifiedSince |
string | No | ISO 8601 date string to filter notes modified on or after this date (e.g., "2025-01-01") |
limit |
number | No | Maximum number of results to return |
Example - Search titles:
{
"query": "meeting"
}
Example - Search content:
{
"query": "budget allocation",
"searchContent": true
}
Example - Search recent notes with limit:
{
"query": "todo",
"searchContent": true,
"modifiedSince": "2025-01-01",
"limit": 10
}
Returns: List of matching notes with titles, folder names, and IDs. Use the returned ID for subsequent operations like get-note-content, update-note, etc.
get-note-content
Retrieves the full content of a specific note.
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string | No | Note ID (preferred - more reliable than title) |
title |
string | No | Note title (use id instead when available) |
account |
string | No | Account containing the note (defaults to iCloud, ignored if id is provided) |
Note: Either id or title must be provided. Using id is recommended as it's unique and avoids issues with duplicate titles.
Example - Using ID (recommended):
{
"id": "x-coredata://ABC123/ICNote/p456"
}
Example - Using title:
{
"title": "Shopping List"
}
Returns: The HTML content of the note, or error if not found. The
structuredContent also includes hashtags — any inline #hashtag tags parsed
from the body. Apple Notes tags are inline hashtags, not a scriptable property;
see docs/APPLESCRIPT-LIMITATIONS.md. Smart Folders are not scriptable.
get-note-plaintext
Retrieves a note's body as plain text, with no HTML markup.
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string | No | Note ID (preferred - more reliable than title) |
title |
string | No | Note title (use id instead when available) |
account |
string | No | Account containing the note (defaults to iCloud, ignored if id is provided) |
Note: Either id or title must be provided. This reads the note's native plaintext property, so it skips the HTML-to-text conversion that get-note-content plus a Markdown pass would do. Use get-note-content when you need the HTML, or get-note-markdown when you want Markdown with checklist state.
Returns: The plain-text content of the note in structuredContent.plaintext, or error if not found.
get-note-details
Retrieves metadata about a note (without full content).
| Parameter | Type | Required | Description |
|---|---|---|---|
title |
string | Yes | Exact title of the note |
account |
string | No | Account containing the note (defaults to iCloud) |
Example:
{
"title": "Project Plan"
}
Returns: JSON with note metadata:
{
"id": "x-coredata://...",
"title": "Project Plan",
"created": "2025-01-15T10:30:00.000Z",
"modified": "2025-01-20T14:22:00.000Z",
"shared": false,
"passwordProtected": false,
"account": "iCloud"
}
get-note-by-id
Retrieves a note using its unique CoreData identifier.
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | The CoreData URL identifier (e.g., x-coredata://...) |
Returns: JSON with note metadata, or error if not found.
show-note
Reveals a note in Notes.app using its unique CoreData identifier.
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | The CoreData URL identifier (e.g., x-coredata://...) |
separately |
boolean | No | Open in a separate note window when supported by Notes.app |
Returns: Confirmation that Notes.app accepted the show command.
update-note
Updates an existing note's content and/or title.
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string | No | Note ID (preferred - more reliable than title) |
title |
string | No | Current title of the note to update (use id instead when available) |
newTitle |
string | No | New title (if changing the title; ignored when format is "html") |
newContent |
string | Yes | New content for the note body |
account |
string | No | Account containing the note (defaults to iCloud, ignored if id is provided) |
format |
string | No | Content format: "plaintext" (default) or "html". When "html", content replaces the entire note body as raw HTML and newTitle is ignored (the first HTML element serves as the title) |
Note: Either id or title must be provided. Using id is recommended.
Returns: Confirmation with the note's visible title and, for ID-based updates, its ID. For HTML updates, the title comes from the first rendered line of newContent, matching Notes.app. The response also warns if the note is shared.
Example - Using ID (recommended):
{
"id": "x-coredata://ABC123/ICNote/p456",
"newContent": "Updated content here"
}
Example - Update content only:
{
"title": "Shopping List",
"newContent": "- Milk\n- Eggs\n- Bread\n- Butter"
}
Example - Update title and content:
{
"title": "Draft",
"newTitle": "Final Version",
"newContent": "This is the completed document."
}
Example - Update with HTML formatting:
{
"id": "x-coredata://ABC123/ICNote/p456",
"newContent": "<p>New findings with <b>bold</b> emphasis.</p><pre><code>console.log('hello');</code></pre>",
"format": "html"
}
Returns: Confirmation message, or error if note not found.
Note: newContent replaces the entire note body — it is not appended. To preserve existing content, read it first (e.g. with get-note-content) and include it in newContent.
Attachments: A full-body replace can drop embedded files, images, scans, PDFs, or audio. When a note may hold attachments, run list-attachments first, and either save them with save-attachment or build a new note rather than overwriting. See the skill's Attachment-Safe Updates guidance.
delete-note
Deletes a note (moves to Recently Deleted in Notes.app).
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string | No | Note ID (preferred - more reliable than title) |
title |
string | No | Exact title of the note to delete (use id instead when available) |
account |
string | No | Account containing the note (defaults to iCloud, ignored if id is provided) |
Note: Either id or title must be provided. Using id is recommended.
Example - Using ID (recommended):
{
"id": "x-coredata://ABC123/ICNote/p456"
}
Example - Using title:
{
"title": "Old Draft"
}
Returns: Confirmation message, or error if note not found.
⚠️ Safety: Irreversible from the agent's side — requires explicit user confirmation before calling. Prefer search-notes / list-notes first to confirm the exact id(s) being deleted.
move-note
Moves a note to a different folder. The note is relocated in place via Notes.app's native move, so its id, creation date, and all embedded attachments (files, images, scans, PDFs, audio) are preserved. The destination folder must already exist — create it first with create-folder.
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string | No | Note ID (preferred - more reliable than title) |
title |
string | No | Title of the note to move (use id instead when available) |
folder |
string | Yes | Destination folder name or nested path (e.g., "Work/Clients") |
account |
string | No | Account containing the note (defaults to iCloud, ignored if id is provided) |
Note: Either id or title must be provided. Using id is recommended.
Example - Using ID (recommended):
{
"id": "x-coredata://ABC123/ICNote/p456",
"folder": "Archive"
}
Example - Using title:
{
"title": "Completed Task",
"folder": "Archive"
}
Returns: Confirmation message, or error if note or folder not found.
append-to-note
Appends or prepends content to an existing note without replacing it. Always reads and writes as HTML, preserving all existing rich formatting.
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string | No | Note ID (preferred - more reliable than title) |
title |
string | No | Note title (use id instead when available) |
content |
string | Yes | Text to append to the note body |
position |
string | No | "after" (default) appends to the end; "before" prepends to the start |
separator |
string | No | String placed between existing content and new content (default: two newlines → <div><br></div> in HTML) |
format |
string | No | Format of the content being appended: "plaintext" (default) or "html" |
account |
string | No | Account containing the note (defaults to iCloud, ignored if id is provided) |
Note: Either id or title must be provided. Using id is recommended.
Example - Append plaintext:
{
"id": "x-coredata://ABC123/ICNote/p456",
"content": "New item added today"
}
Example - Prepend HTML:
{
"id": "x-coredata://ABC123/ICNote/p456",
"content": "<div><b>Status:</b> done</div>",
"format": "html",
"position": "before"
}
Returns: Confirmation with note id and title. Warns when the note is shared with collaborators.
⚠️ Safety: Reads the existing body first, concatenates, then writes back. Run list-attachments first if the note may hold embedded files — a full-body rewrite can drop attachments.
get-note-link
Returns the notes://showNote?identifier=<uuid> deep-link URL for a note. The URL opens the note in Notes.app on iOS and macOS and can be stored in Reminders tasks or shared links.
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string | No | Note ID (preferred - more reliable than title) |
title |
string | No | Note title (use id instead when available) |
account |
string | No | Account containing the note (defaults to iCloud, ignored if id is provided) |
Note: Either id or title must be provided. Using id is recommended. Password-protected notes cannot be linked.
Example:
{
"id": "x-coredata://ABC123/ICNote/p456"
}
Returns: notes://showNote?identifier=<uuid> URL string, plus the note id and title.
Note: Requires Full Disk Access for the app that launches the server so the Notes SQLite database is readable. On macOS 12–15 the tool also falls back to the AppleScript note link property. Run the doctor tool to verify access.
list-notes
Lists all notes, optionally filtered by folder, date, and limit.
| Parameter | Type | Required | Description |
|---|---|---|---|
account |
string | No | Account to list notes from (defaults to iCloud) |
folder |
string | No | Filter to notes in this folder only (supports nested paths like "Work/Clients") |
modifiedSince |
string | No | ISO 8601 date string to filter notes modified on or after this date (e.g., "2025-01-01") |
limit |
number | No | Maximum number of notes to return |
Example - All notes:
{}
Example - Notes in a folder:
{
"folder": "Work"
}
Example - Recent notes with limit:
{
"modifiedSince": "2025-06-01",
"limit": 20
}
Returns: List of note titles.
get-selected-notes
Reads the currently selected note(s) from the Notes.app UI.
Parameters: None
Returns: Selected note metadata, including IDs for follow-up operations. Returns an empty list when Notes.app has no selected note.
Folder Operations
list-folders
Lists all folders in an account with full hierarchical paths.
| Parameter | Type | Required | Description |
|---|---|---|---|
account |
string | No | Account to list folders from (defaults to iCloud) |
Example:
{}
Returns: List of folders with IDs, paths, account names, and shared state. Nested folders are shown as full paths (e.g., Work/Clients/Omnia). Duplicate folder names are disambiguated by their full path. Literal slashes in folder names are escaped as \/ (e.g., Spain\/Portugal 2023).
create-folder
Creates a new folder.
| Parameter | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Name for the new folder |
account |
string | No | Account to create folder in (defaults to iCloud) |
Example:
{
"name": "Work Projects"
}
Returns: Confirmation message, or error if folder already exists.
delete-folder
Deletes a folder.
| Parameter | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Name or path of the folder to delete (supports nested paths like "Work/Old") |
account |
string | No | Account containing the folder (defaults to iCloud) |
Example:
{
"name": "Old Projects"
}
Returns: Confirmation message, or error if folder not found or not empty.
⚠️ Safety: Irreversible — requires explicit user confirmation before calling. Prefer list-folders first to confirm the exact folder path being deleted.
show-folder
Reveals a folder in Notes.app using its unique CoreData identifier.
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | The folder's CoreData identifier (from list-folders) |
separately |
boolean | No | Open in a separate window when supported by Notes.app |
Returns: Confirmation that Notes.app accepted the show command.
Account Operations
list-accounts
Lists all configured Notes accounts.
Parameters: None
Example:
{}
Returns: List of accounts with names, IDs, upgraded state, and default folder metadata.
get-default-location
Returns the default account and folder Notes.app uses for newly created notes.
Parameters: None
Returns: Default account and folder metadata, including IDs and shared state.
show-account
Reveals an account in Notes.app using its unique CoreData identifier.
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | The account's CoreData identifier (from list-accounts) |
separately |
boolean | No | Open in a separate window when supported by Notes.app |
Returns: Confirmation that Notes.app accepted the show command.
Batch Operations
batch-delete-notes
Deletes multiple notes at once by ID.
| Parameter | Type | Required | Description |
|---|---|---|---|
ids |
string[] | Yes | Array of note IDs to delete |
Returns: Summary of successes and failures.
⚠️ Safety: Irreversible — requires explicit user confirmation before calling. Prefer search-notes / list-notes first to confirm the exact ids being deleted.
batch-move-notes
Moves multiple notes to a folder.
| Parameter | Type | Required | Description |
|---|---|---|---|
ids |
string[] | Yes | Array of note IDs to move |
folder |
string | Yes | Destination folder name or nested path (e.g., "Work/Clients") |
account |
string | No | Account containing the folder |
Returns: Summary of successes and failures.
Export Operations
export-notes-json
Exports all notes as a JSON structure.
Parameters: None
Returns: Complete JSON export with all accounts, folders, and notes including metadata.
get-note-markdown
Gets a note's content as Markdown instead of HTML. If the note contains checklists and Full Disk Access is granted, checklist items are automatically annotated with [x] (done) or [ ] (undone).
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string | No | Note ID (preferred) |
title |
string | No | Note title |
account |
string | No | Account containing the note |
Returns: Note content converted to Markdown format. Checklist items include [x]/[ ] prefixes when database access is available.
get-checklist-state
Reads checklist done/undone state for a note. This bypasses the AppleScript limitation where body of note strips checklist state, by reading directly from the NoteStore SQLite database.
Requires: Full Disk Access for the MCP host process (see Full Disk Access Setup).
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Note ID (use search-notes to find it first) |
Example:
{
"id": "x-coredata://ABC123/ICNote/p456"
}
Returns: Checklist items with done/undone state and progress count:
Checklist for "Shopping List" (2/4 done):
[x] Buy milk
[x] Get bread
[ ] Pick up laundry
[ ] Call dentist
get-note-metadata (BETA)
Reads note metadata that AppleScript cannot expose, by querying the NoteStore SQLite database directly: pinned state, checklist flags, trash/recovery state, the preview snippet, and the password hint. The available fields vary by macOS version.
Requires: Full Disk Access for the MCP host process (see Full Disk Access Setup).
BETA: the NoteStore schema changes between macOS releases, so some fields can be absent on older or newer systems. The database is only ever read, never written.
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Note ID (use search-notes to find it first) |
Returns: A metadata object in structuredContent holding any of pinned, hasChecklist, hasChecklistInProgress, recoveringFromTrash, passwordProtected, passwordHint, snippet, widgetSnippet, and smartFolderQuery. Unlike most read tools, it also resolves trashed notes that AppleScript can no longer find.
list-attachments
Lists attachments in a note.
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string | No | Note ID (preferred) |
title |
string | No | Note title |
account |
string | No | Account containing the note |
Returns: List of attachments with IDs, names, content identifiers, URLs when available, created/modified dates, and shared state.
⚠️ Safety: A lookup failure is reported as an error, never as an empty list — so an empty result reliably means the note has no attachments and is safe to replace wholesale. Treat an error as "unknown", not "none".
save-attachment
Saves a note attachment to disk.
| Parameter | Type | Required | Description |
|---|---|---|---|
noteId |
string | Yes | CoreData note ID (from search-notes/list-notes) |
attachmentId |
string | Yes | Attachment ID (from list-attachments) |
savePath |
string | Yes | Absolute destination file path. Must be under your home directory, a temp directory, or /Volumes |
Returns: Confirmation with the saved path, name, and content type (also in structuredContent).
fetch-attachment
Returns a note attachment's bytes as base64, without writing to disk (the read counterpart to save-attachment).
| Parameter | Type | Required | Description |
|---|---|---|---|
noteId |
string | Yes | CoreData note ID (from search-notes/list-notes) |
attachmentId |
string | Yes | Attachment ID (from list-attachments) |
Returns: The attachment name, content type, byte count, and base64 payload in structuredContent.base64.
show-attachment
Reveals one note attachment in Notes.app. Attachments are elements of a note, so this takes both the note id and the attachment id (the same pair used by save-attachment / fetch-attachment).
| Parameter | Type | Required | Description |
|---|---|---|---|
noteId |
string | Yes | CoreData note ID (from search-notes/list-notes) |
attachmentId |
string | Yes | Attachment ID (from list-attachments) |
separately |
boolean | No | Open in a separate window when supported by Notes.app |
Returns: Confirmation that Notes.app revealed the attachment.
Diagnostics
health-check
Verifies Notes.app connectivity and permissions.
Parameters: None
Returns: Status of all health checks (app installed, permissions, account access).
doctor
Run a full setup diagnostic: Notes.app reachability, the Automation permission, configured accounts, and Full Disk Access — each reported as ok / warn / fail with an actionable message. This is the richer counterpart to health-check; reach for it first when something isn't working.
Parameters: None
Returns: A per-check report (structuredContent carries the raw {healthy, checks[]}). The Full Disk Access check tells you whether checklist-state features will work — see Full Disk Access Setup.
get-notes-stats
Gets comprehensive statistics about your notes.
Parameters: None
Returns: Total counts, per-account breakdown, folder statistics, and recently modified counts.
The structuredContent also includes a coverage object — { complete, scanned, covered, warnings[] }. If complete is false, one or more accounts (or the recent-activity scan) could not be read and the counts reflect only the scopes that succeeded; the text output adds a "⚠️ Partial results" line. This lets you tell a genuinely empty library apart from a partial failure.
get-sync-status
Checks iCloud sync status.
Parameters: None
Returns: Whether sync is active, pending uploads, and last activity time.
list-shared-notes
Lists all notes shared with collaborators.
Parameters: None
Returns: List of shared notes with warnings about collaboration.
Usage Patterns
Basic Workflow
User: "Create a note called 'Todo' with my tasks for today"
AI: [calls create-note with title="Todo", content="Tasks for today..."]
"I've created a note called 'Todo' with your tasks."
User: "What notes do I have?"
AI: [calls list-notes]
"You have 15 notes: Todo, Shopping List, Meeting Notes..."
User: "Show me the Shopping List"
AI: [calls get-note-content with title="Shopping List"]
"Here's your shopping list: - Milk - Eggs - Bread..."
Working with Accounts
By default, all operations use iCloud. To work with other accounts:
User: "What accounts do I have?"
AI: [calls list-accounts]
"You have 3 accounts: iCloud, Gmail, Exchange"
User: "List notes in my Gmail account"
AI: [calls list-notes with account="Gmail"]
"Your Gmail account has 5 notes..."
Organizing with Folders
User: "Create a folder called 'Archive'"
AI: [calls create-folder with name="Archive"]
"Created folder 'Archive'"
User: "Move my old meeting notes to Archive"
AI: [calls move-note with title="Old Meeting Notes", folder="Archive"]
"Moved 'Old Meeting Notes' to 'Archive'"
User: "What folders do I have?"
AI: [calls list-folders]
"You have 5 folders: Work, Work/Clients, Work/Clients/Omnia, Archive, Recipes"
User: "Create a note in Work/Clients about Acme Corp"
AI: [calls create-note with title="Acme Corp", content="...", folder="Work/Clients"]
"Created 'Acme Corp' in Work/Clients"
Installation Options
npm (Recommended)
npm install -g apple-notes-mcp
From Source
git clone https://github.com/sweetrb/apple-notes-mcp.git
cd apple-notes-mcp
The repo ships a prebuilt, dependency-free build/index.js, so a bare clone runs with nothing but Node installed. pnpm install and pnpm run build are only needed when you change the source (development uses pnpm, not npm).
You can also install straight from the git repo with npm install -g github:sweetrb/apple-notes-mcp (building from source requires pnpm), but the published npm package above is the recommended path.
If installed from source, use this configuration:
{
"mcpServers": {
"apple-notes": {
"command": "node",
"args": ["/path/to/apple-notes-mcp/build/index.js"]
}
}
}
Running from a clone in Claude Code (project-scope .mcp.json)
This repo ships a .mcp.json at its root so that, when you run claude from inside a clone, the server is registered automatically as a project-scope server — no manual config needed. Just launch Claude Code from the repo directory and approve the server when prompted (the bundled build/index.js is committed, so no build step is required).
The entrypoint is written as:
"args": ["${CLAUDE_PROJECT_DIR:-.}/build/index.js"]
CLAUDE_PROJECT_DIR is the variable Claude Code injects into a project/user-scoped server's environment, and it resolves to the repo root. You must launch claude from inside the repo for this to work — the bare . fallback is only a last resort and is not reliable, because it resolves against the launching process's working directory, not the repo.
Why not
${CLAUDE_PLUGIN_ROOT}?CLAUDE_PLUGIN_ROOTis set only for marketplace plugin installs, never for a project-scope clone, so it can't drive the clone workflow. Conversely, a plugin install can't useCLAUDE_PROJECT_DIR(in a plugin, that points at the user's project, not the plugin's own directory). Claude Code does not support nested defaults like${CLAUDE_PLUGIN_ROOT:-${CLAUDE_PROJECT_DIR:-.}}, so a single entrypoint string cannot serve both contexts. The two distribution paths are therefore decoupled: the plugin carries its own MCP config in.claude-plugin/plugin.json(using${CLAUDE_PLUGIN_ROOT}), while the root.mcp.jsonis dedicated to the clone workflow (using${CLAUDE_PROJECT_DIR:-.}). Becauseplugin.jsondeclares its ownmcpServers, the plugin does not also auto-load the root.mcp.json, so there is no double-registration.
Heads-up on scope precedence: project-scope (
.mcp.json) outranks user-scope. If you also have anapple-notesentry registered at user scope (e.g. an absolute path in~/.claude.json), the project-scope entry wins and the user-scope one is ignored entirely. Pick one — for local development on this repo, the project-scope.mcp.jsonis the intended source. To pin a specific local build instead, register it at local scope (claude mcp add apple-notes -s local -- node /abs/path/build/index.js), which outranks project scope.
Configuration
Environment variables
All configuration is optional — the server works out of the box. Override behavior with these variables (set them in your MCP client's env block, or via the config file below):
| Variable | Default | Description |
|---|---|---|
APPLE_NOTES_MCP_MAX_BUFFER |
67108864 (64 MB) |
Max bytes captured from a single AppleScript invocation. Raise it if a very large export/list is truncated; lower it to cap memory. |
APPLE_NOTES_MCP_MAX_ATTACHMENT_BYTES |
26214400 (25 MB) |
Max size of an attachment that fetch-attachment will base64-encode inline. Larger attachments are rejected with an error pointing at save-attachment (which streams to disk and has no such limit). Raise it to fetch bigger attachments inline; lower it to cap memory. |
APPLE_NOTES_MCP_MAX_INLINE_IMAGE_BYTES |
262144 (256 KB) |
Per-image cap on the base64 payload kept inline in a get-note-content response. Inline images over the cap are replaced with placeholders (with a warning appended) so an image-heavy note cannot exceed the MCP client's message limit and drop the connection; export the real files with save-attachment or fetch-attachment. Raise it to keep bigger images inline. |
APPLE_NOTES_MCP_CONFIG_FILE |
~/Library/Application Support/apple-notes-mcp/config.json |
Path to the JSON config file (see below). |
APPLE_NOTES_MCP_TIMEOUT_MS |
30000 (30 s) |
Total AppleScript operation timeout, including retry attempts and delays. Raise it if full-library operations (large searches, exports) time out on a big Notes library. Per-call timeoutMs options still win. |
APPLE_NOTES_MCP_MAX_RETRIES |
2 |
Maximum attempts for a read-only AppleScr |
No comments yet
Be the first to share your take.