555 lines
29 KiB
Markdown
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` |