appflowy-mcp
A self-hosted, token-scoped Model Context Protocol server for AppFlowy. It gives AI agents tools to read and edit your AppFlowy workspaces while bounding each client to exactly the pages you allow via per-token tree-shaped scopes.
README
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:
- Backend auth (one service account).
APPFLOWY_BASE_URL+APPFLOWY_EMAIL/APPFLOWY_PASSWORD(or a pre-mintedAPPFLOWY_ACCESS_TOKEN). The server logs in once and refreshes automatically on expiry. - 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.
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 CRDTweb-update; there is no official per-block REST endpoint. - Scope checks rely on the workspace folder tree, cached for
APPFLOWY_MCP_FOLDER_CACHE_TTLseconds. Newly created pages invalidate the cache for their workspace. - Open mode (
APPFLOWY_MCP_REQUIRE_AUTH=falsewith 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.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.