Files
flowdeck/docs/FLOWDECK_MCP_SERVER_GUIDE.md
T
bruno f84ff201ea
FlowDeck CI / test (push) Failing after 17s
FlowDeck CI / docker (push) Skipped
docs: section vue générale et workflow des interactions MCP
2026-09-08 08:53:04 -04:00

29 KiB

FlowDeck — Serveur MCP : Guide complet d'intégration agents externes

Version : 1.0 · Date : 2026-09-08 · Cible : FlowDeck v5.x Sujet : Exposer FlowDeck comme serveur MCP (Model Context Protocol) pour que des agents externes (Claude Desktop/Code, Cursor, OpenClaw/Hermes, n'importe quel client MCP) puissent lire et modifier le workspace. Prérequis : FlowDeck v5.x · FastAPI · SQLite WAL · agent interne déjà en place (AgentEngine + ToolRegistry)


Table des matières

  1. Verdict : est-ce une bonne idée ?
  2. Ce que FlowDeck a déjà (l'essentiel est fait)
  3. Positionnement : deux rôles complémentaires
  4. Architecture cible
  5. Services offerts par le serveur MCP
  6. Plan d'implémentation pas à pas
  7. Transport & configuration
  8. Sécurité
  9. Configuration des clients
  10. Tests & vérification
  11. Version & roadmap
  12. Liens & références

1. Verdict

Oui, c'est une excellente idée — et c'est même l'évolution naturelle du projet. Trois raisons factuelles :

  1. C'est exactement ce que fait Notion. Le guide docs/Guide_Complet_Notion_AI.md (déjà dans le repo) documente les endpoints officiels https://mcp.notion.com/mcp (Streamable HTTP) et https://mcp.notion.com/sse. FlowDeck est un clone de Notion : l'équivalent « API publique + agents externes » de Notion, c'est un serveur MCP. On ne copie pas une idée exotique, on complète la parité avec le modèle de référence.

  2. 80 % de l'infrastructure existe déjà. Le ToolRegistry (app/services/tool_registry.py) est déjà un catalogue d'outils compatible MCP : 22 outils, chacun avec name + description + parameters (JSON Schema) + execute(args, user_id=...). C'est exactement le contrat qu'un serveur MCP expose. Le PermissionManager fait déjà l'autorisation, le journal agent_actions fait déjà l'audit + rollback, public_api.py fait déjà l'auth par token Bearer. Il manque seulement la couche de protocole MCP.

  3. Le coût marginal est minuscule. Une dépendance (mcp SDK officiel), un module (app/mcp_server.py, ~150 lignes), un point d'entrée. Aucune modification du cœur de l'app : les outils existants sont réutilisés tels quels, avec leurs permissions et leur audit. Le serveur MCP est un second visage de l'API existante, pas une nouvelle fonctionnalité monolithique.

Ce que ça apporte concrètement : n'importe quel agent externe (Claude Desktop, Cursor, OpenClaw/Hermes…) peut « voir » et piloter FlowDeck : « crée un CR de réunion dans le workspace X », « ajoute les issues du sprint au kanban », « résume les tâches en cours », « crée une collection avec statut/sprint », etc. Sans MCP, chaque agent externe devrait coder son propre client HTTP contre l'API — MCP standardise tout ça (un seul schéma d'outils, découverte automatique, types JSON Schema).


2. Ce que FlowDeck a déjà

2.1 ToolRegistry — 22 outils (cœur réutilisable)

Fichier : app/services/tool_registry.py. Chaque outil est une classe Tool avec :

class Tool:
    name: str = ""
    description: str = ""          # explication lisible par un LLM
    parameters: dict               # JSON Schema (très exactement le format MCP)
    async def execute(self, args: dict, *, user_id: int | None = None) -> ToolResult

ToolRegistry.schema(scope) retourne déjà la liste {name, description, parameters} — le format exact que tools/list MCP doit renvoyer. ToolRegistry.execute(tool, args, user_id=...) exécute avec vérification d'existence.

2.2 PermissionManager — autorisation

Fichier : app/services/permission_manager.py. API :

  • can_read(workspace_id), can_write(workspace_id), can_destructive(workspace_id)
  • assert_can(tool, args, workspace_id, approval_mode) — lève une exception si refus ; les outils destructifs (delete_*) exigent le rôle owner/admin ou approval_mode == "confirm".

2.3 Journal d'audit + rollback

AgentEngine._log_action(...) (app/services/agent_engine.py) écrit chaque action dans agent_actions (payload, résultat, snapshot undo, executed_by). undo_action(action_id) restaure. Le serveur MCP doit journaliser ses actions dans la même table — l'audit reste unifié.

2.4 Auth par token Bearer

Fichier : app/routers/public_api.py. Déjà en place :

  • POST /api/v1/token génère un token fd_<urlsafe> (fonction du user_tokens de l'utilisateur connecté)
  • verify_token(Authorization) valide Bearer <token> contre la table user_tokens

Table user_tokens(gitea_user_id, gitea_token) — attention : gitea_user_id stocke en réalité l'id local de users.id (voir SessionManager.store_token(user_id, ...)). Donc la résolution token → user est directe : SELECT gitea_user_id FROM user_tokens WHERE gitea_token = ?.

2.5 Contexte agent

ContextBuilder (app/services/context_builder.py) collecte le contexte (page courante, mentions, workspace) — utile pour la resource MCP flowdeck://context si on veut l'exposer.


3. Positionnement

Deux surfaces agentiques, complémentaires — ne pas confondre :

Agent interne (existant) Serveur MCP (à créer)
Direction L'utilisateur parle à l'agent dans FlowDeck Un agent externe parle à FlowDeck depuis dehors
Orchestration AgentEngine (boucle ReAct, LLM) Le client MCP orchestre (le serveur ne fait qu'exécuter des outils)
Interface Panneau HTML + SSE (/api/agent/*) Protocole MCP (stdio ou Streamable HTTP)
Réutilise ToolRegistry, PermissionManager, audit Le même ToolRegistry, le même PermissionManager, le même audit
Valeur Assistant intégré à l'UI FlowDeck pilotable par Claude/Cursor/Hermes/scripts MCP

Le serveur MCP ne contient pas de LLM : il exécute les outils demandés par le client. C'est le client (claude, cursor, openclaw…) qui décide quoi appeler. Le code de raisonnement n'est pas dupliqué.

3.1 Vue générale des interactions

Deux portes d'entrée distinctes, un seul socle commun :

        CHEMIN A — AGENTS EXTERNES                     CHEMIN B — AGENT INTERNE
        (serveur MCP, NOUVEAU)                          (UI existante, INCHANGÉE)

  Claude Desktop ─┐                                        Utilisateur (navigateur)
  Cursor          ├─ MCP stdio ───┐                                │
  OpenClaw/Hermes ├─ MCP HTTP ────┼──┐                             │ chat 🤖 + @mentions
  npx mcp-remote ─┘               │  │                             ▼
                      ┌───────────▼──▼──────────────┐   ┌─────────────────────────┐
                      │  Serveur MCP                │   │  AgentEngine (ReAct)    │
                      │  app/mcp_server.py          │   │  boucle raisonnement↔act │
                      │  auth token `fd_*`          │   │  streaming SSE vers l'UI │
                      └───────────┬─────────────────┘   └────────────┬────────────┘
                                  │ calls d'outils                  │ function calls
                      ┌───────────▼──────────────────────────────────▼─────────────┐
                      │              ToolRegistry — 22 outils                      │
                      │  search · read · create · update · delete · gitea · …     │
                      └───────────┬────────────────────────────────────────────────┘
                                  │
                      ┌───────────▼───────────────┐
                      │  PermissionManager        │  ← mêmes ACL pour les deux chemins
                      │  agent_actions (audit+undo)│
                      └───────────┬───────────────┘
                                  │
                      ┌───────────▼───────────────┐
                      │  SQLite · API Gitea/GitHub │
                      └───────────────────────────┘

À retenir : les agents externes (chemin A) et l'agent interne (chemin B) ne se rencontrent jamais « en vol » — ils convergent sur le même ToolRegistry, passent par les mêmes PermissionManager et le même journal agent_actions. Une action faite par Claude Desktop arrive au même endroit et avec les mêmes règles qu'une action faite par l'agent intégré au navigateur.

3.2 Workflow d'un échange avec un agent externe

Exemple réel : un utilisateur demande à Claude Desktop « crée une collection Sprint 27 avec les issues ouvertes du dépôt bruno/flowdeck ».

 Claude Desktop               Serveur MCP                ToolRegistry / Perms / SQLite
      │                              │                              │
      │  1. initialize (handshake)   │                              │
      │─────────────────────────────▶│                              │
      │  2. tools/list               │                              │
      │─────────────────────────────▶│                              │
      │   ◀── 22 outils + schémas    │                              │
      │       (JSON Schema exacts)   │                              │
      │                              │                              │
      │  3. tools/call               │                              │
      │     "sync_gitea"             │                              │
      │     {owner:"bruno",          │                              │
      │      repo:"flowdeck"}        │                              │
      │─────────────────────────────▶│  4. résolution user_id       │
      │                              │     (Bearer fd_* → users.id) │
      │                              │  5. PermissionManager        │
      │                              │     assert_can(...)          │
      │                              │  6. execute() → GET issues   │
      │                              │     Gitea + INSERT collection│
      │                              │  7. journal agent_actions    │
      │                              │     (payload + undo snapshot)│
      │   ◀── résultat JSON          │                              │
      │       {status:"success",     │                              │
      │        data:{collection_id,  │                              │
      │               issues:N}}     │                              │
      │                              │                              │
      │  8. tools/call "create_page" │  (même séquence 4→7 :        │
      │     … contenu du CR…         │   perms → execute → audit)   │

Le client MCP orchestre : il décide seul d'enchaîner sync_gitea puis create_page (ou create_document, create_gitea_issue…). Le serveur ne fait que vérifier, exécuter, auditer — l'équivalent de ce que fait AgentEngine côté interne, mais sans LLM et sans streaming UI.

3.3 Les agents internes passent-ils par le MCP ?

Non. L'agent interne continue d'appeler directement le ToolRegistry — et c'est une décision d'architecture, pas un oubli. Le serveur MCP est la porte d'entrée des agents externes uniquement.

Critère Agent interne → ToolRegistry (direct) Agent interne → serveur MCP (contournement)
Streaming SSE (raisonnement + actions live) événements natifs perdu — le MCP est atomique requête/réponse
Contexte (user, workspace, @mentions, page ouverte) déjà en mémoire dans AgentEngine à re-transmettre à chaque appel
Mode approval interactif (confirm) fonctionne (dialogue dans l'UI) impossible — pas d'humain pour confirmer
Surcharge aucune sérialisation JSON-RPC + re-auth à chaque outil
Bénéfice — aucun : mêmes outils, mêmes permissions, même base

Un mauvais aiguillage (agent interne → MCP → ToolRegistry) créerait une boucle hermétique : l'agent interne n'est pas un « agent externe », il vit dans FlowDeck et possède déjà tout ce dont le MCP aurait besoin d'être re-sécurisé. Réutiliser le serveur MCP en interne reviendrait à faire transiter des appels locaux par un protocole réseau sans protection supplémentaire.

Ce que ça ne change pas : les deux chemins respectent exactement les mêmes règles (permissions, audit, rollback), donc un agent externe ne peut ni voir ni modifier plus de choses que l'agent interne ou l'utilisateur lui-même. Et si un jour l'agent interne doit consommer des serveurs MCP externes (rôle client, façon « External Agents » de Notion), c'est une fonctionnalité séparée — documentée dans Guide_Complet_Notion_AI.md — qui n'entre pas en conflit avec le serveur décrit ici.


4. Architecture cible

                        AGENTS EXTERNES (clients MCP)
        ┌───────────────┬──────────────────┬──────────────────┐
        ▼               ▼                  ▼                  ▼
  Claude Desktop    Cursor CLI     OpenClaw/Hermes      npx mcp-remote
  (stdio local)   (stdio local)   (stdio / HTTP)      (HTTP distant)
        │               │                  │                  │
        │     ┌─────────┴──────────┐       │                  │
        │     ▼                    ▼       ▼                  ▼
        │  ┌─────────────────────────────────────────────┐
        │  │       app/mcp_server.py  (FastMCP)          │
        │  │  ─ stdio :  python -m app.mcp_server        │
        │  │  ─ HTTP  :  uvicorn (Streamable HTTP)       │
        │  │  ├─ auth MCP (token fd_* / env var)         │
        │  │  ├─ résolution user_id                      │
        │  │  └─ mise en correspondance outils MCP       │
        │  └───────────────┬─────────────────────────────┘
        │                  │  (mêmes fonctions que l'app)
        │  ┌───────────────▼─────────────────────────────┐
        │  │  ToolRegistry (22 outils) — INCHANGÉ         │
        │  │  PermissionManager — INCHANGÉ                │
        │  │  agent_actions (audit + undo) — INCHANGÉ     │
        │  │  SessionManager.get_token(user_id)           │
        │  └───────────────┬─────────────────────────────┘
        │                  │
        │      ┌───────────┴────────────┐
        │      ▼                        ▼
        │  SQLite (flowdeck.db)    Gitea / GitHub API

Principe : zéro modification des services existants. Le module MCP importe app.services.tool_registry et appelle les mêmes classes que l'agent interne. La seule chose nouvelle est la couche protocole.


5. Services offerts

Le serveur expose les 22 outils existants (déjà présents dans ToolRegistry), groupés par service :

Recherche & navigation

Outil MCP Description
search_workspace Recherche full-text (collections, pages, documents, workspaces)
read_workspaces Liste les espaces de travail accessibles et leur contenu

Lecture

Outil MCP Description
read_collection Schéma (propriétés + vues) + pages d'une collection
read_page Page de collection + valeurs + dépendances
read_document Document éditeur (titre, contenu, métadonnées)
read_gitea_issues Issues Gitea d'un repo (état, labels, milestones)

Écriture (avec snapshot undo)

Outil MCP Description
create_collection Crée une collection + vue par défaut
create_view Vue (table/board/calendar/gallery/list/timeline/gantt/chart/form/map/feed)
add_property Propriété typée
create_page Page de collection + valeurs
create_document Document éditeur dans un workspace (Markdown ou blocs)
update_page Titre / propriétés d'une page
write_blocks Contenu blocs d'un document
add_relation Relation entre collections (avec réciproque)
create_sub_item Sous-élément
add_dependency Dépendance entre pages
apply_template Page depuis un template

Gitea ↔ FlowDeck

Outil MCP Description
sync_gitea Synchronise les issues d'un repo vers une collection
create_gitea_issue Crée une issue dans un repo

Destructif (gated — voir §8)

Outil MCP Description
delete_document Corbeille (soft delete)
delete_page / delete_collection Suppression (dur, exige confirm)

Extensions possibles (plus tard)

  • Resource MCP flowdeck://workspaces et flowdeck://collection/{id} (le modèle resource MCP est naturel pour « lister mes espaces » sans appeler un outil).
  • Prompts MCP : templates « CR de réunion », « Sprint review » réutilisant les skills de l'agent interne.
  • Notifications MCP (logging/sampling) — à réserver pour plus tard, les outils couvrent 95 % des besoins.

6. Plan d'implémentation

Étape 1 — Dépendance

Ajouter dans requirements.txt :

mcp>=1.9,<2

⚠️ Piège version SDK : le SDK officiel mcp est passé en v2, où FastMCP est renommé MCPServer (from mcp.server.mcpserver import MCPServer) et les API ont changé (migration : py.sdk.modelcontextprotocol.io/v2/migration). Épingler mcp>=1.9,<2 : l'API FastMCP v1 est la plus documentée (la quasi-totalité des exemples publics) et parfaitement stable. La migration vers v2 peut se faire plus tard, sans changer les outils.

Étape 2 — Nouveau module app/mcp_server.py

Squelette de référence (les 22 outils suivent le même patron ; 3 représentatifs ci-dessous) :

"""FlowDeck — Serveur MCP (v1).

Réexpose le ToolRegistry existant comme serveur MCP.
Aucune modification des services : mêmes classes, mêmes permissions, même audit.
Transports : stdio (local) ou Streamable HTTP (distant) via --transport http.
"""
from __future__ import annotations

import json
import os
from typing import Any

from mcp.server.fastmcp import FastMCP
from app.db import get_conn
from app.services.tool_registry import (
    ToolRegistry, SearchWorkspace, ReadCollection, CreateCollection,
    CreatePage, CreateDocument, CreateGiteaIssue,  # ... + les 16 autres
)

mcp = FastMCP("flowdeck")

# ── Résolution d'utilisateur ──────────────────────────────────────────────
def resolve_user_id() -> int | None:
    """stdio : env FLOWDECK_MCP_USER_ID (dev) ou FLOWDECK_MCP_TOKEN (résout via user_tokens)."""
    uid = os.environ.get("FLOWDECK_MCP_USER_ID")
    if uid:
        return int(uid)
    token = os.environ.get("FLOWDECK_MCP_TOKEN")
    if token:
        with get_conn() as conn:
            row = conn.execute(
                "SELECT gitea_user_id FROM user_tokens WHERE gitea_token=?", (token,)
            ).fetchone()
        return row["gitea_user_id"] if row else None
    return None  # mode admin sans authentification (usage local uniquement)

# ── Outils : wrapper mince sur ToolRegistry ───────────────────────────────
async def _run(impl, args: dict) -> str:
    result = await impl.execute(args, user_id=resolve_user_id())
    return json.dumps({
        "status": result.status,
        "message": result.message,
        "data": result.data,
        "target": {"type": result.target_type, "id": result.target_id},
    }, ensure_ascii=False)

@mcp.tool()
async def search_workspace(query: str) -> str:
    """Recherche full-text dans tout FlowDeck (collections, pages, documents, workspaces)."""
    return await _run(SearchWorkspace(), {"query": query})

@mcp.tool()
async def read_collection(collection_id: int) -> str:
    """Lit le schéma (propriétés + vues) et les pages d'une collection."""
    return await _run(ReadCollection(), {"collection_id": collection_id})

@mcp.tool()
async def create_collection(name: str, description: str = "", icon: str = "📋") -> str:
    """Crée une collection avec sa vue par défaut."""
    return await _run(CreateCollection(), {"name": name, "description": description, "icon": icon})

# ... même patron pour create_page, create_document, create_view, add_property,
#     update_page, write_blocks, add_relation, create_sub_item, add_dependency,
#     apply_template, read_page, read_document, read_workspaces,
#     read_gitea_issues, sync_gitea, create_gitea_issue,
#     delete_document, delete_page, delete_collection (voir §8 pour ces 3-là)

# ── Écriture systématique dans le journal d'audit ─────────────────────────
# Chaque outil MCP DOIT journaliser dans agent_actions (comme AgentEngine._log_action)
# pour garder l'audit unifié et permettre le rollback via undo_action().

if __name__ == "__main__":
    import sys
    transport = sys.argv[1] if len(sys.argv) > 1 else "stdio"
    mcp.run(transport=transport)  # "stdio" | "streamable-http"

Étape 3 — Auditer les actions MCP

Réutiliser le schéma agent_actions : insérer un enregistrement à chaque exécution d'outil (tool_name, payload_json, result_json, status, undo_snapshot_json, executed_by=user_id). Le rollback (undo_action) fonctionne ensuite à l'identique pour les actions faites par des agents externes.

Étape 4 — Point d'entrée & lancement

# stdio (Claude Desktop, Cursor, OpenClaw…) — lancé par le client lui-même
FLOWDECK_MCP_USER_ID=1 python -m app.mcp_server

# HTTP (antémémoire: agents distants) — port par défaut 8123
FLOWDECK_MCP_TOKEN=fd_xxx uvicorn app.mcp_server_http:app --port 8123   # ou mcp.run(transport="streamable-http")

En Docker : ajouter une commande/second process au conteneur existant (même image, même volume SQLite) ou un service compose séparé partageant le même volume — la base SQLite WAL se partage sans problème avec le process principal (déjà le cas en dev local).

Étape 5 — Documentation client dans le repo

Ajouter un fichier docs/MCP_CLIENTS.md (ou une section du présent guide, §9) avec les blocs de config pour chaque client.


7. Transport & configuration

Transport Usage Lancement Auth
stdio Agents locaux (Claude Desktop, Cursor, OpenClaw) Le client spawn python -m app.mcp_server Env FLOWDECK_MCP_USER_ID / FLOWDECK_MCP_TOKEN
Streamable HTTP Agents distants / multi-clients uvicorn sur MCP_PORT (défaut 8123) Header Authorization: Bearer fd_*

Variables d'environnement (à ajouter à .env / app/config.py) :

MCP_ENABLED=true
MCP_PORT=8123
MCP_MAX_TOKENS_HISTORY=50     # contrôle de charge côté serveur (optionnel)
FLOWDECK_MCP_TOKEN=           # généré via POST /api/v1/token ou manuellement

Streamable HTTP (spéc. 2025-03-26+) remplace l'ancien SSE : le SDK FastMCP le gère nativement via mcp.run(transport="streamable-http"). C'est le protocole utilisé par Notion (mcp.notion.com/mcp).


8. Sécurité

  1. Auth — HTTP : Bearer fd_* validé contre user_tokens (pattern de public_api.verify_token). stdio : variable d'env obligatoire ; refuser de démarrer sans résolution d'utilisateur en dehors du mode local explicite (FLOWDECK_MCP_ALLOW_ANON=1 uniquement en dev).
  2. Permissions — chaque appel passe par PermissionManager.assert_can(tool, args, workspace_id, ...) comme pour l'agent interne. Un agent externe ne voit/ne modifie que ce que son utilisateur peut voir/modifier (pas de contournement MCP — même règle que Notion : « MCP does not bypass Notion permissions »).
  3. Outils destructifs — delete_document, delete_page, delete_collection : non exposés par défaut ou exigeant un paramètre explicite confirm: true + approval_mode == "confirm". Recommandation : activables par env FLOWDECK_MCP_ALLOW_DESTRUCTIVE=true, désactivé par défaut.
  4. Audit — 100 % des actions MCP journalisées dans agent_actions (qui, quoi, quand, quel user, snapshot undo) → consultable depuis l'UI agent et rollback possible.
  5. Rate limiting — réutiliser le middleware existant (slowapi) côté HTTP ; côté stdio le client contrôle le débit.
  6. Réseau — en HTTP, ne pas exposer directement : reverse-proxy avec TLS (Traefik/Caddy/Nginx) et restriction IP si l'usage est interne au homelab. Le token fd_* transite en clair sinon.

9. Configuration des clients

Claude Desktop — claude_desktop_config.json

{
  "mcpServers": {
    "flowdeck": {
      "command": "python",
      "args": ["-m", "app.mcp_server"],
      "cwd": "/home/bruno/workspace/flowdeck",
      "env": { "FLOWDECK_MCP_USER_ID": "1" }
    }
  }
}

Cursor — .cursor/mcp.json (même structure)

OpenClaw / Hermes — config.yaml (client MCP natif)

mcp:
  servers:
    flowdeck:
      command: python
      args: ["-m", "app.mcp_server"]
      cwd: /home/bruno/workspace/flowdeck
      env: { FLOWDECK_MCP_USER_ID: "1" }

Distant (HTTP) — n'importe quel client MCP

# via mcp-remote (bridge stdio→HTTP)
npx mcp-remote http://flowdeck.lab.home:8123/mcp --header "Authorization: Bearer fd_xxx"

10. Tests & vérification

  1. Inspector officiel : mcp dev app/mcp_server.py (SDK v1) — UI web pour lister les outils, exécuter chaque outil, vérifier schémas et erreurs.
  2. Test pytest (tests/test_mcp_server.py) : démarrer le serveur en stdio, client mcp.client.stdio, tools/list doit renvoyer ≥ 20 outils avec les bons inputSchema ; call_tool("create_collection") crée réellement la collection (vérifier en DB), call_tool("delete_collection") est refusé sans confirm.
  3. Vérification d'audit : après un appel MCP, une ligne agent_actions doit exister avec executed_by = l'utilisateur résolu.
  4. CI Gitea Actions : ajouter ces tests à la pipeline existante (pytest + ruff).

11. Version & roadmap

Proposer une entrée en roadmap (le projet est à v5.x, realtime en v5.13.0) :

v5.14.0 — Serveur MCP : exposition du ToolRegistry via MCP (stdio + Streamable HTTP), auth par token fd_*, audit unifié agent_actions, outils destructifs gated, guide client + tests.

Étapes de livraison : (1) squelette + inspector, (2) audit + rollback, (3) transport HTTP + auth, (4) configs clients + docs, (5) CI.


12. Liens & références

  • Spec MCP : https://modelcontextprotocol.io
  • SDK Python officiel : https://github.com/modelcontextprotocol/python-sdk (épingler <2 pour FastMCP v1)
  • Notion MCP (modèle de référence, déjà documenté dans ce repo) : docs/Guide_Complet_Notion_AI.md
  • Code à réutiliser : app/services/tool_registry.py · app/services/permission_manager.py · app/routers/public_api.py (auth token) · app/services/agent_engine.py (agent_actions + undo)
  • Guide agent interne existant : docs/Flowdeck_Agent_integration.md