Skip to main content
Glama
devkievskiy

notes-mcp-server-l16

by devkievskiy
README.md
# Lesson 16 — Notes MCP Server

A Model Context Protocol (MCP) server that gives Claude a persistent personal
notebook. Notes are stored on disk as JSON, so they survive restarts and are
shared across every tool and resource — no stubbed responses.

- **Stack:** Python 3.10+ · official `mcp` SDK (FastMCP) · **stdio** transport
- **Theme:** Notes (recommended option)

## Tools

| Tool | Arguments | Required? | What it does |
|---|---|---|---|
| `add_note` | `text: str`, `tags: list[str]` | `text` **required**, `tags` *optional* | Create a note (optionally tagged) |
| `search_notes` | `query: str` | **required** | Case-insensitive full-text search over text + tags |
| `list_notes` | `tag: str` | *optional* | List all notes, or only those with a given tag |
| `delete_note` | `note_id: int` | **required** | Delete a note by id |

(Satisfies the rubric: ≥2 tools, one with a required-only arg, one with an optional arg.)

## Resources

| Resource URI | What it returns |
|---|---|
| `notes://all` | All notes as a JSON array |
| `notes://stats` | Computed stats: total count, tag frequencies, most-recent note |
| `note://{note_id}` | A single note by id (templated resource) |

## Storage

Notes live in a JSON file, default `~/.notes_mcp/notes.json`. Override with the
`NOTES_DB_PATH` environment variable (set in the Claude Desktop config below).

## Setup

### 1. Install the dependency

```bash
# from this folder, using the course venv (or any Python 3.10+ env)
pip install "mcp[cli]"
```

### 2. (Optional) try it locally with the MCP Inspector

```bash
mcp dev server.py
```

This opens a local web inspector where you can call the tools and read the
resources without Claude Desktop — handy for verifying everything works.

### 3. Connect to Claude Desktop

1. Install Claude Desktop from https://claude.ai/download
2. Open its config file (Claude Desktop → Settings → Developer → Edit Config, or
   directly at `~/Library/Application Support/Claude/claude_desktop_config.json`
   on macOS).
3. Merge in the contents of [`claude_desktop_config.json`](./claude_desktop_config.json).
   Adjust the three absolute paths to match your machine:
   - `command` → your Python interpreter (the one where `mcp` is installed)
   - `args[0]` → the absolute path to `server.py`
   - `env.NOTES_DB_PATH` → where you want the notes file
4. **Fully quit and reopen Claude Desktop.** The `notes` server should appear
   under the tools (🔌 / hammer icon) in the chat input.

## Example dialogs

> **1 — Add notes (required + optional args)**
> 👤 Add a note "Buy oat milk and coffee beans" tagged shopping and groceries.
> 🤖 *(calls `add_note(text="Buy oat milk and coffee beans", tags=["shopping","groceries"])`)*
> Saved note #1. Tags: shopping, groceries.
> 👤 Also note "Read the MCP spec" — no tags.
> 🤖 *(calls `add_note(text="Read the MCP spec")`)* Saved note #2.

> **2 — Search & filter**
> 👤 Which of my notes mention coffee?
> 🤖 *(calls `search_notes(query="coffee")`)*
> 1 match for 'coffee': #1 Buy oat milk and coffee beans [tags: shopping, groceries]
> 👤 Show me everything tagged shopping.
> 🤖 *(calls `list_notes(tag="shopping")`)* … lists note #1.

> **3 — Resource + cleanup**
> 👤 Give me a summary of my notebook.
> 🤖 *(reads the `notes://stats` resource)* You have 2 notes; tags: shopping (1),
> groceries (1); most recent is "Read the MCP spec".
> 👤 Delete the shopping note.
> 🤖 *(calls `delete_note(note_id=1)`)* Deleted note #1.

(See `examples/` for real screenshots of these in Claude Desktop.)

## Known limitations

- **Single-user, local only.** One JSON file, no auth, no multi-user support.
  Concurrent writes from multiple server instances could race (last-write-wins);
  fine for a single Claude Desktop client.
- **No full-text index.** `search_notes` is a linear substring scan — perfectly
  fast for hundreds/thousands of notes, not meant for millions.
- **No edit/update tool.** You can add, search, list, and delete; editing means
  delete + re-add. (Easy to extend.)
- **Plain substring search**, not semantic — "groceries" won't match "food"
  unless that word is present.
- **stdio transport only.** Designed to be launched by Claude Desktop; not
  exposed as a network service.