Skip to main content
Glama
Joplin MCP Server

Joplin MCP Server

by Darkflib

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 (package mcp_joplin_streamable_sse). The command is now mcp-joplin and the package mcp_joplin. The old mcp-joplin-streamable-sse command 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/mcp

  • GET /health: readiness. 200 only when Joplin's Web Clipper service is reachable and accepts JOPLIN_TOKEN; otherwise 503 with {"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. Always 200 while 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} (plain joplin-folders://tree for 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 to application/octet-stream). Subject to MCP_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_get

    • notes_list

    • notes_create

    • notes_append: add text to the end of a note without touching the rest

    • notes_replace_text: replace exact text; refused unless it matches exactly expected_count times (default 1)

    • notes_update: rename, move, or replace the whole body

    • notes_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_list

    • folders_tree

    • folders_create

    • folders_update

    • folders_delete

  • Tags:

    • tags_list

    • tags_create

    • tags_delete

    • tags_add_note

    • tags_remove_note

  • Resources (attachments):

    • resources_list

    • resources_get

    • resources_read: the attachment as native image/audio/text/file content

    • resources_get_content: base64 in JSON (kept for compatibility)

    • resources_create

    • resources_delete

    • notes_list_resources

    • notes_attach_resource

    • notes_detach_resource

  • Search:

    • search

Installation

Setup (uv)

  1. Create a .env from the example:

    • copy .env.example to .env

  2. Fill in:

    • JOPLIN_TOKEN

    • MCP_API_KEY (for serve only; at least 12 characters, e.g. python3 -c 'import secrets; print(secrets.token_urlsafe(32))')

  3. 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 port

Exit 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/EFGH789

Read-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

off (default)

Never ask; deletes work as before.

ask

Ask whenever the client can show a prompt; otherwise delete as before.

require

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 under ask those deletes go ahead unprompted, and under require they 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=true for clients that only need to read, and MCP_CONFIRM_DELETES=ask (or require) for ones that write

  • Rotate 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"

Maintenance

ActivityMaintained
ResponsivenessSyncing

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    A
    maintenance
    Enables AI assistants to interact with Joplin notes, notebooks, and tags through a standardized MCP interface, supporting CRUD operations, search, and organization.
    19
    194 PyPI
    174
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI assistants to interact with Joplin notes and notebooks, including searching, reading, and listing notebooks through natural language commands via the MCP protocol.
    192 npm
    50
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    A 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.
    13
    MIT