joplin-mcp
# joplin-mcp
Model Context Protocol (MCP) server for the [Joplin](https://joplinapp.org/) note-taking app.
Designed by belsar.ai to be easy to install & enjoyable to use.
## Video
https://www.youtube.com/watch?v=B3qJa7ycqNM&t=6s
## Platform Support
Available on macOS and Linux. Windows users should use WSL2.
## Architecture
By default, this MCP server exposes two tools:
- `execute_joplin_readonly_script`: For running scripts in read-only mode (inspecting/reading notes and notebooks).
- `execute_joplin_script`: For running scripts that can also perform modifications, creations, or deletions.
This script-based execution pattern follows [Anthropic's recommended pattern for MCP servers](https://www.anthropic.com/engineering/code-execution-with-mcp) and is the most performant, token-efficient way to build an MCP server today.
Scripts execute in a separate runner process isolated at the OS level by [Anthropic Sandbox Runtime](https://github.com/anthropic-experimental/sandbox-runtime). `node:vm` limits the globals available inside that process; the Sandbox Runtime is the security boundary that blocks network access and filesystem writes.
```
joplin api ←http→ broker (allowlisted proxy) ←stdio→ runner (sandboxed)
```
The runner calls `joplin.*` methods as if talking to Joplin directly, but all requests pass through the broker, which only permits a specific set of API methods. The broker is the only piece that can interact with your notes — and nothing else.
## Quick Start
1. Open Joplin & navigate to tools > web clipper > enable web clipper service
2. The Joplin app needs to remain running (minimized is fine)
3. Add the server to your MCP client:
Claude Code:
```bash
claude mcp add --scope user --transport stdio joplin -- npx -y @belsar-ai/joplin-mcp
```
Codex:
```bash
codex mcp add joplin -- npx -y @belsar-ai/joplin-mcp
```
Agy: add this entry to `~/.gemini/config/mcp_config.json` (or configure the same stdio server through `/mcp`):
```json
{
"mcpServers": {
"joplin": {
"command": "npx",
"args": ["-y", "@belsar-ai/joplin-mcp"]
}
}
}
```
4. That's it. Send a test request like "Find my notes about installing Fedora linux".
### Linux Requirements
Install the following system packages before first use:
```bash
# Fedora
sudo dnf install bubblewrap socat ripgrep
# Ubuntu/Debian
sudo apt install bubblewrap socat ripgrep
```
The server will fail with an error message if these are missing.
### Uninstall
```bash
claude mcp remove joplin
```
```bash
codex mcp remove joplin
```
```bash
agy mcp remove joplin
```
## Configuration (Optional)
### Notebook Scope Config
Create `.mcp-config/joplin-mcp.toml` in your project root to scope which notebooks are visible:
```toml
[defaults]
notebook = "Notes"
[scope]
notebooks = ["Notes", "Software"]
```
- `defaults.notebook`: Where new notes go if you don't specify a notebook
- `scope.notebooks`: Only these notebooks are visible to the AI
The config file is discovered by walking up from the current directory (like `.git`).
### Read-Only Lock Mode
You can lock the MCP server down to a strict read-only mode so that no notes or notebooks can be modified, created, or deleted. In this mode, only the `execute_joplin_readonly_script` tool is exposed to the AI, and any attempts to execute write operations are blocked at the process level.
Configure read-only mode in one of two ways:
1. **CLI Flag**: Append `--readonly` to your start command:
```bash
npx -y @belsar-ai/joplin-mcp --readonly
```
2. **Environment Variable**: Set `JOPLIN_READONLY=true` (any value other than `false`, `0`, `no`, `off`, or empty string will enable lock mode).
## Example Usage
The AI can now handle complex requests in a single shot:
```
Find my notes about installing Arch Linux.
```
```
Look for all notes whose last update time was before 2025 and move them to my Archive notebook.
```
```
Show me all notes in my Work Projects notebook.
```
```
Make a new note with a Mermaid diagram showing how a bill is passed on Capitol Hill.
```
### Working with Large Notes
The AI can navigate, search, and edit large notes without pulling the entire body into context:
```
Show me the table of contents for my platform docs note.
```
```
Show me section 10.1 of my platform docs note.
```
```
Search my project plan for "deadline".
```
```
Replace "Q3 2025" with "Q4 2025" in my roadmap note.
```
## Available API (Script Context)
The AI has access to a global `joplin` object with the following methods:
### Notes (`joplin.notes`)
- `readNote(id)`: Pretty-printed note with metadata header and body. Preferred for display.
- `getNote(id)`: Raw note object. Prefer `readNote()` for display.
- `searchNotes(query: string)`: Smart search with "any:1" logic.
- `listAllNotes(fields?, ...)`: Get all notes.
- `createNote(title, body, notebookId, ...)`: Create a new note.
- `updateNote(id, updates)`: Update properties or body.
- `appendToNote(id, text)`: Add text to the end.
- `prependToNote(id, text)`: Add text to the beginning.
- `editNote(id, oldString, newString, replaceAll?)`: Server-side string replacement. Fails if not found or ambiguous without `replaceAll`.
- `getNoteLineRange(id, startLine, endLine)`: Read a slice of a note by line number (1-indexed).
- `searchInNote(id, pattern)`: Case-insensitive search within a note. Returns matches with line numbers and context.
- `getNoteSections(id)`: Parse markdown headings into a table of contents with line numbers.
- `deleteNote(id)`: Move to trash.
- `moveNoteToNotebook(id, notebookId)`: Move a note to a different notebook.
### Notebooks (`joplin.notebooks`) — read-only
- `listNotebooks()`: Get folder structure.
- `getNotebook(id)`: Get a single notebook.
- `getNotebookNotes(notebookId, fields?, ...)`: Get notes in a notebook.
- `getNotebookTree(notebookId, depth?)`: Get formatted tree of a notebook with notes.
- `getAllNotebooksTree({ exclude? })`: Get formatted tree of all notebooks.
- `getScopedTree({ exclude?, depth? })`: Get formatted tree of scoped notebooks with notes.
## Troubleshooting
- Verify Joplin desktop app is running
- Confirm Web Clipper is enabled in Joplin settings
- Ensure Joplin is listening on port 41184 (default)
- Go outside for a nice walk
TDQS
Scored across 2 tools
The two tools have nearly identical names and both execute JavaScript, differing only in permissions (read-only vs. write). The descriptions are detailed enough to distinguish them, but the overlap in core functionality could cause initial confusion for an agent.
Both tool names follow a consistent 'execute_joplin_<permission>_script' pattern, using snake_case and clear permission labels (readonly vs. script). No mixing of conventions.
Only 2 tools for a server that exposes a full API (CRUD for notebooks, notes, search, etc.) feels minimal. While the design intentionally wraps everything into two script executors, a more granular tool surface might be expected given the scope.
All operations available through the Joplin API are accessible via the two tools (read-only in one, write in the other). No obvious gaps in functionality; the set fully covers the domain.