Joplin MCP Server
Allows interaction with the Joplin knowledge manager, providing tools for searching, retrieving, and managing notes, notebooks, and tags through the Joplin API.
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
mcp-joplin: Joplin MCP server for LLM integration
This project provides a Streamable HTTP MCP server over the Joplin Data API (Web Clipper). It enables MCP clients (including coding agents/LLMs) to read and manage notes, folders, tags, search, and attachments.
Renamed in 0.2.0. This project was
mcp-joplin-streamable-sse(packagemcp_joplin_streamable_sse). The command is nowmcp-joplinand the packagemcp_joplin. The oldmcp-joplin-streamable-ssecommand still works, prints a deprecation note to stderr, and will be removed in a future major release. Settings and environment variables are unchanged.
Features
Remote Joplin access via MCP Streamable HTTP
API-key protected MCP endpoint (
X-API-Key)Notes CRUD
Folder (notebook) list/tree/create/update/delete
Tag list/create/delete + attach/remove tag on note
Search across Joplin items
Attachment/resource support:
list/get/create/delete resources
list note resources
attach/detach resource links in note bodies
read attachments as native content (images, audio, text, files)
Live note-change notifications for MCP 2026-07-28 clients (
subscriptions/listen)
Related MCP server: Joplin MCP Server
Protocol support
Built on the MCP Python SDK 2.x. One endpoint serves both:
MCP 2026-07-28 clients: stateless single-exchange requests (no
initialize, no session id).Earlier protocol versions (handshake-based, e.g. 2025-11-25): served statelessly with JSON responses.
Clients must address the server as 127.0.0.1, localhost or [::1] with an explicit port;
the SDK's DNS rebinding protection rejects any other Host header, or one without a port, with 421.
Runtime endpoints
MCP endpoint:
http://$MCP_HOST:$MCP_PORT/mcpGET /health: readiness.200only when Joplin's Web Clipper service is reachable and acceptsJOPLIN_TOKEN; otherwise503with{"ok": false, "joplin": "unreachable" | "unauthorized" | "error", "detail": ...}. The probe result is cached for 15 s, and concurrent requests share a single probe, so polling it is cheap. Every state change is logged with its reason.GET /health/live: liveness. Always200while the process is serving.
Both health endpoints are unauthenticated and return no secrets or note data.
All MCP requests must include:
X-API-Key: $MCP_API_KEY
Exposed MCP resources
joplin-note://{note_id}joplin-folders://tree{?root_id}(plainjoplin-folders://treefor the whole tree;?root_id=<folder id>for a subtree)joplin-resource://{resource_id}: an attachment's bytes, served with its own MIME type (falls back toapplication/octet-stream). Subject toMCP_MAX_ATTACHMENT_BYTES.
Attachments
resources_read returns an attachment in the form a model can use directly: PNG, JPEG, GIF
and WebP as images, audio/* as audio, text files (text/*, JSON, XML, YAML, SVG) as text,
and anything else as an embedded file, alongside a one-line summary (name, type, size).
It and joplin-resource:// refuse attachments larger than MCP_MAX_ATTACHMENT_BYTES
(default 10 MiB): first from Joplin's recorded size, without downloading, then by counting
the download itself in case that size is wrong.
resources_get_content is unchanged (base64 inside JSON, no size cap) so existing callers
keep working; new callers should prefer resources_read.
Note-change notifications
MCP 2026-07-28 clients can open a subscriptions/listen stream and subscribe to
joplin-note://{note_id} URIs; they receive notifications/resources/updated when that note
is created, edited or deleted, from Joplin or anywhere else.
Joplin has no push API, so the server follows Joplin's /events change log every
MCP_EVENTS_POLL_SECONDS (default 5; 0 turns it off) — but only while at least one listen
stream is open, so an idle server makes no extra Joplin requests. Changes are de-duplicated per
poll. Delivery is best-effort with no replay, as the protocol specifies: a client that
reconnects should re-read what it cares about. Polling backs off (up to 60 s) while Joplin is
unreachable and logs only when it starts failing and when it recovers.
Clients on earlier protocol versions have no listen stream and receive no notifications; they keep reading notes on demand as before.
Exposed MCP tools
Every tool carries MCP annotations (readOnlyHint, destructiveHint, idempotentHint), so
clients can auto-approve reads and ask before destructive calls.
Notes:
notes_getnotes_listnotes_createnotes_append: add text to the end of a note without touching the restnotes_replace_text: replace exact text; refused unless it matches exactlyexpected_counttimes (default 1)notes_update: rename, move, or replace the whole bodynotes_delete
notes_update, notes_append, notes_replace_text and notes_detach_resource accept an
optional expected_updated_time (from your last read). If the note changed since then, the
edit is refused instead of overwriting the other change. This is best-effort: Joplin has no
conditional write, so a change landing between the server's read and its write can still be
overwritten.
Folders:
folders_listfolders_treefolders_createfolders_updatefolders_delete
Tags:
tags_listtags_createtags_deletetags_add_notetags_remove_note
Resources (attachments):
resources_listresources_getresources_read: the attachment as native image/audio/text/file contentresources_get_content: base64 in JSON (kept for compatibility)resources_createresources_deletenotes_list_resourcesnotes_attach_resourcenotes_detach_resource
Search:
search
Installation
Setup (uv)
Create a .env from the example:
copy .env.example to .env
Fill in:
JOPLIN_TOKEN
MCP_API_KEY (for
serveonly; at least 12 characters, e.g.python3 -c 'import secrets; print(secrets.token_urlsafe(32))')
Install dependencies:
uv sync --all-extras
Run
uv run mcp-joplin # same as `serve`
uv run mcp-joplin serve # Streamable HTTP on $MCP_HOST:$MCP_PORT (needs MCP_API_KEY)
uv run mcp-joplin stdio # MCP over stdin/stdout for clients that launch servers
uv run mcp-joplin doctor # check config, Joplin reachability/token, and the portExit codes: 0 ok, 1 runtime failure (or a failed doctor check), 2 invalid configuration.
Configuration errors are a one-line message naming the setting, never its value.
Run under the Joplin sidecar launcher
joplin-sidecar-launcher (0.4+) starts the
server with Joplin, supplies JOPLIN_BASE_URL/JOPLIN_TOKEN from its own settings, and stores
MCP_API_KEY encrypted (key in the OS keychain) rather than in a plaintext .env:
{
"name": "Joplin MCP",
"command": "/opt/homebrew/bin/uv",
"args": ["run", "--project", "/Users/you/mcp-joplin", "mcp-joplin"],
"cwd": "/Users/you/mcp-joplin",
"healthUrl": "http://127.0.0.1:5005/health",
"env": { "MCP_HOST": "127.0.0.1", "MCP_PORT": "5005" },
"secretEnv": ["MCP_API_KEY"],
"autostart": true
}Then Secrets → MCP_API_KEY → Generate and paste the copied key into your MCP client. Use an
absolute command path (Joplin started from the Dock doesn't inherit your shell PATH).
Because healthUrl points at the readiness endpoint, the panel shows unhealthy when Joplin's
Web Clipper service is off or the token has been revoked. Expand the log to see why. Point it at
/health/live instead if you only want to know that the process is up.
Connect an MCP client
Streamable HTTP, with the API key as a header. For example, Claude Code:
claude mcp add --transport http joplin http://127.0.0.1:5005/mcp --header "X-API-Key: <key>"or a client JSON config that supports headers:
{
"mcpServers": {
"joplin": {
"type": "http",
"url": "http://127.0.0.1:5005/mcp",
"headers": { "X-API-Key": "<key>" }
}
}
}Address the server as 127.0.0.1, localhost or [::1] with an explicit port. Any other
Host, or one without a port, is rejected with 421 (DNS rebinding protection).
For clients that can only launch stdio servers, run stdio directly (no sidecar, no API key).
Note that JOPLIN_TOKEN then lives in that client's config:
{
"mcpServers": {
"joplin": {
"command": "/opt/homebrew/bin/uv",
"args": ["run", "--project", "/Users/you/mcp-joplin", "mcp-joplin", "stdio"],
"env": { "JOPLIN_TOKEN": "<joplin web clipper token>" }
}
}
}Development
Install (with dev extras):
uv sync --all-extras
Run tests:
uv run python -m pytest -q
References
Joplin Data API reference -- the API documentation for Joplin, which provides details on how to interact with your notes and other data. Note: this is the data API, the plugin API is different and not used here.
In the documentation below, the token may not be specified every time however you will need to include it.
curl http://localhost:41184/notes?token=ABCD123ABCD123ABCD123ABCD123ABCD123
curl --data '{ "title": "My note", "body": "Some note in **Markdown**"}' http://localhost:41184/notes
curl -X DELETE http://localhost:41184/tags/ABCD1234/notes/EFGH789Read-only mode
Set MCP_READ_ONLY=true to register only the tools that read the notebook (get, list, tree,
search, attachment metadata/content). Tools that create, edit or delete are not offered at all,
so they don't appear in tools/list. Resources are unaffected. The default is false.
Planned change: a future major release may make read-only the default, so that writing has to
be switched on deliberately. It stays off for now because flipping it would silently remove
tools from existing deployments; set MCP_READ_ONLY explicitly if you want either behaviour
to survive that change.
Under the sidecar launcher, add it to the entry's env:
"env": { "MCP_READ_ONLY": "true", ... }.
Delete confirmations
Set MCP_CONFIRM_DELETES to have the user approve each delete (notes_delete,
folders_delete, tags_delete, resources_delete) before it happens. The client shows a
prompt naming the item, e.g. Delete the notebook “Work” and every note in it?
Value | Behaviour |
| Never ask; deletes work as before. |
| Ask whenever the client can show a prompt; otherwise delete as before. |
| Ask, and refuse to delete when the client can't ask. |
The prompt's Delete box starts unticked: only accepting with it ticked deletes. Declining, cancelling, or accepting with the box unticked keeps the item, and the model gets an error saying it was not deleted. The model never sees or fills the confirmation: it isn't a tool argument.
Whether a client "can ask" depends on the client and the connection:
MCP 2026-07-28 clients that support elicitation can always be asked, over HTTP or stdio.
Earlier clients need the server to send them a request mid-call. That works over
stdio, but not over the stateless HTTP endpoint, so underaskthose deletes go ahead unprompted, and underrequirethey are refused.
It is off by default so existing setups behave as before; ask is the recommended setting
for interactive use. Under the sidecar launcher: "env": { "MCP_CONFIRM_DELETES": "ask", ... }.
Security
Do not commit .env
Use
MCP_READ_ONLY=truefor clients that only need to read, andMCP_CONFIRM_DELETES=ask(orrequire) for ones that writeRotate JOPLIN_TOKEN if it was ever shared or committed
Keep MCP bound to 127.0.0.1 unless you have additional network protections
The Joplin token is redacted from tool errors and logs (Joplin only accepts it as a URL query parameter, so request URLs are never logged)
History
This repository held an earlier, stdio-only server: four read-only tools on MCP SDK 1.x,
unmaintained and superseded by this one. Both histories are kept here. The last version of
that server is commit
6d34444,
released under the MIT licence. It uses SDK 1.x APIs, so pin the SDK if you run it:
git checkout 6d3444464c172a3844e49ea4cc06701ce2b535f8
uv venv && uv pip install -e . "mcp>=1.0.0,<2"This server cannot be deployed
Maintenance
Related MCP Connectors
Markdown-based note-taking with a hosted MCP server. Your notes serve you and your AI.
Google Keep-style notes app with an MCP server for AI agents to read/write notes.
Persistent memory layer for AI tools. Save and recall notes across Claude and other MCP clients.
MCP-native notes and memory for ChatGPT, Claude, and other AI tools.
Related MCP Servers
- AlicenseAqualityAmaintenanceEnables AI assistants to interact with Joplin notes, notebooks, and tags through a standardized MCP interface, supporting CRUD operations, search, and organization.19194 PyPI174MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI assistants to interact with Joplin notes and notebooks, including searching, reading, and listing notebooks through natural language commands via the MCP protocol.192 npm50MIT
- AlicenseNot gradedqualityAmaintenanceMCP server for Joplin Server that gives LLMs full access to notes, notebooks, tags, and attachments via the REST API.6MIT
- AlicenseAqualityAmaintenanceA minimal MCP server that enables interaction with Joplin notes and notebooks through the local Web Clipper REST API, providing tools for search, create, update, and note management.13MIT