Skip to main content
Glama

notes-mcp: Virtual Sticky Notes

for Mac

CI

An MCP server that keeps notes on your own machine, stored as plain Markdown files. Connect it to Claude Code or Claude Desktop and you can say "save a note about this" or "what did I write down about the migration?" in the middle of a conversation.

What it exposes

MCP servers offer three kinds of things to a client. This one uses all three, because they do different jobs:

  • Tools are verbs. They are actions the model decides to take. Writing and deleting notes belongs here.

  • Resources are nouns. They are content the client can pull into context, addressed by URI. The model does not have to "decide" to call them; you or the client can attach them directly.

  • Prompts are reusable message templates the user invokes deliberately, usually from a menu.

Tools

Tool

Arguments

Returns

add_note

title, content, tags

The new note's id

list_notes

tag (optional)

A Markdown table: id, title, tags, and a short blurb

search_notes

query, tags, match_all

Matching notes with a short snippet

get_note

note_id

One note in full, including its body

delete_note

note_id

Confirmation

search_notes matches query against note titles and bodies, case-insensitively, and ANDs that with the tag filter. match_all decides whether a note needs every tag listed or just one of them.

Resources

URI

Contents

notes://all

An index of every note: id, title, tags

notes://tags

Every tag in use, with how many notes carry it

note://{note_id}

One note's full Markdown source

note://{note_id} is a resource template — the client fills in {note_id} to read a specific note, rather than the server listing all of them up front. Since ids are small integers, that means note://5 for the fifth note.

Prompt

summarize_notes(tag) gathers your notes, groups them by theme, and pulls out anything that looks like a task or an open question. Pass a tag to narrow it, or leave it empty for everything.

Related MCP server: MCP Notes Server

Setup

Requires uv and Python 3.10+.

git clone https://github.com/pdegner/notes-mcp.git
cd notes-mcp
uv sync

Claude Code

claude mcp add notes -- uv --directory /full/path/to/notes-mcp run notes-mcp

Claude Desktop

Add this to claude_desktop_config.json:

{
  "mcpServers": {
    "notes": {
      "command": "uv",
      "args": ["--directory", "/full/path/to/notes-mcp", "run", "notes-mcp"]
    }
  }
}

Where your virtual sticky notes live

In ~/.notes-mcp/notes/, one Markdown file per note, named {id}.md. Set NOTES_MCP_DIR to put them elsewhere.

---
id: '5'
title: Dentist appointment
tags:
- errands
- health
created: '2026-07-30T17:19:20Z'
updated: '2026-07-30T17:19:20Z'
---

Tuesday 3pm, Dr. Okafor.

Ids are assigned in order starting from 1. Deleting a note frees its number, and the next note you write gets the lowest free number rather than the next-highest — so if you delete note 3 out of five notes, the next note you add becomes the new 3, not 6.

Because a note is just a Markdown file, you can read, edit, grep, or back it up without this server involved. Hand-edited files are read back fine, including a tags: work, urgent shorthand instead of a YAML list.

Your notes stay on this machine. They live outside this repository, so git never sees them and they are never committed or pushed — the repo holds code only. The notes directory is created 0700 and each note file 0600, so other accounts on the machine cannot read them.

They are stored as plain text, not encrypted. That protects against other local users and against accidentally publishing them; it does not protect against anyone who has your login, admin access to the machine, or a backup that copies your home directory. Treat them like sticky notes on your desk, and don't put passwords in them.

Design notes

A few decisions worth explaining:

List and search return metadata, not bodies. Only get_note returns note text. If list_notes returned full bodies, a directory of a few hundred notes would flood the model's context on a single call. Search returns a short snippet around the match instead, which is enough for the model to pick the right note and then ask for it.

Note ids are validated before they touch the filesystem. Ids arrive as tool arguments, which means they come from a model and are untrusted input. Every id is checked against ^[a-z0-9][a-z0-9-]*$ before it is turned into a path, so ../../etc/passwd is rejected rather than resolved. That pattern is broader than the plain integers the server assigns on purpose: a hand-named file dropped into the notes directory stays readable even though the server would never generate an id like that itself. Writes go to a temp file and are then atomically renamed, so an interrupted write can't truncate an existing note.

