docmost-mcp-oss
An MCP server built with FastMCP that exposes the REST API of Docmost as tools for AI assistants (Claude Desktop, Claude Code, Cursor, VS Code…).
Managed with uv.
📄 Full API research:
docs/DOCMOST-API.md. Verified against a real Docmost instance (not just the documentation).
Why a custom MCP?
Docmost ships with an official MCP, but it requires a Business/Enterprise license and is enabled from Settings → AI settings → MCP. This project uses the internal API (the same one the web UI consumes), which is also available in the self-hosted OSS edition.
About the name: the -oss suffix distinguishes this package from the unrelated
docmost-mcp already on PyPI. They are different
projects by different authors, and they would collide if installed side by side (both used to
ship the same import package). This one targets self-hosted Docmost and can edit page bodies;
install it as docmost-mcp-oss.
Exposed tools (20)
| Category | Tools |
|---|---|
| Pages | search_pages, get_page, create_page, update_page, update_page_content, delete_page, restore_page, move_page, list_recent_pages, list_child_pages, get_page_breadcrumbs, get_page_history |
| Spaces | list_spaces, get_space, create_space |
| Comments | get_comments, create_comment, update_comment |
| User | get_current_user, list_workspace_members |
Two tools stand out:
get_pagereturns metadata and content in Markdown (it combines/pages/infowith/pages/export, because the former does not return the body).update_page_contentreplaces the body of an existing page. The REST API can't do this: it writes directly to the Yjs document over the collaboration WebSocket. Requires theyjsextra and takes ~13 s (Docmost persists with a 10 s debounce).
Editing the body of an existing page
Docmost's REST API ignores the content field in /pages/create and /pages/update (they respond 200 but save nothing): the body lives in the Yjs collaboration server.
That's why there are two distinct paths:
| Operation | How |
|---|---|
| Read content | get_page |
| Create pagewith content | create_page (uses /pages/import) |
| Replace the body of an existing page | update_page_content (Yjs WebSocket) |
| Rename | update_page |
| Delete / restore / move | ✅ |
update_page_content opens the wss://<host>/collab WebSocket, syncs the document, replaces the content and waits for it to persist. Markdown is
converted using Docmost's own converter, so it supports the full schema (tables, lists, code, quotes, images…).
uv sync --extra yjs # enables update_page_content
Technical details of the protocol: docs/YJS-EDITING.md.
Installation
uv creates the virtual environment and installs the dependencies (pinned in uv.lock):
uv sync
No need to activate the environment: use uv run ….
Configuration
cp .env.example .env # then edit the values
DOCMOST_URL=https://docmost.example.com
# Option A — API Key
DOCMOST_API_KEY=dm_xxx
# Option B — login (works on OSS)
DOCMOST_EMAIL=tu@email.com
DOCMOST_PASSWORD=tu_password
Verify the connection:
uv run docmost-mcp-oss --check
# -> OK: authenticated as tu@email.com (https://docmost.example.com)
Usage
stdio (recommended for local use)
uv run docmost-mcp-oss
HTTP (for remote access)
uv run docmost-mcp-oss --http --port 8000
# MCP endpoint: http://127.0.0.1:8000/mcp
Connecting to MCP clients
Claude Desktop (claude_desktop_config.json)
{
"mcpServers": {
"docmost": {
"command": "uv",
"args": ["--directory", "/ruta/a/docmost-mcp-oss", "run", "docmost-mcp-oss"],
"env": {
"DOCMOST_URL": "https://docmost.example.com",
"DOCMOST_EMAIL": "tu@email.com",
"DOCMOST_PASSWORD": "tu_password"
}
}
}
}
Claude Code
claude mcp add docmost -- uv --directory /ruta/a/docmost-mcp-oss run docmost-mcp-oss
Cursor (.cursor/mcp.json)
Same as Claude Desktop, inside the mcpServers key.
Tests
| Command | What it validates | Needs instance |
|---|---|---|
uv run python tests/test_client.py |
Client against a mocked Docmost (12 cases) | No |
uv run python tests/test_tools.py |
MCP tool registry | No |
uv run python tests/smoke_live.py |
Read-only against a real instance | Yes |
uv run python tests/smoke_mcp_live.py |
Tools through the MCP layer | Yes |
uv run --extra yjs python tests/smoke_yjs_live.py |
Body editing via Yjs (7 checks) | Yes |
uv run ruff check . |
Lint | No |
The live tests read credentials from .docmost-creds.json (ignored by git):
{
"url": "https://docmost.example.com",
"email": "you@example.com",
"password": "your-password"
}
--write adds a create → update → get → delete cycle over a test page
(use --write keep to keep it).
Implementation notes
Things that are not obvious and that the client already handles:
- All endpoints are
POSTand respond with{data, success, status}; the client unwrapsdata. - Content does not come from
/pages/info: it is fetched via/pages/export(markdown|html), which responds with the raw file, no wrapper. - Only
/pages/importpersists content over REST;/pages/createand/pages/updateignorecontent. To edit the body of an already-created page you must write to the Yjs document (see above). - Authentication: both real variants are supported — the
authTokencookie (httpOnly) anddata.tokens.accessTokenforwarded asBearer. /searchrequiresqueryandspaceId; if you don't provide a space, it fans out across all accessible spaces and merges byrank./pages/sidebar-pagesrequiresspaceId; if you only provide a page, its space is resolved first.- Lexical search (PostgreSQL FTS): stopwords ("a", "de", "the") return 0 results.
- Heterogeneous response shapes: some builds return
{items, meta}and others raw lists; the client normalizes both. - Permissions: the MCP acts as the authenticated user; it can never do more than the user can.
Structure
docmost-mcp-oss/
├── docmost_mcp_oss/
│ ├── __init__.py
│ ├── client.py # async HTTP client for the Docmost REST API
│ ├── collab.py # Yjs WebSocket: read/write page bodies
│ └── server.py # FastMCP server + tools
├── docs/
│ ├── DOCMOST-API.md # API research (OSS + real instance)
│ └── YJS-EDITING.md # collaboration WebSocket protocol
├── tests/
│ ├── test_client.py # mocked, no network
│ ├── test_tools.py # MCP tool registry, no network
│ ├── smoke_live.py # real instance, read-only
│ ├── smoke_mcp_live.py # MCP layer against a real instance
│ └── smoke_yjs_live.py # body editing via Yjs
├── .github/workflows/ci.yml # lint, tests and packaging
├── CONTRIBUTING.md
├── LICENSE # MIT
├── pyproject.toml # metadata, dependencies and ruff config
├── uv.lock # reproducible resolution (it is versioned)
└── .env.example
License
MIT © 2026 Abel Santillan Rodriguez
Release files for docmost-mcp-oss 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| docmost_mcp_oss-0.1.0.tar.gz | 159.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| docmost_mcp_oss-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 181.2 kB
Release files / docmost_mcp_oss-0.1.0.tar.gz
| Download URL | docmost_mcp_oss-0.1.0.tar.gz |
|---|---|
| Size | 159.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ad5cbbc85d9f4bb134b69feb7a01be1cbbc2a79fb261ad28df17dad279d4ab99
|
|
BLAKE2b-256 checksum How to use checksums |
6aad0ff40df687691a3ffff83a0a902320965647921864f076def9ba54a7c055
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.
Transparency logRelease files / docmost_mcp_oss-0.1.0-py3-none-any.whl
| Download URL | docmost_mcp_oss-0.1.0-py3-none-any.whl |
|---|---|
| Size | 21.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2ba5f07c2e3aadd5aa1cb8d0b9c9157c982db76ef4edb5a223c98d72cfa31cf0
|
|
BLAKE2b-256 checksum How to use checksums |
1a23db837d8d4d44dc9a9ce42efbd99621a4d7942bc902198b72a5ffc0216bab
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.
Transparency log