GoodNotes MCP Server
# GoodNotes MCP Server
An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that reads handwritten notes from [GoodNotes](https://www.goodnotes.com/) on macOS. It exposes your handwritten notebooks as structured data that AI assistants like Claude can read, search, and process.
## How It Works
GoodNotes stores all data in local SQLite databases on macOS:
- **`projection.sqlite`** — document metadata (names, folders, page ordering)
- **`fts.sqlite`** — full-text search index with OCR'd handwriting (multiple recognition candidates per word)
This MCP server reads those databases (read-only) and exposes 6 tools:
| Tool | Description |
|------|-------------|
| `list_notebooks` | List all notebooks with IDs, page counts, dates |
| `read_notebook` | Read OCR text from a notebook (supports page ranges) |
| `read_page` | Read OCR text from a single page |
| `search_notes` | Full-text search across all handwritten notes |
| `get_unprocessed` | Find new/changed pages since last processing |
| `mark_processed` | Mark pages as processed (for pipeline workflows) |
### OCR Candidate Format
GoodNotes OCR produces multiple word candidates. The server returns them separated by `|`:
```
Temple|Tomple|temple Voice|Voica recognition
```
Your AI assistant picks the best word using semantic context — much more accurate than taking the top candidate alone.
## Requirements
- macOS (GoodNotes stores its databases locally)
- [GoodNotes](https://www.goodnotes.com/) installed and synced
- Python 3.11+
- [uv](https://docs.astral.sh/uv/) (recommended) or pip
## Installation
```bash
git clone https://github.com/withsivram/goodnotes-mcp.git
cd goodnotes-mcp
uv venv && uv pip install -e .
```
## Configuration
### Claude Code
Add to your Claude Code MCP settings:
```json
{
"mcpServers": {
"goodnotes": {
"type": "stdio",
"command": "/path/to/goodnotes-mcp/.venv/bin/python",
"args": ["/path/to/goodnotes-mcp/server.py"]
}
}
}
```
### Claude Desktop
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"goodnotes": {
"command": "/path/to/goodnotes-mcp/.venv/bin/python",
"args": ["/path/to/goodnotes-mcp/server.py"]
}
}
}
```
### Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `GOODNOTES_DB_DIR` | `~/Library/Containers/com.goodnotesapp.x/Data/Library/Databases` | Path to GoodNotes SQLite databases |
| `GOODNOTES_TRACKING_FILE` | `~/.goodnotes-mcp/processed.json` | Path to processing state file |
## Usage
Once configured, your AI assistant can:
1. **List your notebooks**: "What notebooks do I have in GoodNotes?"
2. **Read notes**: "Read my latest notebook"
3. **Search**: "Search my handwritten notes for 'meeting action items'"
4. **Process pipeline**: Use `get_unprocessed` + `mark_processed` to build automated workflows (e.g., handwriting → structured Obsidian notes)
### Example: Processing Notes into Obsidian
The server is designed as a minimal data pipe — all intelligence (OCR resolution, categorization, structuring) happens in the AI assistant. A typical workflow:
1. `get_unprocessed` → find new pages
2. `read_notebook` → get raw OCR with word candidates
3. AI resolves `Temple|Tomple|temple` → "Temple" using context
4. AI categorizes and structures into markdown
5. Write to Obsidian (or any markdown-based system)
6. `mark_processed` → track what's been handled
## Architecture
```
iPad (GoodNotes) → iCloud Sync → macOS SQLite DBs → MCP Server → AI Assistant
```
The server is intentionally minimal (~390 lines, zero external dependencies beyond `mcp`). All intelligence lives in the AI layer, making the server easy to maintain and extend.
## License
MIT License — see [LICENSE](LICENSE).
TDQS
Scored across 6 tools
read_notebook and read_page overlap significantly, as read_notebook can read a single page using page_start/page_end, causing potential misselection. The other tools (list, get_unprocessed, search, mark_processed) are clearly distinct.
Tool names follow a snake_case verb_noun pattern, but verbs vary (list, read, get, search, mark) and 'get_unprocessed' and 'mark_processed' use adjectives rather than clear nouns, creating minor inconsistency.
With 6 tools, the set is well-scoped for the server's purpose of reading, searching, and tracking GoodNotes content. It is neither too sparse nor overly heavy.
The core workflow of listing notebooks, reading pages, searching, retrieving unprocessed pages, and marking them processed is covered. Minor gaps include redundancy between read_notebook/read_page and no explicit tool for listing page IDs independently.