Get workspace listLABSCO SUMMARY
Two layers of auth kept apart: the server holds one AppFlowy login, and each client gets a token that reaches only the subtree you granted it.
Enforcement is by ancestry — for any page a tool touches the server walks up the folder tree, and the call proceeds only if it reaches one of the token's allowed roots. Block edits go through the same Yjs/CRDT web-update path as the official web client, which is also why Update database row and Delete database row act on an existing row by its id: REST exposes no route to edit or delete a row by UUID. One setting undoes the fence — APPFLOWY_MCP_REQUIRE_AUTH set to false with no tokens configured grants full access to anyone who can reach the port, so leave it on unless the port is on a trusted network.
What it is
A self-hosted MCP server for AppFlowy that logs in once as a service account and hands each client an opaque token bounded to the workspaces and page subtrees you name.
What you get
- The page tree, pruned to what the token may see: Get workspace list and Get workspace folder return only allowed subtrees, and Create new page, Update page and Get page details create, rename, lock and read pages under an allowed parent.
- Block-level editing rather than whole-page rewrites: Get page blocks lists a page's blocks in order, Insert block places a new one at any position — including an image referenced from a public URL — Edit block text replaces rich content in place with its formatting preserved, Delete block removes a leaf, and Append content to page adds to the end.
- Databases without opening a grid: Create database makes a grid, board or calendar under a parent, Get workspace databases and Get database fields describe it, Add database field adds a text, number, select or date column, and List database rows, Get database row, Create database row, Update database row and Delete database row work the rows, with cells keyed by field name or id.
- Trash and favourites as ordinary calls: Move page to trash, Restore page from trash and Delete page from trash cover the lifecycle, alongside Get trash, Get favorite pages and Toggle favorite page.
- Scopes shaped like the tree: a scope is a path of AppFlowy ids whose last id roots the allowed subtree, one token may carry several disjoint grants, and an empty scope list means everything the service account can see.
Requirements
An AppFlowy Cloud base URL in APPFLOWY_BASE_URL and a service account — APPFLOWY_EMAIL with APPFLOWY_PASSWORD, or a pre-minted APPFLOWY_ACCESS_TOKEN, which takes precedence. Clients never see those credentials: each presents its own token as a bearer header over streamable HTTP at /mcp.
Setup effort
Run a container — pull the image and point your client at it
USE CASES
Giving an assistant part of a workspace, not all of it
Each client token carries its own allowed page subtrees, and both listings come back pruned to what that token may see.
Writing notes into a page that already exists
Append blocks to the end, or insert one at the position you name.
Correcting one line without rewriting the page
List the blocks with their ids and text, then replace the text of the block that is wrong.
Keeping a tracker up to date
Rows come back with cells keyed by field name; edit the one row in place by its id.
Standing up a database from a description
Create the grid, board or calendar under a parent page, add the columns, then start adding rows.
Filing a page away without losing it
Move the page to trash, look at what is in there, and restore it when it is wanted back.
25 tools
Listed in project docs. Your client may show a different list.
Get workspace folderCreate new pageUpdate pageGet page detailsAppend content to pageGet page blocksInsert blockEdit block textDelete blockCreate databaseGet workspace databasesGet database fieldsAdd database fieldList database rowsGet database rowCreate database rowUpdate database rowDelete database rowMove page to trashRestore page from trashDelete page from trashGet trashGet favorite pagesToggle favorite pageBEFORE YOU INSTALL
What to know first
Tools you get
25 tools named in the project's documentation.
Requires
- A self-hosted AppFlowy Cloud instance, its URL set as APPFLOWY_BASE_URL on the server
- An AppFlowy service account: APPFLOWY_EMAIL and APPFLOWY_PASSWORD, or a pre-minted JWT in APPFLOWY_ACCESS_TOKEN
- Docker (or Helm, or uv to run it from source)
- A client that can reach a remote MCP server over HTTP
INSTALLATION
Connect your client
Pull the image and point your client at it.
Server setup
Run the server with Docker — <YOUR_APPFLOWY_BASE_URL> and <YOUR_APPFLOWY_EMAIL> are your AppFlowy Cloud URL and service-account email, APPFLOWY_PASSWORD=secret is a sample password, and sk-full a sample client token — choose your own: docker run --rm -p 8000:8000 -e APPFLOWY_BASE_URL=<YOUR_APPFLOWY_BASE_URL> -e APPFLOWY_EMAIL=<YOUR_APPFLOWY_EMAIL> -e APPFLOWY_PASSWORD=secret -e APPFLOWY_MCP_TOKENS='[{"token":"sk-full","scopes":[]}]' m2n2/appflowy-mcp:latest
Or, for Docker Compose, Helm or running from source, clone the repository first: git clone https://github.com/CAREEMER/appflowy-mcp && cd appflowy-mcp
Docker Compose, after filling in the values in .env:
cp .env.example .env && docker compose up -dHelm, with the same values as the Docker command:
helm install appflowy-mcp ./deploy/helm --set appflowy.baseUrl=<YOUR_APPFLOWY_BASE_URL> --set appflowy.email=<YOUR_APPFLOWY_EMAIL> --set appflowy.password=secret --set-json 'tokens=[{"token":"sk-full","scopes":[]}]'From source, with the APPFLOWY_ variables set in your environment or a config file:
uv run appflowy-mcpThen point your client at http://localhost:8000/mcp (your host and port) and send your token in an Authorization: Bearer header
MCP Clients
Claude Code
{
"mcpServers": {
"appflowy": {
"type": "http",
"url": "<YOUR_APPFLOWY_URL>/mcp",
"headers": {
"Authorization": "Bearer sk-full"
}
}
}
}<YOUR_APPFLOWY_URL>/mcp is your server's address (http://localhost:8000/mcp for the Docker command above). sk-full is a sample; yours is the token you set in APPFLOWY_MCP_TOKENS. The AppFlowy login is set on the server, not in this block.