Appflowy MCP

MCP para instancia autoalojada de Appflowy Cloud con interfaz HTTP, preparado en Docker

Documentación

appflowy-mcp

Docker Image Version Docker Pulls Docker Image Size Architectures MCP Coverage License: MIT

🐳 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:

  1. Autenticación de backend (una cuenta de servicio). APPFLOWY_BASE_URL + APPFLOWY_EMAIL/APPFLOWY_PASSWORD (o un APPFLOWY_ACCESS_TOKEN previamente generado). El servidor inicia sesión una vez y se refresca automáticamente al expirar.
  2. 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:

ÁmbitoConcede
(lista vacía)todo lo que la cuenta de servicio puede ver
WORKSPACEel espacio de trabajo completo
WORKSPACE/VIEWesa página y todo lo anidado bajo ella
WORKSPACE/VIEW_L1/VIEW_L2/VIEW_L3una 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

VariableDescripción
APPFLOWY_BASE_URLURL base de AppFlowy Cloud, p. ej. https://appflowy.example.com
APPFLOWY_EMAIL / APPFLOWY_PASSWORDInicio de sesión de la cuenta de servicio (concesión de contraseña GoTrue)
APPFLOWY_ACCESS_TOKENJWT previamente generado en lugar de correo/contraseña (tiene prioridad)
APPFLOWY_MCP_CONFIGRuta opcional a un archivo de configuración YAML/JSON
APPFLOWY_MCP_HOST / APPFLOWY_MCP_PORT / APPFLOWY_MCP_PATHDirección de escucha (por defecto 0.0.0.0:8000/mcp)
APPFLOWY_MCP_REQUIRE_AUTHtrue (por defecto) rechaza solicitudes no autenticadas; false + sin tokens = modo abierto
APPFLOWY_MCP_FOLDER_CACHE_TTLSegundos para almacenar en caché los árboles de carpetas para las comprobaciones de ámbito (por defecto 15)
APPFLOWY_MCP_LOG_LEVELINFO (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

HerramientaPropósito
Get workspace listLista 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 pageCrea una página bajo un padre permitido
Update pageRenombrar / establecer icono / bloquear
Get page detailsMetadatos completos de la página + contenido
Append content to pageAñade bloques al final
Get page blocksLista los bloques de una página en orden (ids + texto)
Insert blockInserta un nuevo bloque en cualquier posición (incluido un image desde una URL pública)
Edit block textReemplaza el texto/contenido enriquecido de un bloque en su lugar
Delete blockElimina un bloque hoja
Create databaseCrea una base de datos de cuadrícula/tablero/calendario bajo un padre
Get workspace databasesLista las bases de datos (+ sus vistas), con ámbito
Get database fieldsLista los campos/columnas de una base de datos
Add database fieldAñade una columna (texto, número, selección, fecha, …)
List database rowsLista filas, celdas claveadas por nombre de campo (opcionalmente documentos de fila)
Get database rowLee las celdas de una fila por id (opcionalmente su documento)
Create database rowAñade una fila desde celdas {field: value} (+ documento markdown opcional)
Update database rowEdita las celdas de una fila existente en su lugar, por id de fila
Delete database rowElimina una fila de todas las vistas de la base de datos
Move page to trash / Restore page from trash / Delete page from trashCiclo de vida de la papelera
Get trash / Get favorite pagesListados, con ámbito
Toggle favorite pageMarcar/desmarcar una página como favorita

Notas y limitaciones

  • Las herramientas de edición de bloques requieren pycrdt (incluido). Reflejan el web-update CRDT del cliente web; no existe un endpoint REST oficial por bloque.
  • Insert block con block_type="image" incrusta una URL de imagen pública por referencia (no se sube nada); elimínalo con Delete block como 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 row pasa por el endpoint REST; Update database row y Delete database row actúan sobre una fila existente por su id mediante la ruta web-update CRDT (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_TTL segundos. Las páginas recién creadas invalidan la caché de su espacio de trabajo.
  • El modo abierto (APPFLOWY_MCP_REQUIRE_AUTH=false sin 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.