docmost-mcp-oss 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,16 @@
1
+ # URL of your Docmost instance.
2
+ # - Self-hosted: the base of your installation, e.g. https://docmost.example.com
3
+ # - Cloud: includes your workspace's subdomain, e.g. https://mi-org.docmost.com
4
+ DOCMOST_URL=https://docmost.example.com
5
+
6
+ # --- Option A: API Key (requires an Enterprise license) ---
7
+ # The key is sent as `Authorization: Bearer <key>`.
8
+ DOCMOST_API_KEY=
9
+
10
+ # --- Option B: login with email/password (works on OSS) ---
11
+ # The server obtains the `authToken` cookie and reuses it.
12
+ DOCMOST_EMAIL=
13
+ DOCMOST_PASSWORD=
14
+
15
+ # HTTP timeout in seconds (optional, defaults to 30)
16
+ DOCMOST_TIMEOUT=30
@@ -0,0 +1,82 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ concurrency:
10
+ group: ${{ github.workflow }}-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ lint:
15
+ name: Lint and format
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - name: Install uv
21
+ uses: astral-sh/setup-uv@v5
22
+ with:
23
+ enable-cache: true
24
+
25
+ - name: Install dependencies
26
+ run: uv sync --frozen
27
+
28
+ - name: Ruff lint
29
+ run: uv run ruff check .
30
+
31
+ - name: Ruff format
32
+ run: uv run ruff format --check .
33
+
34
+ test:
35
+ name: Tests (Python ${{ matrix.python-version }})
36
+ runs-on: ubuntu-latest
37
+ strategy:
38
+ fail-fast: false
39
+ matrix:
40
+ python-version: ["3.10", "3.12", "3.13"]
41
+ steps:
42
+ - uses: actions/checkout@v4
43
+
44
+ - name: Install uv
45
+ uses: astral-sh/setup-uv@v5
46
+ with:
47
+ python-version: ${{ matrix.python-version }}
48
+ enable-cache: true
49
+
50
+ # The `yjs` extra is installed so the collab module is imported too.
51
+ - name: Install dependencies
52
+ run: uv sync --frozen --extra yjs
53
+
54
+ # Offline suites: they run against a simulated Docmost and need no
55
+ # credentials. The `smoke_*` scripts hit a real instance and are
56
+ # intentionally not run here.
57
+ - name: Client tests
58
+ run: uv run python tests/test_client.py
59
+
60
+ - name: Tool registry
61
+ run: uv run python tests/test_tools.py
62
+
63
+ build:
64
+ name: Build distribution
65
+ runs-on: ubuntu-latest
66
+ steps:
67
+ - uses: actions/checkout@v4
68
+
69
+ - name: Install uv
70
+ uses: astral-sh/setup-uv@v5
71
+
72
+ - name: Build sdist and wheel
73
+ run: uv build
74
+
75
+ - name: Inspect the wheel
76
+ run: |
77
+ python -m zipfile -l dist/*.whl > /tmp/wheel.txt
78
+ cat /tmp/wheel.txt
79
+ grep -q "docmost_mcp_oss/client.py" /tmp/wheel.txt
80
+ grep -q "docmost_mcp_oss/collab.py" /tmp/wheel.txt
81
+ grep -q "licenses/LICENSE" /tmp/wheel.txt
82
+ echo "OK: wheel contains the package and the LICENSE"
@@ -0,0 +1,59 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ build:
13
+ name: Build distribution
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Install uv
19
+ uses: astral-sh/setup-uv@v5
20
+
21
+ # Guard against a tag that does not match the packaged version.
22
+ - name: Check the tag matches the project version
23
+ run: |
24
+ VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
25
+ TAG="${GITHUB_REF_NAME#v}"
26
+ if [ "$TAG" != "$VERSION" ]; then
27
+ echo "::error::tag v$TAG does not match project version $VERSION"
28
+ exit 1
29
+ fi
30
+ echo "OK: tag v$TAG matches version $VERSION"
31
+
32
+ - name: Build sdist and wheel
33
+ run: uv build
34
+
35
+ - name: Upload artifacts
36
+ uses: actions/upload-artifact@v4
37
+ with:
38
+ name: dist
39
+ path: dist/
40
+
41
+ publish:
42
+ name: Publish to PyPI
43
+ needs: build
44
+ runs-on: ubuntu-latest
45
+ environment:
46
+ name: pypi
47
+ # Update this if the PyPI project name differs from the repository name.
48
+ url: https://pypi.org/p/docmost-mcp-oss
49
+ permissions:
50
+ id-token: write # Trusted Publishing (OIDC): no API token needed
51
+ steps:
52
+ - name: Download artifacts
53
+ uses: actions/download-artifact@v4
54
+ with:
55
+ name: dist
56
+ path: dist/
57
+
58
+ - name: Publish
59
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,14 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ .env
5
+ .pytest_cache/
6
+ .ruff_cache/
7
+ *.egg-info/
8
+ dist/
9
+ build/
10
+
11
+ # Credenciales locales (nunca versionar)
12
+ .docmost-creds.json
13
+ creds.json
14
+ *.creds.json
@@ -0,0 +1,89 @@
1
+ # Contributing
2
+
3
+ Thanks for your interest in improving `docmost-mcp-oss`. This project is small, so
4
+ the process is intentionally light.
5
+
6
+ ## Setup
7
+
8
+ The project is managed with [uv](https://docs.astral.sh/uv/):
9
+
10
+ ```bash
11
+ git clone https://github.com/abelsr/docmost-mcp-oss
12
+ cd docmost-mcp-oss
13
+ uv sync --extra yjs # --extra yjs is needed for the collaboration module
14
+ ```
15
+
16
+ There is no need to activate the environment: prefix commands with `uv run`.
17
+
18
+ ## Checks
19
+
20
+ These are the same checks CI runs, and they need **no credentials**:
21
+
22
+ ```bash
23
+ uv run ruff check . # lint
24
+ uv run ruff format --check . # formatting
25
+ uv run python tests/test_client.py # client against a simulated Docmost
26
+ uv run python tests/test_tools.py # MCP tool registry
27
+ ```
28
+
29
+ `ruff format` also formats Python code blocks inside Markdown files, so run it
30
+ before committing if you touch the docs.
31
+
32
+ ## Live tests
33
+
34
+ Three suites hit a **real** Docmost instance and are therefore not run in CI.
35
+ They read credentials from a `.docmost-creds.json` file in the repo root, which
36
+ is git-ignored:
37
+
38
+ ```json
39
+ {
40
+ "url": "https://docmost.example.com",
41
+ "email": "you@example.com",
42
+ "password": "your-password"
43
+ }
44
+ ```
45
+
46
+ ```bash
47
+ uv run python tests/smoke_live.py # read-only
48
+ uv run python tests/smoke_live.py --write # create/rename/delete
49
+ uv run python tests/smoke_mcp_live.py --write # through the MCP layer
50
+ uv run --extra yjs python tests/smoke_yjs_live.py # body editing over Yjs
51
+ ```
52
+
53
+ Use an API key (`"api_key": "..."`) instead of a password if your account has
54
+ MFA enabled. Prefer a throwaway account or a test space: `--write` creates and
55
+ deletes pages.
56
+
57
+ ## Guidance
58
+
59
+ - **Nothing about this API is documented upstream.** Everything in
60
+ [`docs/DOCMOST-API.md`](docs/DOCMOST-API.md) and
61
+ [`docs/YJS-EDITING.md`](docs/YJS-EDITING.md) was derived from source code and
62
+ from probing real instances. If you change a behaviour, say *how you verified
63
+ it* — observed facts, not assumptions.
64
+ - **Several behaviours differ between Docmost builds.** The client normalizes
65
+ them (raw lists vs `{items, meta}`, cookie vs token login, optional vs required
66
+ `spaceId`). Keep those normalizations tolerant rather than assuming one build.
67
+ - **Tool docstrings are the LLM's interface.** They must stay accurate about
68
+ limitations (for example, that `/pages/update` only changes titles).
69
+ - **Typed signatures matter.** FastMCP only fills a tool's structured output
70
+ (`.data`) when the return type is annotated, hence `-> list[dict]` / `-> dict`.
71
+ `tests/test_tools.py` guards against a regression here.
72
+
73
+ ## Adding a tool
74
+
75
+ 1. Add the method to `docmost_mcp_oss/client.py` (or `collab.py`).
76
+ 2. Register it in `docmost_mcp_oss/server.py` with `@mcp.tool`, a **typed**
77
+ signature, and a docstring with an `Args:` section.
78
+ 3. Add it to `EXPECTED_TOOLS` in `tests/test_tools.py`.
79
+
80
+ ## Commits
81
+
82
+ Short imperative subject lines, English, in the style of
83
+ `Add page history tool` or `Fix spaceId requirement in search`. Explain the
84
+ *why* in the body when the change is not obvious.
85
+
86
+ ## License
87
+
88
+ By contributing you agree that your contributions are licensed under the
89
+ [MIT License](LICENSE).
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Abel Santillan Rodriguez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,237 @@
1
+ Metadata-Version: 2.5
2
+ Name: docmost-mcp-oss
3
+ Version: 0.1.0
4
+ Summary: MCP server (FastMCP) for Docmost: read and edit pages, spaces and comments over the REST API and the Yjs collaboration WebSocket
5
+ Project-URL: Homepage, https://github.com/abelsr/docmost-mcp-oss
6
+ Project-URL: Repository, https://github.com/abelsr/docmost-mcp-oss
7
+ Project-URL: Issues, https://github.com/abelsr/docmost-mcp-oss/issues
8
+ Project-URL: Changelog, https://github.com/abelsr/docmost-mcp-oss/commits/main
9
+ Author: Abel Santillan Rodriguez
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: docmost,fastmcp,mcp,model-context-protocol,wiki,yjs
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Documentation
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: fastmcp<5,>=4.0
25
+ Requires-Dist: httpx>=0.27
26
+ Requires-Dist: python-dotenv>=1.0
27
+ Provides-Extra: yjs
28
+ Requires-Dist: pycrdt>=0.14; extra == 'yjs'
29
+ Requires-Dist: websockets>=13; extra == 'yjs'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # docmost-mcp-oss
33
+
34
+ [![CI](https://github.com/abelsr/docmost-mcp-oss/actions/workflows/ci.yml/badge.svg)](https://github.com/abelsr/docmost-mcp-oss/actions/workflows/ci.yml)
35
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/abelsr/docmost-mcp-oss/blob/main/LICENSE)
36
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](pyproject.toml)
37
+
38
+ An [MCP](https://modelcontextprotocol.io) server built with **FastMCP** that exposes the REST API of [Docmost](https://docmost.com) as tools for AI assistants (Claude Desktop, Claude Code, Cursor, VS Code…).
39
+
40
+ Managed with **[uv](https://docs.astral.sh/uv/)**.
41
+
42
+ > 📄 Full API research: [`docs/DOCMOST-API.md`](https://github.com/abelsr/docmost-mcp-oss/blob/main/docs/DOCMOST-API.md).
43
+ > Verified against a real Docmost instance (not just the documentation).
44
+
45
+ ## Why a custom MCP?
46
+
47
+ 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.
48
+
49
+ **About the name:** the `-oss` suffix distinguishes this package from the unrelated
50
+ [`docmost-mcp`](https://pypi.org/project/docmost-mcp/) already on PyPI. They are different
51
+ projects by different authors, and they would collide if installed side by side (both used to
52
+ ship the same import package). This one targets self-hosted Docmost and can edit page bodies;
53
+ install it as `docmost-mcp-oss`.
54
+
55
+ ## Exposed tools (20)
56
+
57
+ | Category | Tools |
58
+ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
59
+ | 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` |
60
+ | Spaces | `list_spaces`, `get_space`, `create_space` |
61
+ | Comments | `get_comments`, `create_comment`, `update_comment` |
62
+ | User | `get_current_user`, `list_workspace_members` |
63
+
64
+ Two tools stand out:
65
+
66
+ - **`get_page`** returns **metadata and content** in Markdown (it combines `/pages/info` with `/pages/export`, because the former **does not** return the body).
67
+ - **`update_page_content`** **replaces 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 the `yjs` extra and takes ~13 s (Docmost persists with a 10 s *debounce*).
68
+
69
+ ## Editing the body of an existing page
70
+
71
+ 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.
72
+
73
+ That's why there are two distinct paths:
74
+
75
+ | Operation | How |
76
+ | ---------------------------------------------- | ------------------------------------------------- |
77
+ | Read content | `get_page` |
78
+ | Create page**with** content | `create_page` (uses `/pages/import`) |
79
+ | **Replace the body of an existing page** | **`update_page_content`** (Yjs WebSocket) |
80
+ | Rename | `update_page` |
81
+ | Delete / restore / move | ✅ |
82
+
83
+ `update_page_content` opens the `wss://<host>/collab` WebSocket, syncs the document, replaces the content and waits for it to persist. Markdown is
84
+ converted using Docmost's own converter, so it supports the full schema (tables, lists, code, quotes, images…).
85
+
86
+ ```bash
87
+ uv sync --extra yjs # enables update_page_content
88
+ ```
89
+
90
+ Technical details of the protocol: [`docs/YJS-EDITING.md`](https://github.com/abelsr/docmost-mcp-oss/blob/main/docs/YJS-EDITING.md).
91
+
92
+ ## Installation
93
+
94
+ `uv` creates the virtual environment and installs the dependencies (pinned in `uv.lock`):
95
+
96
+ ```bash
97
+ uv sync
98
+ ```
99
+
100
+ No need to activate the environment: use `uv run …`.
101
+
102
+ ## Configuration
103
+
104
+ ```bash
105
+ cp .env.example .env # then edit the values
106
+ ```
107
+
108
+ ```dotenv
109
+ DOCMOST_URL=https://docmost.example.com
110
+
111
+ # Option A — API Key
112
+ DOCMOST_API_KEY=dm_xxx
113
+
114
+ # Option B — login (works on OSS)
115
+ DOCMOST_EMAIL=tu@email.com
116
+ DOCMOST_PASSWORD=tu_password
117
+ ```
118
+
119
+ Verify the connection:
120
+
121
+ ```bash
122
+ uv run docmost-mcp-oss --check
123
+ # -> OK: authenticated as tu@email.com (https://docmost.example.com)
124
+ ```
125
+
126
+ ## Usage
127
+
128
+ ### stdio (recommended for local use)
129
+
130
+ ```bash
131
+ uv run docmost-mcp-oss
132
+ ```
133
+
134
+ ### HTTP (for remote access)
135
+
136
+ ```bash
137
+ uv run docmost-mcp-oss --http --port 8000
138
+ # MCP endpoint: http://127.0.0.1:8000/mcp
139
+ ```
140
+
141
+ ## Connecting to MCP clients
142
+
143
+ ### Claude Desktop (`claude_desktop_config.json`)
144
+
145
+ ```json
146
+ {
147
+ "mcpServers": {
148
+ "docmost": {
149
+ "command": "uv",
150
+ "args": ["--directory", "/ruta/a/docmost-mcp-oss", "run", "docmost-mcp-oss"],
151
+ "env": {
152
+ "DOCMOST_URL": "https://docmost.example.com",
153
+ "DOCMOST_EMAIL": "tu@email.com",
154
+ "DOCMOST_PASSWORD": "tu_password"
155
+ }
156
+ }
157
+ }
158
+ }
159
+ ```
160
+
161
+ ### Claude Code
162
+
163
+ ```bash
164
+ claude mcp add docmost -- uv --directory /ruta/a/docmost-mcp-oss run docmost-mcp-oss
165
+ ```
166
+
167
+ ### Cursor (`.cursor/mcp.json`)
168
+
169
+ Same as Claude Desktop, inside the `mcpServers` key.
170
+
171
+ ## Tests
172
+
173
+ | Command | What it validates | Needs instance |
174
+ | ----------------------------------------------------- | ------------------------------------------ | -------------- |
175
+ | `uv run python tests/test_client.py` | Client against a mocked Docmost (12 cases) | No |
176
+ | `uv run python tests/test_tools.py` | MCP tool registry | No |
177
+ | `uv run python tests/smoke_live.py` | Read-only against a real instance | Yes |
178
+ | `uv run python tests/smoke_mcp_live.py` | Tools through the MCP layer | Yes |
179
+ | `uv run --extra yjs python tests/smoke_yjs_live.py` | **Body editing via Yjs** (7 checks) | Yes |
180
+ | `uv run ruff check .` | Lint | No |
181
+
182
+ The live tests read credentials from `.docmost-creds.json` (ignored by git):
183
+
184
+ ```json
185
+ {
186
+ "url": "https://docmost.example.com",
187
+ "email": "you@example.com",
188
+ "password": "your-password"
189
+ }
190
+ ```
191
+
192
+ `--write` adds a **create → update → get → delete** cycle over a test page
193
+ (use `--write keep` to keep it).
194
+
195
+ ## Implementation notes
196
+
197
+ Things that are **not** obvious and that the client already handles:
198
+
199
+ - **All endpoints are `POST`** and respond with `{data, success, status}`; the client unwraps `data`.
200
+ - **Content does not come from `/pages/info`**: it is fetched via `/pages/export` (`markdown`|`html`), which responds with the raw file, no wrapper.
201
+ - **Only `/pages/import` persists content over REST**; `/pages/create` and `/pages/update` ignore `content`. To edit the body of an already-created page you must write to the Yjs document (see above).
202
+ - **Authentication:** both real variants are supported — the `authToken` cookie (httpOnly) and `data.tokens.accessToken` forwarded as `Bearer`.
203
+ - **`/search` requires `query` and `spaceId`**; if you don't provide a space, it fans out across all accessible spaces and merges by `rank`.
204
+ - **`/pages/sidebar-pages` requires `spaceId`**; if you only provide a page, its space is resolved first.
205
+ - **Lexical search (PostgreSQL FTS):** stopwords ("a", "de", "the") return 0 results.
206
+ - **Heterogeneous response shapes:** some builds return `{items, meta}` and others raw lists; the client normalizes both.
207
+ - **Permissions:** the MCP acts *as* the authenticated user; it can never do more than the user can.
208
+
209
+ ## Structure
210
+
211
+ ```
212
+ docmost-mcp-oss/
213
+ ├── docmost_mcp_oss/
214
+ │ ├── __init__.py
215
+ │ ├── client.py # async HTTP client for the Docmost REST API
216
+ │ ├── collab.py # Yjs WebSocket: read/write page bodies
217
+ │ └── server.py # FastMCP server + tools
218
+ ├── docs/
219
+ │ ├── DOCMOST-API.md # API research (OSS + real instance)
220
+ │ └── YJS-EDITING.md # collaboration WebSocket protocol
221
+ ├── tests/
222
+ │ ├── test_client.py # mocked, no network
223
+ │ ├── test_tools.py # MCP tool registry, no network
224
+ │ ├── smoke_live.py # real instance, read-only
225
+ │ ├── smoke_mcp_live.py # MCP layer against a real instance
226
+ │ └── smoke_yjs_live.py # body editing via Yjs
227
+ ├── .github/workflows/ci.yml # lint, tests and packaging
228
+ ├── CONTRIBUTING.md
229
+ ├── LICENSE # MIT
230
+ ├── pyproject.toml # metadata, dependencies and ruff config
231
+ ├── uv.lock # reproducible resolution (it is versioned)
232
+ └── .env.example
233
+ ```
234
+
235
+ ## License
236
+
237
+ [MIT](https://github.com/abelsr/docmost-mcp-oss/blob/main/LICENSE) © 2026 Abel Santillan Rodriguez