Labsco
CAREEMER logo

Appflowy MCP

β˜… 5

from CAREEMER

MCP for self-hosted Appflowy Cloud instance with HTTP interface, prepared in Docker

πŸ”₯πŸ”₯πŸ”₯βœ“ VerifiedAccount requiredAdvanced setup

appflowy-mcp

<p align="center"> <a href="https://hub.docker.com/r/m2n2/appflowy-mcp"><img alt="Docker Image Version" src="https://img.shields.io/docker/v/m2n2/appflowy-mcp?sort=semver&logo=docker&logoColor=white&label=docker%20hub&color=2496ED"></a> <a href="https://hub.docker.com/r/m2n2/appflowy-mcp"><img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/m2n2/appflowy-mcp?logo=docker&logoColor=white&color=2496ED"></a> <a href="https://hub.docker.com/r/m2n2/appflowy-mcp/tags"><img alt="Docker Image Size" src="https://img.shields.io/docker/image-size/m2n2/appflowy-mcp/latest?logo=docker&logoColor=white&label=image%20size&color=2496ED"></a> <img alt="Architectures" src="https://img.shields.io/badge/arch-amd64%20%7C%20arm64-blue?logo=linux&logoColor=white"> <a href="https://modelcontextprotocol.io"><img alt="MCP" src="https://img.shields.io/badge/MCP-Model%20Context%20Protocol-7C3AED?logo=anthropic&logoColor=white"></a> <a href="./AGENTS.md"><img alt="Coverage" src="https://img.shields.io/badge/coverage-100%25-brightgreen?logo=pytest&logoColor=white"></a> <a href="./LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-green.svg"></a> </p> <p align="center"> 🐳 <a href="https://hub.docker.com/r/m2n2/appflowy-mcp"><strong><code>m2n2/appflowy-mcp:latest</code> on Docker Hub</strong></a> </p>

A self-hosted, token-scoped Model Context Protocol server for AppFlowy. It gives AI agents (Claude, or any MCP client) tools to read and edit your AppFlowy workspaces β€” list workspaces, walk the page tree, create/update/read pages, and edit individual blocks in place β€” while bounding each client to exactly the pages you allow via per-token tree-shaped scopes.

  • πŸ”’ Token-scoped access. The server logs into AppFlowy once as a service account. Clients never see those credentials β€” they present an opaque token, and each token is restricted to a set of workspaces / page subtrees.
  • 🌳 Tree-shaped scopes. Grant a whole workspace, a top-level page and everything under it, or a page four levels deep and its descendants. Mix and match several grants per token.
  • 🐳 Runs anywhere. Streamable-HTTP transport, small multi-arch image (m2n2/appflowy-mcp, amd64 + arm64) on Docker Hub, ready for Docker Compose, Kubernetes, or a Helm chart.
  • ✏️ Real editing. Append blocks, insert blocks at any position, edit block text (rich formatting preserved), and delete blocks β€” via the same Yjs/CRDT path the official web client uses.

How access works

            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   token: scopes               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 MCP client β”‚  Authorization: Bearer <token>  ──────────▢ β”‚  appflowy-mcp β”‚
 (Claude)   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                               β”‚  enforces     β”‚
                                                          β”‚  scope, then  β”‚
                                                          β”‚  acts as the  β”‚
            service account (email+password / JWT) ◀──────│  service acct β”‚
                                                          β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
                                                                 β–Ό
                                                          AppFlowy Cloud REST

Two layers of auth, kept separate:

  1. Backend auth (one service account). APPFLOWY_BASE_URL + APPFLOWY_EMAIL/APPFLOWY_PASSWORD (or a pre-minted APPFLOWY_ACCESS_TOKEN). The server logs in once and refreshes automatically on expiry.
  2. Client auth (many tokens). Each MCP client presents a token. The token decides what it can touch β€” the backend credentials are never exposed.

Scopes

A scope is a path of AppFlowy ids:

ScopeGrants
(empty list)everything the service account can see
WORKSPACEthe whole workspace
WORKSPACE/VIEWthat page and everything nested under it
WORKSPACE/VIEW_L1/VIEW_L2/VIEW_L3a page several levels deep and its subtree

The last id is the root of the allowed subtree; earlier ids only help locate it (AppFlowy view ids are globally unique, so intermediate ids are optional). A token may list several scopes to grant multiple disjoint subtrees at once.

Enforcement is by ancestry: for any page a tool touches, the server walks up the folder tree; if it reaches one of the token's allowed roots, the call proceeds, otherwise it's rejected. Get workspace list and Get workspace folder are pruned to what the token may see.

Connecting a client

The server speaks streamable HTTP at http://HOST:PORT/mcp. Point your MCP client at it and send the token as a bearer header. For Claude Code:

{
  "mcpServers": {
    "appflowy": {
      "type": "http",
      "url": "https://appflowy-mcp.example.com/mcp",
      "headers": { "Authorization": "Bearer sk-full" }
    }
  }
}

Health check: GET /healthz β†’ {"status":"ok"}.

Tools

ToolPurpose
Get workspace listList workspaces visible to the token
Get workspace folderPage tree of a workspace, pruned to scope
Create new pageCreate a page under an allowed parent
Update pageRename / set icon / lock
Get page detailsFull page metadata + content
Append content to pageAppend blocks to the end
Get page blocksList a page's blocks in order (ids + text)
Insert blockInsert a new block at any position
Edit block textReplace a block's text/rich content in place
Delete blockDelete a leaf block
Create databaseCreate a grid/board/calendar database under a parent
Get workspace databasesList databases (+ their views), scoped
Get database fieldsList a database's fields/columns
Get database rowsList rows, with cell values keyed by field name
Add database fieldAdd a column (text, number, select, date, …)
Add database rowAdd a row from {field: value} cells
Update database rowUpsert a row by a stable key (idempotent)
Move page to trash / Restore page from trash / Delete page from trashTrash lifecycle
Get trash / Get favorite pagesListings, scoped
Toggle favorite page(Un)favorite a page

Notes & limits

  • Block-editing tools require pycrdt (bundled). They mirror the web client's CRDT web-update; there is no official per-block REST endpoint.
  • Database row cells are keyed by field name or id; values follow the field type (string for text/URL, number for Number, bool for Checkbox, ISO-8601 for DateTime). Update database row derives the row id from pre_hash, so reusing a key updates the same row β€” there is no REST endpoint to edit an arbitrary existing row by its UUID.
  • Scope checks rely on the workspace folder tree, cached for APPFLOWY_MCP_FOLDER_CACHE_TTL seconds. Newly created pages invalidate the cache for their workspace.
  • Open mode (APPFLOWY_MCP_REQUIRE_AUTH=false with no tokens) grants full access to anyone who can reach the port β€” only use on a trusted network.

Development

uv sync            # install runtime + dev dependencies
uv run pytest      # run the test suite with the 100% coverage gate
uv run ruff check  # lint

The suite enforces 100% line and branch coverage (--cov-fail-under=100 in pyproject.toml). CI runs it as the test job in .github/workflows/docker.yml; the Docker image build needs: test, so a failing test or a coverage drop blocks the image from ever being built. See AGENTS.md for the testing definition of done.

License

MIT β€” see LICENSE.

This project began as a self-hosting-focused rework of LucasXu0/appflowy_mcp.