Appflowy MCP
MCP para instancia autoalojada de Appflowy Cloud con interfaz HTTP, preparado en Docker
Documentación
appflowy-mcp
🐳 m2n2/appflowy-mcp:latest en Docker Hub
Un servidor Model Context Protocol autoalojado y con acceso limitado por token para AppFlowy. Proporciona a los agentes de IA (Claude o cualquier cliente MCP) herramientas para leer y editar tus espacios de trabajo de AppFlowy: listar espacios de trabajo, recorrer el árbol de páginas, crear/actualizar/leer páginas y editar bloques individuales en su lugar, mientras limita cada cliente exactamente a las páginas que tú permitas mediante ámbitos con forma de árbol por token.
- 🔒 Acceso limitado por token. El servidor inicia sesión en AppFlowy una vez como cuenta de servicio. Los clientes nunca ven esas credenciales: presentan un token opaco, y cada token está restringido a un conjunto de espacios de trabajo / subárboles de páginas.
- 🌳 Ámbitos con forma de árbol. Concede un espacio de trabajo completo, una página de nivel superior con todo lo que contiene, o una página cuatro niveles hacia abajo y sus descendientes. Combina varias concesiones por token.
- 🐳 Se ejecuta en cualquier lugar. Transporte HTTP transmisible, imagen multiarquitectura pequeña (
m2n2/appflowy-mcp, amd64 + arm64) en Docker Hub, lista para Docker Compose, Kubernetes o un chart de Helm. - ✏️ Edición real. Añade bloques, inserta bloques en cualquier posición, edita el texto de bloques (conservando el formato enriquecido) y elimina bloques, mediante la misma ruta Yjs/CRDT que usa el cliente web oficial.
Cómo funciona el acceso
┌─────────────┐ 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
Dos capas de autenticación, mantenidas por separado:
- Autenticación de backend (una cuenta de servicio).
APPFLOWY_BASE_URL+APPFLOWY_EMAIL/APPFLOWY_PASSWORD(o unAPPFLOWY_ACCESS_TOKENpreviamente generado). El servidor inicia sesión una vez y se refresca automáticamente al expirar. - Autenticación de cliente (muchos tokens). Cada cliente MCP presenta un token. El token decide qué puede tocar: las credenciales de backend nunca se exponen.
Ámbitos
Un ámbito es una ruta de ids de AppFlowy:
| Ámbito | Concede |
|---|---|
| (lista vacía) | todo lo que la cuenta de servicio puede ver |
WORKSPACE | el espacio de trabajo completo |
WORKSPACE/VIEW | esa página y todo lo anidado bajo ella |
WORKSPACE/VIEW_L1/VIEW_L2/VIEW_L3 | una página varios niveles hacia abajo y su subárbol |
El último id es la raíz del subárbol permitido; los ids anteriores solo ayudan a localizarla (los ids de vista de AppFlowy son globalmente únicos, por lo que los ids intermedios son opcionales). Un token puede listar varios ámbitos para conceder varios subárboles disjuntos a la vez.
La aplicación se basa en la ascendencia: para cualquier página que una herramienta toque, el servidor recorre el árbol de carpetas hacia arriba; si alcanza una de las raíces permitidas del token, la llamada continúa; de lo contrario, se rechaza. Get workspace list y Get workspace folder se recortan a lo que el token puede ver.
Configuración
Todo es configurable mediante variables de entorno (ideal para Docker / Helm) y/o un archivo YAML/JSON. Las variables de entorno tienen prioridad sobre el archivo.
Variables de entorno
| Variable | Descripción |
|---|---|
APPFLOWY_BASE_URL | URL base de AppFlowy Cloud, p. ej. https://appflowy.example.com |
APPFLOWY_EMAIL / APPFLOWY_PASSWORD | Inicio de sesión de la cuenta de servicio (concesión de contraseña GoTrue) |
APPFLOWY_ACCESS_TOKEN | JWT previamente generado en lugar de correo/contraseña (tiene prioridad) |
APPFLOWY_MCP_CONFIG | Ruta opcional a un archivo de configuración YAML/JSON |
APPFLOWY_MCP_HOST / APPFLOWY_MCP_PORT / APPFLOWY_MCP_PATH | Dirección de escucha (por defecto 0.0.0.0:8000/mcp) |
APPFLOWY_MCP_REQUIRE_AUTH | true (por defecto) rechaza solicitudes no autenticadas; false + sin tokens = modo abierto |
APPFLOWY_MCP_FOLDER_CACHE_TTL | Segundos para almacenar en caché los árboles de carpetas para las comprobaciones de ámbito (por defecto 15) |
APPFLOWY_MCP_LOG_LEVEL | INFO (por defecto), DEBUG, … |
Tokens mediante variables de entorno: dos formas equivalentes.
Blob JSON (mejor como un único secreto de 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 (sin JSON incrustado):
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
Archivo de configuración
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
Consulta config.example.yaml y .env.example.
Ejecución
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
Un chart mínimo se encuentra en 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":[]}]'
Desde el código fuente
uv run appflowy-mcp
Conexión de un cliente
El servidor habla HTTP transmisible en http://HOST:PORT/mcp. Apunta tu cliente MCP hacia él y envía el token como cabecera de portador. Para Claude Code:
{
"mcpServers": {
"appflowy": {
"type": "http",
"url": "https://appflowy-mcp.example.com/mcp",
"headers": { "Authorization": "Bearer sk-full" }
}
}
}
Comprobación de estado: GET /healthz → {"status":"ok"}.
Herramientas
| Herramienta | Propósito |
|---|---|
Get workspace list | Lista los espacios de trabajo visibles para el token |
Get workspace folder | Árbol de páginas de un espacio de trabajo, recortado al ámbito |
Create new page | Crea una página bajo un padre permitido |
Update page | Renombrar / establecer icono / bloquear |
Get page details | Metadatos completos de la página + contenido |
Append content to page | Añade bloques al final |
Get page blocks | Lista los bloques de una página en orden (ids + texto) |
Insert block | Inserta un nuevo bloque en cualquier posición (incluido un image desde una URL pública) |
Edit block text | Reemplaza el texto/contenido enriquecido de un bloque en su lugar |
Delete block | Elimina un bloque hoja |
Create database | Crea una base de datos de cuadrícula/tablero/calendario bajo un padre |
Get workspace databases | Lista las bases de datos (+ sus vistas), con ámbito |
Get database fields | Lista los campos/columnas de una base de datos |
Add database field | Añade una columna (texto, número, selección, fecha, …) |
List database rows | Lista filas, celdas claveadas por nombre de campo (opcionalmente documentos de fila) |
Get database row | Lee las celdas de una fila por id (opcionalmente su documento) |
Create database row | Añade una fila desde celdas {field: value} (+ documento markdown opcional) |
Update database row | Edita las celdas de una fila existente en su lugar, por id de fila |
Delete database row | Elimina una fila de todas las vistas de la base de datos |
Move page to trash / Restore page from trash / Delete page from trash | Ciclo de vida de la papelera |
Get trash / Get favorite pages | Listados, con ámbito |
Toggle favorite page | Marcar/desmarcar una página como favorita |
Notas y limitaciones
- Las herramientas de edición de bloques requieren
pycrdt(incluido). Reflejan elweb-updateCRDT del cliente web; no existe un endpoint REST oficial por bloque. Insert blockconblock_type="image"incrusta una URL de imagen pública por referencia (no se sube nada); elimínalo conDelete blockcomo cualquier bloque.- Las celdas de fila de la base de datos se clavean por nombre o id de campo; los valores siguen el tipo de campo (cadena para texto/URL, número para Número, booleano para Casilla, ISO-8601 o segundos Unix para FechaHora, nombre(s) de opción para selección).
Create database rowpasa por el endpoint REST;Update database rowyDelete database rowactúan sobre una fila existente por su id mediante la rutaweb-updateCRDT (reflejando el cliente web), ya que REST no expone ninguna ruta para editar o eliminar una fila por UUID. - Las comprobaciones de ámbito dependen del árbol de carpetas del espacio de trabajo, almacenado en caché durante
APPFLOWY_MCP_FOLDER_CACHE_TTLsegundos. Las páginas recién creadas invalidan la caché de su espacio de trabajo. - El modo abierto (
APPFLOWY_MCP_REQUIRE_AUTH=falsesin tokens) concede acceso completo a cualquiera que pueda alcanzar el puerto: úsalo solo en una red de confianza.
Desarrollo
uv sync # install runtime + dev dependencies
uv run pytest # run the test suite with the 100% coverage gate
uv run ruff check # lint
El conjunto de pruebas exige 100 % de cobertura de líneas y ramas (--cov-fail-under=100 en pyproject.toml). CI lo ejecuta como el trabajo test en .github/workflows/docker.yml; la compilación de la imagen Docker needs: test, por lo que una prueba fallida o una caída de cobertura impide que la imagen se compile. Consulta AGENTS.md para la definición de "hecho" de las pruebas.
Licencia
MIT — consulta LICENSE.
Este proyecto comenzó como una reelaboración centrada en el autoalojamiento de LucasXu0/appflowy_mcp.