set-mcp is a local MCP server that lets AI clients (Claude
Desktop, Claude Code, Cursor, …) search and read your notes. It's read-only by default. Read &
add also lets it create pages. A page created under a parent is linked from the end of that
parent, the way the app does it, and that is the only change it makes to a page that already
exists: it can't otherwise edit, move or delete pages yet.
-
Turn on Settings → Agent access.
-
Click Copy prompt and paste it to your agent. The agent adds the server to its own config:
I keep my notes in an app called Set. Add its MCP server so you can search and read them: name: set command: /absolute/path/to/set-mcp It runs locally over stdio and takes no arguments. -
Choose an Access mode: Read only (default) or Read & add.
- Changes apply on the client's next connection. They're stored in an access file, not the client's config.
- Set doesn't need to be running. The client spawns
set-mcp, which reads the notes folder directly. It can miss unsaved edits on the open page (autosave runs after 1.5s idle). - The access file records the notes folder. Set updates it when the notes folder changes and on
launch, so configured clients follow a move.
--notes-dirandSET_NOTES_DIRoverride it.
| Install | Location |
|---|---|
| macOS | Set.app/Contents/MacOS/ |
.deb / .rpm |
/usr/bin/ |
| Windows | install folder |
| AppImage | copied into Set's own folder, since the AppImage mount path changes every launch |
| Dev build | cd src-tauri && cargo build --release --bin set-mcp → target/release/set-mcp |
| Tool | Mode | Does |
|---|---|---|
search_pages |
read | Ranked search over titles, breadcrumbs and body text. Optional context |
list_contexts |
read | Top-level contexts and their page counts |
list_pages |
read | Page tree in sidebar order, paginated by cursor. Optional context |
get_page |
read | A page's full Markdown |
create_page |
read & add | New page: title, optional Markdown body, and a parent_id or context. A child is linked from the end of its parent, as the app does; a locked parent is refused |
create_context |
read & add | New top-level context (folder at the notes root) |
context is case-insensitive. An unknown context is refused with the list of valid ones.
Breadcrumbs start with the context (Work / Standups).
- Off by default. The switch writes an access file to the OS config dir
(
src-tauri/src/mcp/access.rs).set-mcpreads it at startup and exits unless access is on. A missing or malformed file counts as off. - Read is the fallback. A missing or invalid mode means read tools only. In read mode the create tools aren't listed or callable.
- Context scope (not in Settings yet). If the access file's
allowedContextslists contexts, only those are visible.list_contexts,list_pagesandget_pageare filtered, and out-of-scope pages are removed beforesearch_pagesruns. A scoped session can only create pages in its contexts and can't usecreate_context. - Activity log. Every call is appended to a capped log in the same config folder
(
src-tauri/src/mcp/log.rs): queries, pages read or created, refused connections. Settings shows recent entries.
set-mcp speaks JSON-RPC over stdio, with no port or socket. The process that spawns it runs as you
and can already read the notes folder, so a token wouldn't add protection:
| Threat | Does a token help? |
|---|---|
| Another local process reads the notes | No, it can read the folder directly |
| A configured client is malicious | No, it would have the token from its config |
| Prompt injection in a note | No. Read only has nothing to misuse, and Read & add can at most create a page you didn't want, linked from the end of its parent |
| The connected AI leaks your notes | No, that's the access you granted |
The switch isn't a security boundary: anyone who can run set-mcp can read the folder directly. A
network transport would need real auth, Origin validation and DNS-rebinding protection.
src-tauri/src/mcp/unit tests run the server in-process: ranking, pagination, scope, Read & add, refusals.src-tauri/tests/mcp_stdio.rsspawns the real binary: access gate, one-reply-per-line framing, no reply to notifications, notes-folder resolution, a created page found by a later session, and the activity log. Each test uses a temp home directory.
Don't wrap io::stdout() in a BufWriter. Unit tests still pass, but real clients hang waiting for
a buffered reply. The stdio tests catch this.