upnote-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@upnote-mcp search my notes for project ideas"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
upnote-mcp
A local Model Context Protocol server for UpNote. It lets Claude Code, Cursor, and other AI agents read and write your UpNote library.
UpNote has no public cloud API, so this server works with the two integration surfaces UpNote exposes locally:
Reads come from UpNote's local SQLite database (read-only).
Note/notebook creation uses UpNote's official
upnote://URL scheme (app-native).Edits, moves, deletes, restores, and tagging are written directly into the SQLite database (guarded by backups and a "quit the app first" preflight), because UpNote provides no edit/delete endpoint.
Features
Read tools:
search_notes- full-text-ish search over title/body, scoped optionally to a notebook or tagget_note- a note's content (markdown / html / text) plus metadata, notebooks, and tagslist_notebooks- the notebook tree with per-notebook note countslist_notes- by filter:recent,notebook,tag,bookmarked,pinned,trashed,templateslist_tags- all tags with note countsget_note_attachments/read_attachment- attachment metadata and bytesget_stats- library counts and the resolved DB path
Write tools:
create_note,create_notebook- via theupnote://URL scheme (works while the app is running)update_note,move_note,delete_note,restore_note,tag_note,untag_note- direct SQLite writes (app should be quit)append_note- append content to the end of a note without rewriting its existing body (safe for rich/image-heavy notes)merge_notes- fold one note into another (append its body under an## <source title>divider, migrate its images so nothing is orphaned, then trash the source)open_in_upnote- open a note or run a search in the app
Related MCP server: bear-mcp
How it works
flowchart LR
agent["Claude Code / Cursor"] -->|stdio MCP| server["upnote-mcp"]
server -->|read-only| readdb[("upnote.sqlite3")]
server -->|"create (default)"| scheme["upnote:// URL scheme"]
scheme --> app["UpNote app"]
server -->|"edit / move / delete (app quit)"| writer["backup + preflight + tx"]
writer --> writedb[("upnote.sqlite3")]
app --> writedbWhen a row is changed directly, the server sets synced = 0, bumps revision, and updates updatedAt. UpNote re-pushes those rows to its sync backend the next time it launches.
Safety model
Editing a live, syncing database is inherently risky. This server defends your data:
Reads use a read-only connection and never lock or modify anything.
Before any direct write it takes a timestamped backup of
upnote.sqlite3(plus-wal/-shm) into aMCP Backups/folder.Direct writes are refused while UpNote is running (it caches rows in memory and could overwrite your change). Quit UpNote first, or set
UPNOTE_ALLOW_WHILE_RUNNING=1to override (not recommended).Each mutation runs in a single transaction and checkpoints the WAL afterward.
Destructive tools accept
dry_run=trueto preview without writing.
Recommended workflow for edits/deletes: quit UpNote, run the changes, then reopen UpNote to let it sync. Creating notes via the default URL scheme is safe while the app is open.
Inbox / whiteboard workflow
A capture-then-triage loop keeps the library tidy:
Capture (app open). Dump quick thoughts as notes into a single
📥 Inboxnotebook — type them in UpNote, or have the agentcreate_notethem. No structure required.Triage (ask the agent). Tell the agent "triage my inbox". It reads every note in
📥 Inbox, then proposes, per note, one of: move to the right notebook, merge into an existing note, or create a new note — and shows the plan for you to review.Accept. On your OK (and with UpNote quit), it applies the plan with
move_note/merge_notes/append_note. A timestamped backup is taken first; merged sources go to Trash (recoverable) and their images are migrated so nothing is orphaned.
merge_notes and append_note make this safe: the destination note's existing content (including images) is preserved verbatim and the new material is appended under a labelled ## divider.
Requirements
UpNote desktop app installed (macOS verified; Windows/Linux best-effort).
uv installed.
Python 3.10+.
Install
git clone https://github.com/bug-breeder/upnote-mcp.git
cd upnote-mcp
uv syncRun it directly (stdio):
uv run upnote-mcpConfiguration
All paths are auto-detected per platform and can be overridden with environment variables.
Variable | Purpose | Default (macOS) |
| Path to |
|
| UpNote data directory |
|
| Attachment blob directory |
|
| Where pre-write backups go |
|
| Create backend: |
|
| Permit direct writes while UpNote runs | unset (off) |
Default data directories: macOS uses the container path above; Windows uses %APPDATA%/UpNote; Linux uses ~/.config/UpNote.
Client setup
Replace the path below with your local clone if different: /Users/alanguyen/Code/Personal/upnote-mcp.
Cursor
Add to ~/.cursor/mcp.json (global) or .cursor/mcp.json (per project):
{
"mcpServers": {
"upnote": {
"command": "uv",
"args": ["run", "--directory", "/Users/alanguyen/Code/Personal/upnote-mcp", "upnote-mcp"]
}
}
}Claude Code
claude mcp add upnote -- uv run --directory /Users/alanguyen/Code/Personal/upnote-mcp upnote-mcpOr add it to .mcp.json with the same command/args shown above.
uvx alternative
If you prefer uvx, use "command": "uvx" with "args": ["--from", "/Users/alanguyen/Code/Personal/upnote-mcp", "upnote-mcp"].
Limitations and caveats
The URL scheme is fire-and-forget:
create_note/create_notebookdo not return the id of what they create. SetUPNOTE_WRITE_MODE=sqliteto create via SQLite and receive the new note id (requires the app to be quit).UpNote must be quit for edits/moves/deletes to be reliable.
Direct writes set
synced = 0so UpNote re-syncs on next launch; the first time you use the write tools, verify a test edit appears correctly in the app before trusting it with important notes.Search is substring-based (
LIKE) over title and plain text, not a ranked full-text index.
Development
uv sync
uv run pytestTests run against a throwaway SQLite database in a temp directory and never touch your real UpNote data.
License
MIT
Available Tools
19 toolsappend_noteA
Append content to the end of a note, preserving its existing body verbatim.
Unlike update_note (which replaces content), this concatenates the new content after the current html, optionally under an '## heading' divider. Safe for rich/image-heavy notes. Direct SQLite write; the app must be quit.
Inputs: content (required), note_id (required), dry_run, heading, +1 more. Returns: object
create_noteA
Create a new note.
By default this uses the upnote:// URL scheme (app-native and safe while UpNote is running); the app generates the note, so no id is returned. Set the UPNOTE_WRITE_MODE=sqlite environment variable to create via direct SQLite instead (requires the app to be quit; returns the new note id). For 'notebook', pass a notebook title (URL mode) or notebook id (sqlite mode).
Inputs: title (required), content, notebook, content_format. Returns: object
create_notebookC
Create a new notebook via the upnote:// URL scheme.
Inputs: title (required). Returns: object
delete_noteA
Delete a note: move to trash (default) or hard-delete with hard=true. Direct SQLite write; the app must be quit. Use dry_run=true to preview.
Inputs: note_id (required), hard, dry_run. Returns: object
get_noteB
Get a single note's full content and metadata in the requested format.
Inputs: note_id (required), format. Returns: object
get_note_attachmentsA
List the attachments/files referenced by a note, with on-disk paths.
Inputs: note_id (required). Returns: result
get_statsA
Return library statistics (counts of notes, notebooks, tags, files) and the DB path.
Inputs: no named parameters. Returns: object
list_notebooksA
List all notebooks as a parent/child tree, with per-notebook note counts.
Inputs: no named parameters. Returns: object
list_notesC
List notes by a filter: recent, notebook (needs notebook_id), tag (needs tag_id), bookmarked, pinned, trashed, or templates.
Inputs: limit, filter, tag_id, notebook_id. Returns: result
list_tagsA
List all tags with their note counts.
Inputs: no named parameters. Returns: result
merge_notesA
Fold one note into another: append the source's body under an '## ' divider, migrate its images so nothing is orphaned, then trash the source.
Ideal for triaging an inbox/whiteboard note into an existing canonical note. Direct SQLite write; the app must be quit. Use dry_run=true to preview.
Inputs: source_note_id (required), target_note_id (required), dry_run, trash_source. Returns: object
move_noteA
Move a note into the given notebook (direct SQLite write; app must be quit).
Inputs: note_id (required), notebook_id (required), dry_run. Returns: object
open_in_upnoteA
Open a note in UpNote (by note_id) or run a search in the app (by query) using the upnote:// URL scheme.
Inputs: query, note_id. Returns: object
read_attachmentA
Read an attachment by file id. Small files are returned inline as base64; large files return their on-disk path instead.
Inputs: file_id (required). Returns: object
restore_noteB
Restore a trashed/deleted note (direct SQLite write; app must be quit).
Inputs: note_id (required), dry_run. Returns: object
search_notesA
Search notes by text in the title/body, optionally scoped to a notebook or tag.
Returns brief records (id, title, snippet, notebooks, flags, timestamps),
most-recently-updated first. Omit query to list recent notes.
Inputs: limit, query, tag_id, notebook_id, +1 more. Returns: result
tag_noteA
Add a tag (by title or id) to a note, creating the tag if needed. Direct SQLite write; the app must be quit.
Inputs: tag (required), note_id (required), dry_run, create_if_missing. Returns: object
untag_noteA
Remove a tag (by title or id) from a note (direct SQLite write; app must be quit).
Inputs: tag (required), note_id (required), dry_run. Returns: object
update_noteA
Edit a note's title/content/flags via direct SQLite write.
Requires the UpNote app to be quit (override with UPNOTE_ALLOW_WHILE_RUNNING=1). A database backup is taken automatically. Use dry_run=true to preview.
Inputs: note_id (required), title, pinned, content, +3 more. Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 19 tools
Each tool targets a distinct operation (create, read, update, delete, search, etc.) on specific resources (notes, notebooks, tags, attachments). No two tools have overlapping purposes; even similar operations like append_note and update_note are clearly differentiated.
All tool names follow a verb_noun pattern in snake_case (e.g., create_note, list_notebooks, tag_note). The naming is consistent across the entire set with no mixing of conventions like camelCase or inconsistent verbs.
With 19 tools, the set is well-scoped for a note-taking application. It covers the full lifecycle of notes, notebooks, tags, and attachments without being bloated. The count aligns with the typical range for a focused domain.
The tool surface covers CRUD for notes, plus advanced operations (merge, append, restore, move) and tag/attachment management. Minor gaps exist—notebook update/delete and direct tag creation are missing—but these are less critical and do not hinder core workflows.
Maintenance
Related MCP Connectors
Search, read, and write your Apple Notes from ChatGPT/Claude via a local Mac agent + MCP relay.
Notes and actions in one app. Let Claude or ChatGPT read and update them.
Create, search, and update notes in an xNotepad AI notebook, with semantic search and AI Q&A.
AI access to your aNotepad online notes: read, search, write, and organize via 22 tools.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to interact with Upnote via its x-callback-url API, allowing creation of notes, notebooks, tag management, and search. It also supports navigation to various Upnote sections and custom filters.26MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to read and write Bear notes using SQLite for queries and x-callback-url for safe writes.1MIT
- FlicenseNot gradedqualityDmaintenanceProvides AI assistants with tools to create, list, search, tag, and delete notes stored in a local SQLite database.-
- AlicenseAqualityCmaintenanceEnables Claude to read and create UpNote notes locally, using a WAL-correct SQLite snapshot for reading and the upnote:// URL scheme for writing, with no account, cloud, or API key required.1016MIT