AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
MCP verified MIT Self-run

Joplin Mcp

mcp-gelse-joplin-mcp · by gelse

An MCP server to access a Joplin server

No reviews yet
0 installs
37 views
0.0% view→install

Install

$ agentstack add mcp-gelse-joplin-mcp

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution Used
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/mcp-gelse-joplin-mcp)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude DesktopCursorWindsurf

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Joplin Mcp? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Joplin API MCP Server

An MCP (Model Context Protocol) server that exposes Joplin's note-taking functionality — notes, folders, tags, search, and sync — to AI assistants via 17 tools.

tl;dr / Quick Start

Docker (recommended)

The recommended deployment uses two containers — a stateful sync backend ([joplin-core](#architecture)) and a stateless MCP HTTP server ([joplin-mcp](#architecture)) — orchestrated via [docker-compose.yml](docker-compose.yml):

cp .env.example .env   # fill in required variables
docker compose up -d   # starts both containers with healthchecks

Add this to your MCP client config:

{
  "mcpServers": {
    "joplin": {
      "url": "http://localhost:3000/mcp"
    }
  }
}

> joplin-mcp exposes an HTTP endpoint on port 3000 (not stdio). See [MCP Client Configuration](#mcp-client-configuration) for other setups.

Native Installation

For local development or without Docker, the MCP HTTP server ([src/mcp/entry.ts](src/mcp/entry.ts)) connects to a running Joplin Data API:

git clone  && cd joplin-api
cp .env.example .env   # fill in JOPLIN_API_TOKEN and JOPLIN_CORE_URL
pnpm install
pnpm build && pnpm start

MCP client config (HTTP):

{
  "mcpServers": {
    "joplin": {
      "url": "http://localhost:3000/mcp"
    }
  }
}

Detailed How-To

Direct Installation

Prerequisites
  • Node.js 20 or later (the project's [package.json](package.json) engines field requires >=22.0.0)
  • pnpm 9 or later (for package management)
  • Joplin desktop app running with the Data API (ClipperServer) enabled:
  • In Joplin: Web Clipper → Options → Enable Clipper Server
  • The server binds to 127.0.0.1:41184 by default and ignores --host/--port flags
  • Joplin Server (optional but recommended) — a sync target for multi-device synchronisation. Without it, write-through sync will fail and notes remain local-only
Installation
git clone 
cd joplin-api
pnpm install
pnpm build
Configuration

Copy the environment template and fill in your values:

cp .env.example .env

All configuration is done via environment variables:

| Variable | Required | Default | Description | | ----------------------- | -------- | ------- | ------------------------------------------------------------ | | JOPLIN_SERVER_URL | Yes | — | Joplin Server URL (e.g., https://joplin.example.com/) | | JOPLIN_USERNAME | Yes | — | Joplin Server username/email | | JOPLIN_PASSWORD | Yes | — | Joplin Server password | | JOPLIN_DATA_API_PORT | No | 41184 | Internal Data API listen port (Joplin ClipperServer hardcoded default) | | LOG_LEVEL | No | info | Log level: debug, info, warn, error, silent | | SYNC_INTERVAL_SECONDS | No | 300 | Periodic sync interval in seconds | | NODE_ENV | No | — | Set to production to enforce HTTPS for JOPLIN_SERVER_URL |

> Note: JOPLIN_API_TOKEN is not a user-facing variable. The [core entrypoint script](entrypoint-core.sh) automatically extracts it from Joplin's config (joplin config api.token) and exports it for the server. If running natively without the entrypoint, you must set JOPLIN_API_TOKEN manually (run joplin config api.token in your terminal to get it).

Running the Server
# Production (compiled)
pnpm build && pnpm start

# Development (hot reload via tsx watch)
pnpm dev

The MCP HTTP server connects to a running Joplin Data API (via JOPLIN_CORE_URL), validates connectivity, then begins serving MCP requests over HTTP on port 3000 (configurable via MCP_PORT).

MCP Client Configuration (Native / Node.js)

The MCP HTTP server exposes an HTTP endpoint (not stdio). Configure your MCP client to connect via URL:

{
  "mcpServers": {
    "joplin": {
      "url": "http://localhost:3000/mcp"
    }
  }
}
Testing
# Run all tests
pnpm test

# Watch mode
pnpm test:watch

# Lint
pnpm lint

# Format
pnpm format

Tests use Vitest and cover all modules: config parsing, CLI executor, data client, error classes, sync manager, pagination, MCP schemas, tool handlers, and integration tests against a live Joplin Data API.

Docker

Prerequisites
  • Docker and Docker Compose installed on your system
  • The [.env.example](.env.example) file copied to .env and configured with your Joplin Server credentials

The deployment uses two Dockerfiles ([Dockerfile.core](Dockerfile.core) and [Dockerfile.mcp](Dockerfile.mcp)) orchestrated via [docker-compose.yml](docker-compose.yml).

Building
# Build both containers
docker compose build

# Or build individually
docker compose build joplin-core
docker compose build joplin-mcp
Running
docker compose up -d   # starts joplin-core first, then joplin-mcp after healthcheck passes
Viewing Logs
docker compose logs -f               # both containers
docker compose logs -f joplin-core   # sync/Data API logs only
docker compose logs -f joplin-mcp    # MCP HTTP server logs only
Stopping
docker compose down
Environment Variables

Place variables in the .env file (automatically picked up by [docker-compose.yml](docker-compose.yml)).

| Variable | Container | Required | Default | Description | | ----------------------- | --------------- | -------- | ------- | ------------------------------------------------------------ | | JOPLIN_SERVER_URL | joplin-core | Yes | — | Joplin Server URL (e.g., https://joplin.example.com/) | | JOPLIN_USERNAME | joplin-core | Yes | — | Joplin Server username/email | | JOPLIN_PASSWORD | joplin-core | Yes | — | Joplin Server password | | JOPLIN_API_TOKEN | both | Yes | — | Joplin Data API token (extracted from joplin config api.token) | | JOPLIN_CORE_URL | joplin-mcp | Yes | — | URL of the joplin-core Data API (e.g., http://joplin-core:41184) | | JOPLIN_DATA_API_PORT | joplin-core | No | 41184 | Internal Data API listen port | | MCP_PORT | joplin-mcp | No | 3000 | MCP HTTP server port (exposed to host) | | LOG_LEVEL | both | No | info | Log level: debug, info, warn, error, silent | | SYNC_INTERVAL_SECONDS | joplin-core | No | 300 | Periodic sync interval in seconds | | NODE_ENV | joplin-core | No | — | Set to production to enforce HTTPS for JOPLIN_SERVER_URL |

MCP Client Configuration (Docker)

The joplin-mcp container exposes an HTTP endpoint (not stdio). Configure your MCP client to connect via URL:

{
  "mcpServers": {
    "joplin": {
      "url": "http://localhost:3000/mcp"
    }
  }
}
How It Works
  • Two containers: joplin-core (stateful, runs Joplin CLI + Data API + bash sync scheduler) and joplin-mcp (stateless, Node.js MCP HTTP server only)
  • Multi-stage builds: [Dockerfile.mcp](Dockerfile.mcp) uses node:22-bookworm-slim with separate build and production stages; [Dockerfile.core](Dockerfile.core) is a single-stage Debian-based image
  • Non-root users: joplin user in joplin-core, mcp user in joplin-mcp
  • Persistent volume: joplin_data volume mounted at /home/joplin/.config/joplin stores the Joplin profile and SQLite database
  • Internal networking: joplin-mcp communicates with joplin-core via the Docker internal network using the service name joplin-core
  • Healthchecks: joplin-core healthchecks /ping on port 41184; joplin-mcp waits for joplin-core to be healthy before starting
  • Sync scheduler: A bash while true loop in [entrypoint-core.sh](entrypoint-core.sh) handles periodic sync with extensive logging to /var/log/joplin/
Testing with Docker

A dedicated [Dockerfile.tests](Dockerfile.tests) and test service in [docker-compose.yml](docker-compose.yml) allow running the test suite in a container:

# Build the test image
docker build -f Dockerfile.tests -t joplin-api-tests .

# Run tests
docker run --rm joplin-api-tests

# Or via docker compose (requires --profile test since the test service uses profiles)
docker compose --profile test run --rm tests

Tests use Vitest with v8 coverage (thresholds: 70% statements, 60% branches, 70% functions, 70% lines) and output JUnit XML reports to ./reports/. When running via docker compose, the ./reports directory is mounted into the container so reports persist on the host.

The test suite does not require a running Joplin instance — unit tests use mocks, and integration tests are skipped when the Joplin Data API is unavailable.


Architecture

Two-Container Deployment (Docker)

graph TD
    A[AI Client] -->|"MCP HTTP (port 3000)"| B[Container B: joplin-mcp]
    B -->|"HTTP fetch() + Bearer Token"| C[Container A: joplin-core]
    subgraph "Container A: joplin-core"
        C[Joplin Data API :41184] -->|"read/write"| D[(Joplin SQLite DB)]
        E[Bash Sync Scheduler] -->|"joplin sync"| F[Joplin Server]
        F -->|"HTTPS"| E
    end
    subgraph "Container B: joplin-mcp"
        B -->|"Tool handlers"| G[JoplinDataClient]
    end
  1. AI Client connects to joplin-mcp via HTTP on port 3000 (MCP StreamableHTTP transport)
  2. joplin-mcp is a stateless Node.js server — no Joplin CLI, no sync logic, no local DB
  3. JoplinDataClient in joplin-mcp issues HTTP requests to joplin-core on the internal Docker network
  4. joplin-core runs the Joplin Data API internally on 127.0.0.1:41185, with a socat TCP proxy exposing 0.0.0.0:41184 to the Docker network, backed by a persistent SQLite volume
  5. Bash sync scheduler in joplin-core handles periodic sync via the Joplin CLI against Joplin Server
  6. Write operations from the MCP server trigger sync via the Data API; bash scheduler provides periodic backup sync
  7. Both containers use healthchecks — joplin-mcp waits for joplin-core to be healthy before starting

Available MCP Tools

Tool Overview

| Tool | Description | Writes? | | ---------------- | ------------------------------------------------- | ------- | | list_notebooks | List all notebooks/folders | No | | list_notes | List notes with pagination and metadata fields | No | | search_notes | Search notes, folders, and tags | No | | read_note | Read a single note by ID | No | | read_notebook | Read a single notebook by ID | No | | read_multinote | Read multiple notes by IDs | No | | read_tags | Get tags for a note | No | | create_note | Create a new note | Yes | | create_folder | Create a new notebook | Yes | | edit_note | Edit an existing note | Yes | | edit_folder | Edit an existing folder | Yes | | create_tag | Create a new tag | Yes | | tag_note | Apply a tag to a note | Yes | | untag_note | Remove a tag from a note | Yes | | delete_note | Delete a note | Yes | | delete_folder | Delete a folder | Yes | | sync | Manually trigger sync | No |

Input / Output Schemas

All tool input is validated through Zod schemas. Below are the expected input fields and return types.

Read Tools

| Tool | Input | Output | | ---------------- | ---------------------------------------------------------------------- | ------------------------------------------------- | | list_notebooks | {} | Folder[] | | list_notes | { limit?: number (1–100), page?: number (≥1) } | { items: Note[], has_more: boolean } | | search_notes | { query: string (1–1000 chars), type?: "note" \| "folder" \| "tag" } | SearchResult[] | | read_note | { note_id: string (32-char hex) } | Note | | read_notebook | { notebook_id: string (32-char hex) } | Folder | | read_multinote | { note_ids: string[] (array of 32-char hex IDs) } | { notes: Note[], errors: { note_id, error }[] } | | read_tags | { note_id: string (32-char hex) } | Tag[] |

Write Tools

| Tool | Input | Output | | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | | create_note | { title (1–500 chars), parent_id?, body? (max 1 MB), author? (max 200), source_url? (validated URL), is_todo? (boolean \| number 0/1), todo_due? (unix ms) } | Note | | create_folder | { title (1–500 chars), parent_id?, icon? (max 100) } | Folder | | edit_note | { note_id, title?, parent_id?, body?, author? (max 200), source_url? (validated URL), is_todo? (boolean \| number 0/1), todo_due? (unix ms) } | Note | | edit_folder | { folder_id, title?, parent_id?, icon? (max 100) } | Folder | | create_tag | { title (1–200 chars) } | Tag | | tag_note | { note_id, tag_id } | NoteTag | | untag_note | { note_id, tag_id } | { success: true } |

Delete Tools

| Tool | Input | Output | | --------------- | --------------- | ------------------- | | delete_note | { note_id } | { success: true } | | delete_folder | { folder_id } | { success: true } |

Sy

Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.