Joplin MCP Server
What it does
An MCP (Model Context Protocol) server for Joplin. A single Docker container syncs with your Joplin Server and exposes that workspace to MCP-aware clients via 17 tools covering notes, notebooks, tags, search, and sync status. Your AI client can list, read, search, create, edit, and delete notes, notebooks, and tags; writes persist locally and reach Joplin Server on the next scheduled sync.
Where it excels
- Single-container deployment. One image runs the Joplin CLI, the Data API, the sync scheduler, and the MCP HTTP server; only port 3000 is published, bound to loopback.
- E2EE handled inside the container. With
JOPLIN_MASTER_PASSWORDset, the container decrypts after the initial sync at boot and again after every periodic sync — clients get plaintext titles, not ciphertext. - Sync-safety guarantees. Every sync is flock-serialized to prevent overlap; a deletion circuit-breaker halts sync after mass deletions; a persistent
.sync-haltmarker refuses further syncs until you intervene. - Loopback-only Data API. The Joplin Data API binds to
127.0.0.1inside the container and is never published to the host or the Docker network. - Token auto-provisioning. The Data API token is extracted from the Joplin CLI config at startup — no manual retrieval from container logs.
- Stateless MCP transport. Each request is handled independently with no session state, so any MCP client that speaks HTTP can connect.
What it does not do
- Not a Joplin client. It exposes an existing Joplin Server account to MCP clients; it is not a note-taking app and not a full Joplin replacement.
- Not multi-user. One container serves one Joplin account with one API token.
syncdoes not force a sync. Thesynctool returns a status pointer; actual syncing is scheduled by the container entrypoint everySYNC_INTERVAL_SECONDS.search_noteshas an upstream gap. Joplin's search index misses notes written through the Data API — see laurent22/joplin#11631.- Sync needs a compatible client version. Joplin Server enforces a minimum client version; if the image's pinned Joplin CLI falls behind your server, sync fails silently until you upgrade the image.
- The first destructive sync cannot be prevented. A known upstream race can damage the profile on its first occurrence; the container limits the damage, it cannot prevent it (
docs/sync.md). - No URL-scheme enforcement.
JOPLIN_SERVER_URLaccepts any valid URL scheme; front the MCP endpoint with TLS yourself if you expose it beyond loopback.
How to install it via image
You need Docker and a reachable Joplin Server. To run the published image:
docker run -d \
--name joplin-mcp \
--restart unless-stopped \
-p 127.0.0.1:3000:3000 \
-v joplin_data:/home/joplin/.config/joplin \
-e JOPLIN_SERVER_URL=https://joplin.example.com/ \
-e [email protected] \
-e JOPLIN_PASSWORD=your-password \
ghcr.io/gelse/joplin-mcp:latest
- The
joplin_datavolume stores the Joplin profile and SQLite database — keep it, or your notes re-sync from scratch on the next start. - If Joplin Server runs on the same host, use
https://host.docker.internal:22300asJOPLIN_SERVER_URL. - To track unreleased builds, use the
latest-testingtag instead oflatest. - The container always listens on port 3000; to publish a different host port, change the left side of
-p(seeMCP_HOST_PORTbelow).
MCP client configuration
The container serves MCP over HTTP (not stdio). Point your MCP client at the published port:
{
"mcpServers": {
"joplin": {
"url": "http://localhost:3000/mcp"
}
}
}
Environment variables
JOPLIN_CORE_URL is set internally by the entrypoint and is not operator-facing — see Migration.
Other documentation
- Architecture
- E2EE setup
- MCP tools
- Sync behaviour & safety
- Security
- Testing & CI
- Development
- Troubleshooting
- Migration