# 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 ?](#1-verdict) 2. [Ce que FlowDeck a déjà (l'essentiel est fait)](#2-ce-que-flowdeck-a-déjà) 3. [Positionnement : deux rôles complémentaires](#3-positionnement) - [3.1 Vue générale des interactions](#31-vue-générale-des-interactions) - [3.2 Workflow d'un échange avec un agent externe](#32-workflow-dun-échange-avec-un-agent-externe) - [3.3 Les agents internes passent-ils par le MCP ?](#33-les-agents-internes-passent-ils-par-le-mcp-) 4. [Architecture cible](#4-architecture-cible) 5. [Services offerts par le serveur MCP](#5-services-offerts) 6. [Plan d'implémentation pas à pas](#6-plan-dimplémentation) 7. [Transport & configuration](#7-transport--configuration) 8. [Sécurité](#8-sécurité) 9. [Configuration des clients](#9-configuration-des-clients) 10. [Tests & vérification](#10-tests--vérification) 11. [Version & roadmap](#11-version--roadmap) 12. [Liens & références](#12-liens--références) --- ## 1. Verdict {#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-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 : ```python 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_` (fonction du `user_tokens` de l'utilisateur connecté) - `verify_token(Authorization)` valide `Bearer ` 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 {#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 {#31-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 {#32-workflow-dun-é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 ? {#33-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 {#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 {#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 {#6-plan-dimplé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) : ```python """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 ```bash # 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 {#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é {#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 {#9-configuration-des-clients} ### Claude Desktop — `claude_desktop_config.json` ```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) ```yaml 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 ```bash # via mcp-remote (bridge stdio→HTTP) npx mcp-remote http://flowdeck.lab.home:8123/mcp --header "Authorization: Bearer fd_xxx" ``` --- ## 10. Tests & vérification {#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 {#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 {#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`