Skip to main content
Glama
README.md
# docmost-mcp

An MCP server that gives AI agents read/write access to a self-hosted
[Docmost](https://docmost.com) wiki through its regular REST API.

## Why this exists

Docmost ships its own MCP endpoint, but it is gated behind a paid licence — the
Settings UI shows *"Available with a paid license"* for API management, and
`/api/mcp` returns `404` on the Community Edition. Minting API keys is gated the
same way.

The ordinary REST API, however, is fully open in the Community Edition. This
server is a thin wrapper over it: same operations, session authentication
instead of an API key.

## Requirements

- Python 3.11+
- A Docmost instance you can reach over HTTP(S)
- A dedicated Docmost user account for the agent

## Install

```bash
git clone https://github.com/<you>/docmost-mcp.git
cd docmost-mcp
python3 -m venv venv
./venv/bin/pip install -r requirements.txt
```

## Configure

Copy the example config and fill it in:

```bash
cp config.example.json config.json
chmod 600 config.json
```

```json
{
  "url": "https://docmost.example.com",
  "api_key": "",
  "email": "agent@example.com",
  "password": "..."
}
```

Two authentication modes are supported:

| Mode | When to use |
|---|---|
| `api_key` | If you hold a Docmost Enterprise licence. Sent as a Bearer token. |
| `email` + `password` | Community Edition. The server logs in and re-authenticates automatically when the session expires. |

If `api_key` is set it takes precedence; otherwise the credentials are used.

The config path can be overridden with the `DOCMOST_MCP_CONFIG` environment
variable.

### Create a dedicated account

Do not use the workspace owner's credentials. Invite a separate user
(Settings → Members → Invite) and grant it access only to the spaces the agent
needs. Space access in Docmost is usually inherited from the default *Everyone*
group, so check what that group can reach before assuming the agent is
restricted.

Gmail-style plus-addressing (`you+agent@gmail.com`) works if you would rather
not create a second mailbox.

## Verify

`selftest.py` exercises the whole chain — login, read, write, read back, delete:

```bash
./venv/bin/python selftest.py
```

## Connecting an agent

The server speaks MCP over **stdio**.

### Claude Code

```bash
claude mcp add docmost -- /path/to/docmost-mcp/venv/bin/python /path/to/docmost-mcp/server.py
```

Or add it to `~/.claude.json` by hand:

```json
{
  "mcpServers": {
    "docmost": {
      "type": "stdio",
      "command": "/path/to/docmost-mcp/venv/bin/python",
      "args": ["/path/to/docmost-mcp/server.py"],
      "env": {}
    }
  }
}
```

Restart Claude Code afterwards — the configuration is read at startup.

### Claude Desktop

Add the same block to `claude_desktop_config.json`:

- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`

### Cursor

Add it to `.cursor/mcp.json` in the project, or to `~/.cursor/mcp.json`
globally, using the same `mcpServers` shape.

### Any other MCP client

Launch `server.py` with the virtualenv's Python and speak JSON-RPC over stdin
and stdout. A minimal handshake:

```json
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"1"}}}
{"jsonrpc":"2.0","method":"notifications/initialized"}
{"jsonrpc":"2.0","id":2,"method":"tools/list"}
```

A convenience wrapper keeps the command short:

```sh
#!/bin/sh
exec /path/to/docmost-mcp/venv/bin/python /path/to/docmost-mcp/server.py "$@"
```

## Tools

| Tool | Arguments | Does |
|---|---|---|
| `list_spaces` | `limit` | Lists workspace spaces |
| `search` | `query`, `space_id?`, `limit` | Full-text search across pages |
| `get_page` | `page_id` | Fetches a page by id or slugId |
| `recent_pages` | `space_id?`, `limit` | Recently changed pages |
| `create_page` | `space_id`, `title`, `content?`, `parent_page_id?`, `fmt` | Creates a page |
| `update_page` | `page_id`, `title?`, `content?`, `fmt` | Updates title and/or body |
| `move_page` | `page_id`, `parent_page_id?` | Reparents a page |
| `delete_page` | `page_id`, `permanently` | Trashes (or permanently deletes) a page |

`fmt` is `markdown` (default), `html` or `json`.

## Notes on the Docmost API

Things worth knowing if you extend this server:

- Every endpoint is `POST`, including reads.
- `/pages/create` and `/pages/update` **require** a `format` field
  (`json` | `markdown` | `html`). Omitting it returns `400`.
- Responses wrap the payload in `{"data": ...}`; the client unwraps it.
- `/api/api-keys` answers `200` with an empty list on the Community Edition —
  listing is open, only key *creation* is licence-gated.
- Endpoint paths and payload shapes were taken from the Docmost client source
  (`apps/client/src/features/*/services/*.ts`), not guessed.

## Security

- `config.json` holds a plaintext password. Keep it at mode `600` and out of
  version control — it is in `.gitignore`.
- Give the agent account the narrowest space access that still lets it work.
- The account's actions appear in Docmost under its own name, so page history
  stays attributable.

## Compatibility

Built against Docmost 0.95.0 and Python MCP SDK 2.0. Note that SDK 2.0 renamed
`FastMCP` to `MCPServer` and moved it to `mcp.server`; the pre-2.0 import path
`mcp.server.fastmcp` will not work.

## Licence

MIT — see [LICENSE](LICENSE).

This project is not affiliated with Docmost.