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

555 lines
29 KiB
Markdown

# 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_<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 {#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`