Appflowy MCP
MCP para instância auto-hospedada do Appflowy Cloud com interface HTTP, preparado no Docker
Documentação
appflowy-mcp
🐳 m2n2/appflowy-mcp:latest on Docker Hub
Um servidor auto-hospedado, com escopo por token, do Model Context Protocol para AppFlowy. Ele fornece a agentes de IA (Claude, ou qualquer cliente MCP) ferramentas para ler e editar seus espaços de trabalho do AppFlowy — listar espaços de trabalho, percorrer a árvore de páginas, criar/atualizar/ler páginas e editar blocos individuais no lugar — enquanto limita cada cliente exatamente às páginas que você permite por meio de escopos em forma de árvore por token.
- 🔒 Acesso com escopo por token. O servidor faz login no AppFlowy uma vez como conta de serviço. Os clientes nunca veem essas credenciais — eles apresentam um token opaco, e cada token é restrito a um conjunto de espaços de trabalho / subárvores de páginas.
- 🌳 Escopos em forma de árvore. Conceda um espaço de trabalho inteiro, uma página de nível superior e tudo abaixo dela, ou uma página quatro níveis abaixo e seus descendentes. Combine várias concessões por token.
- 🐳 Roda em qualquer lugar. Transporte HTTP streamable, imagem multi-arquitetura pequena (
m2n2/appflowy-mcp, amd64 + arm64) no Docker Hub, pronta para Docker Compose, Kubernetes ou um chart Helm. - ✏️ Edição real. Anexar blocos, inserir blocos em qualquer posição, editar texto de blocos (formatação rica preservada) e excluir blocos — pelo mesmo caminho Yjs/CRDT que o cliente web oficial usa.
Como o acesso funciona
┌─────────────┐ 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
Duas camadas de autenticação, mantidas separadas:
- Autenticação de backend (uma conta de serviço).
APPFLOWY_BASE_URL+APPFLOWY_EMAIL/APPFLOWY_PASSWORD(ou umAPPFLOWY_ACCESS_TOKENpré-gerado). O servidor faz login uma vez e atualiza automaticamente na expiração. - Autenticação de cliente (muitos tokens). Cada cliente MCP apresenta um token. O token decide o que ele pode acessar — as credenciais de backend nunca são expostas.
Escopos
Um escopo é um caminho de ids do AppFlowy:
| Escopo | Concessões |
|---|---|
| (lista vazia) | tudo que a conta de serviço pode ver |
WORKSPACE | o espaço de trabalho inteiro |
WORKSPACE/VIEW | essa página e tudo aninhado abaixo dela |
WORKSPACE/VIEW_L1/VIEW_L2/VIEW_L3 | uma página vários níveis abaixo e sua subárvore |
O último id é a raiz da subárvore permitida; ids anteriores apenas ajudam a localizá-la (os ids de visualização do AppFlowy são globalmente únicos, então ids intermediários são opcionais). Um token pode listar vários escopos para conceder múltiplas subárvores disjuntas de uma vez.
A aplicação é por ancestralidade: para qualquer página que uma ferramenta toque, o servidor sobe a árvore de pastas; se alcançar uma das raízes permitidas do token, a chamada prossegue, caso contrário é rejeitada. Get workspace list e Get workspace folder são podados para o que o token pode ver.
Configuração
Tudo é configurável por variáveis de ambiente (ideal para Docker / Helm) e/ou um arquivo YAML/JSON. As variáveis de ambiente têm precedência sobre o arquivo.
Variáveis de ambiente
| Variável | Descrição |
|---|---|
APPFLOWY_BASE_URL | URL base do AppFlowy Cloud, ex.: https://appflowy.example.com |
APPFLOWY_EMAIL / APPFLOWY_PASSWORD | Login da conta de serviço (concessão de senha GoTrue) |
APPFLOWY_ACCESS_TOKEN | JWT pré-gerado em vez de e-mail/senha (tem precedência) |
APPFLOWY_MCP_CONFIG | Caminho opcional para um arquivo de configuração YAML/JSON |
APPFLOWY_MCP_HOST / APPFLOWY_MCP_PORT / APPFLOWY_MCP_PATH | Endereço de escuta (padrão 0.0.0.0:8000/mcp) |
APPFLOWY_MCP_REQUIRE_AUTH | true (padrão) rejeita solicitações não autenticadas; false + sem tokens = modo aberto |
APPFLOWY_MCP_FOLDER_CACHE_TTL | Segundos para armazenar em cache as árvores de pastas para verificações de escopo (padrão 15) |
APPFLOWY_MCP_LOG_LEVEL | INFO (padrão), DEBUG, … |
Tokens via env — duas formas equivalentes.
Blob JSON (melhor como um único segredo Helm/Docker):
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"]}
]'
Indexado (sem JSON embutido):
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
Arquivo de configuração
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
Veja config.example.yaml e .env.example.
Executando
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
Um chart mínimo está em 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":[]}]'
A partir do código-fonte
uv run appflowy-mcp
Conectando um cliente
O servidor fala HTTP streamable em http://HOST:PORT/mcp. Aponte seu cliente MCP para ele e envie o token como um cabeçalho bearer. Para Claude Code:
{
"mcpServers": {
"appflowy": {
"type": "http",
"url": "https://appflowy-mcp.example.com/mcp",
"headers": { "Authorization": "Bearer sk-full" }
}
}
}
Verificação de saúde: GET /healthz → {"status":"ok"}.
Ferramentas
| Ferramenta | Propósito |
|---|---|
Get workspace list | Listar espaços de trabalho visíveis ao token |
Get workspace folder | Árvore de páginas de um espaço de trabalho, podada ao escopo |
Create new page | Criar uma página sob um pai permitido |
Update page | Renomear / definir ícone / bloquear |
Get page details | Metadados completos da página + conteúdo |
Append content to page | Anexar blocos ao final |
Get page blocks | Listar os blocos de uma página em ordem (ids + texto) |
Insert block | Inserir um novo bloco em qualquer posição (incl. um image de uma URL pública) |
Edit block text | Substituir o texto/conteúdo rico de um bloco no lugar |
Delete block | Excluir um bloco folha |
Create database | Criar um banco de dados de grade/quadro/calendário sob um pai |
Get workspace databases | Listar bancos de dados (+ suas visualizações), com escopo |
Get database fields | Listar os campos/colunas de um banco de dados |
Add database field | Adicionar uma coluna (texto, número, seleção, data, …) |
List database rows | Listar linhas, células chaveadas por nome de campo (opcionalmente documentos de linha) |
Get database row | Ler as células de uma linha por id (opcionalmente seu documento) |
Create database row | Adicionar uma linha a partir de células {field: value} (+ documento markdown opcional) |
Update database row | Editar as células de uma linha existente no lugar, por id da linha |
Delete database row | Remover uma linha de todas as visualizações do banco de dados |
Move page to trash / Restore page from trash / Delete page from trash | Ciclo de vida da lixeira |
Get trash / Get favorite pages | Listagens, com escopo |
Toggle favorite page | (Des)favoritar uma página |
Notas e limites
- As ferramentas de edição de blocos exigem
pycrdt(incluído). Elas espelham o CRDTweb-updatedo cliente web; não há endpoint REST oficial por bloco. Insert blockcomblock_type="image"incorpora uma URL de imagem pública por referência (nada é enviado); remova-a comDelete blockcomo qualquer bloco.- As células das linhas do banco de dados são chaveadas por nome ou id do campo; os valores seguem o tipo do campo (string para texto/URL, número para Número, bool para Caixa de seleção, ISO-8601 ou segundos unix para Data/Hora, nome(s) da opção para seleção).
Create database rowpassa pelo endpoint REST;Update database roweDelete database rowagem em uma linha existente pelo seu id através do caminho CRDTweb-update(espelhando o cliente web), já que o REST não expõe rota para editar ou excluir uma linha por UUID. - As verificações de escopo dependem da árvore de pastas do espaço de trabalho, armazenada em cache por
APPFLOWY_MCP_FOLDER_CACHE_TTLsegundos. Páginas recém-criadas invalidam o cache para seu espaço de trabalho. - Modo aberto (
APPFLOWY_MCP_REQUIRE_AUTH=falsesem tokens) concede acesso total a qualquer pessoa que alcance a porta — use apenas em uma rede confiável.
Desenvolvimento
uv sync # install runtime + dev dependencies
uv run pytest # run the test suite with the 100% coverage gate
uv run ruff check # lint
A suíte exige 100% de cobertura de linhas e ramos (--cov-fail-under=100 em pyproject.toml). O CI a executa como o job test em .github/workflows/docker.yml; a construção da imagem Docker needs: test, então uma falha de teste ou queda de cobertura bloqueia a construção da imagem. Veja AGENTS.md para a definição de pronto dos testes.
Licença
MIT — veja LICENSE.
Este projeto começou como uma reformulação focada em auto-hospedagem de LucasXu0/appflowy_mcp.