Skip to main content
Glama

appflowy-mcp

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:

Scope

Grants

(empty list)

everything the service account can see

WORKSPACE

the whole workspace

WORKSPACE/VIEW

that page and everything nested under it

WORKSPACE/VIEW_L1/VIEW_L2/VIEW_L3

a 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.

Related MCP server: Notion MCP Server

Configuration

Everything is configurable by environment variables (ideal for Docker / Helm) and/or a YAML/JSON file. Env wins over the file.

Environment variables

Variable

Description

APPFLOWY_BASE_URL

AppFlowy Cloud base URL, e.g. https://appflowy.example.com

APPFLOWY_EMAIL / APPFLOWY_PASSWORD

Service-account login (GoTrue password grant)

APPFLOWY_ACCESS_TOKEN

Pre-minted JWT instead of email/password (takes precedence)

APPFLOWY_MCP_CONFIG

Optional path to a YAML/JSON config file

APPFLOWY_MCP_HOST / APPFLOWY_MCP_PORT / APPFLOWY_MCP_PATH

Listen address (default 0.0.0.0:8000/mcp)

APPFLOWY_MCP_REQUIRE_AUTH

true (default) rejects unauthenticated requests; false + no tokens = open mode

APPFLOWY_MCP_FOLDER_CACHE_TTL

Seconds to cache folder trees for scope checks (default 15)

APPFLOWY_MCP_LOG_LEVEL

INFO (default), DEBUG, …

Tokens via env — two equivalent forms.

JSON blob (best as a single Helm/Docker secret):

APPFLOWY_MCP_TOKENS='[
  {"token":"sk-full",    "name":"full",    "scopes":[]},
  {"token":"sk-teamws",  "name":"team",    "scopes":["WORKSPACE_ID"]},
  {"token":"sk-project", "name":"project", "scopes":["WORKSPACE_ID/ROOT_VIEW_ID",
                                                     "WORKSPACE_ID/A/B/DEEP_VIEW_ID"]}
]'

Indexed (no embedded JSON):

APPFLOWY_MCP_TOKEN_0=sk-full
APPFLOWY_MCP_TOKEN_0_NAME=full
APPFLOWY_MCP_TOKEN_0_SCOPES=                       # empty => all workspaces

APPFLOWY_MCP_TOKEN_1=sk-project
APPFLOWY_MCP_TOKEN_1_NAME=project
APPFLOWY_MCP_TOKEN_1_SCOPES=WORKSPACE_ID/ROOT_VIEW_ID,WORKSPACE_ID/A/B/DEEP_VIEW_ID

Config file

appflowy:
  base_url: https://appflowy.example.com
  email: service@example.com
  password: ${APPFLOWY_PASSWORD}   # plain string; env is not interpolated — set real value
server:
  host: 0.0.0.0
  port: 8000
  path: /mcp
  require_auth: true
tokens:
  - token: sk-full
    name: full
    scopes: []                     # all workspaces
  - token: sk-project
    name: project
    scopes:
      - WORKSPACE_ID/ROOT_VIEW_ID            # a page + its whole subtree
      - WORKSPACE_ID/A/B/DEEP_VIEW_ID        # a deep page + its subtree

See config.example.yaml and .env.example.

Running

Docker

docker run --rm -p 8000:8000 \
  -e APPFLOWY_BASE_URL=https://appflowy.example.com \
  -e APPFLOWY_EMAIL=service@example.com \
  -e APPFLOWY_PASSWORD=secret \
  -e APPFLOWY_MCP_TOKENS='[{"token":"sk-full","scopes":[]}]' \
  m2n2/appflowy-mcp:latest

Docker Compose

cp .env.example .env   # fill in values
docker compose up -d

Kubernetes / Helm

A minimal chart lives in deploy/helm:

helm install appflowy-mcp ./deploy/helm \
  --set appflowy.baseUrl=https://appflowy.example.com \
  --set appflowy.email=service@example.com \
  --set appflowy.password=secret \
  --set-json 'tokens=[{"token":"sk-full","scopes":[]}]'

From source

uv run appflowy-mcp

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

Tool

Purpose

Get workspace list

List workspaces visible to the token

Get workspace folder

Page tree of a workspace, pruned to scope

Create new page

Create a page under an allowed parent

Update page

Rename / set icon / lock

Get page details

Full page metadata + content

Append content to page

Append blocks to the end

Get page blocks

List a page's blocks in order (ids + text)

Insert block

Insert a new block at any position

Edit block text

Replace a block's text/rich content in place

Delete block

Delete a leaf block

Move page to trash / Restore page from trash / Delete page from trash

Trash lifecycle

Get trash / Get favorite pages

Listings, 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.

  • 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.

License

MIT — see LICENSE.

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

A
license - permissive license
-
quality - not tested
A
maintenance

Maintenance

Maintainers
Response time
0dRelease cycle
2Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/CAREEMER/appflowy-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server