A note's id never changes for as long as it exists. These are meant to work like sticky notes: you write one down, and if you end up checking it often, note://5 is a fixed address you can always go back to, not something that drifts as other notes come and go. Ids are only ever handed out at creation, so nothing renumbers a note out from under you. Deleting a note frees its number for the next note written after it, rather than leaving a permanent gap; the store scans existing filenames on every add_note call and picks the smallest positive integer not already in use (an O(n) scan, fine for a personal notes directory, not for thousands of notes). That reuse is what keeps ids low and memorable instead of growing forever, but it only ever assigns a freed number to a brand-new note, never to one that's still around.

Nothing prints to stdout. On the stdio transport, file descriptor 1 carries the JSON-RPC message stream, so anything else written there corrupts the framing. The Python SDK does defend against this — mcp.server.stdio claims fd 1 for the transport and repoints the process's own stdout at stderr — but that only happens when the SDK opens the stream itself, and other MCP implementations don't all do it. Treating stdout as reserved and sending logs to stderr is the portable discipline rather than a workaround for one library's behavior.

Development

uv run pytest          # 59 tests
uv run ruff check .    # lint
uv run ruff format .   # format

License

MIT

Available Tools

5 tools
add_noteA

Save a new note.

Args:
    title: Short title for the note.
    content: The body of the note, as Markdown.
    tags: Optional labels for grouping, e.g. ["work", "ideas"].

Inputs: title (required), content (required), tags. Returns: object

delete_noteA

Delete a note permanently.

Args:
    note_id: The note's id, as returned by list_notes or search_notes.

Inputs: note_id (required). Returns: object

get_noteA

Read one note in full, including its body.

Args:
    note_id: The note's id, as returned by list_notes or search_notes.

Inputs: note_id (required). Returns: object

list_notesA

List saved notes, most recently updated first.

Returns titles and tags only, not note bodies. Call get_note to read one.

Args:
    tag: Optional tag to filter by.

Inputs: tag. Returns: result

search_notesA

Find notes by text and/or tags.

Returns matching notes with a short snippet, not their full bodies. Call
get_note to read one in full.

Args:
    query: Text to look for in note titles and bodies (case-insensitive).
    tags: Only return notes carrying these tags.
    match_all: If true, a note must have every listed tag; otherwise any one
        of them is enough.

Inputs: tags, query, match_all. Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.2/5.0

    Scored across 5 tools

    Disambiguation5/5

    Each tool has a clearly distinct purpose: adding, listing (with metadata only), searching (with snippets), getting full content, and deleting. The list and search tools are complementary rather than overlapping, with explicit guidance to call get_note for full bodies.

    Naming Consistency4/5

    Tool names follow a consistent verb_noun pattern, but there is a minor inconsistency in pluralization: add_note, get_note, and delete_note use singular while list_notes and search_notes use plural. Despite this, the pattern is predictable and easy to follow.

    Tool Count5/5

    Five tools is well-scoped for a notes server, covering the essential operations (create, read, list, search, delete) without unnecessary bloat. Each tool has a clear role and contributes to the overall functionality.

    Completeness4/5

    The tool surface covers most of the CRUD lifecycle (add, list, get, search, delete) but lacks an update/edit operation, which is a common requirement for notes. This is a minor gap that can be worked around by deleting and re-adding, but it is not ideal.

    Maintenance

    ActivityInactive
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      D
      maintenance
      A beginner-friendly MCP server for managing personal notes. Enables Claude to create, list, read, search, update, and delete notes saved as Markdown files.
      6
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      A note-taking MCP server for Claude-based agents that provides persistent markdown files for easily referable notes, supporting create, read, update, append, and delete operations.
      5
      Apache 2.0
    • A
      license
      A
      quality
      C
      maintenance
      MCP server that gives Claude Code and other MCP clients persistent memory using plain Markdown notes stored on your disk and optionally synced to cloud storage (iCloud, OneDrive, Google Drive, Dropbox).
      36
      1
      MIT