Flowdeck 7.76.0 — serveur MCP en écriture (create_page, update_page, create_collection)
- create_page: collection_id (int, requis), title (string, requis), icon (optionnel), properties (objet optionnel -> property_values_json)
- update_page: id (int, requis), title (optionnel), properties (objet optionnel, remplace property_values_json)
- create_collection: name (requis), description (optionnel), icon (optionnel) + vue par défaut
- Aucune suppression; erreurs métier en {"erreur": "..."}; GET /mcp read_only=false et liste mise à jour
- Tests étendus: création+relecture, mise à jour, create_collection, erreurs, 401 sans jeton sur écriture
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
"""Fusion — serveur MCP Flowdeck (branchement du service d'agents).
|
||||
|
||||
POST /mcp en lecture seule, authentifié par les jetons d'API
|
||||
personnels existants (api_tokens). Le service d'agents lit les
|
||||
collections et leurs pages au nom du propriétaire du jeton.
|
||||
POST /mcp, authentifié par les jetons d'API personnels existants
|
||||
(api_tokens). Le service d'agents lit et écrit (création/mise à
|
||||
jour de pages, création de collections — aucune suppression) au
|
||||
nom du propriétaire du jeton.
|
||||
"""
|
||||
import hashlib
|
||||
import json
|
||||
@@ -68,7 +69,8 @@ def test_mcp_handshake_et_outils(client, jeton):
|
||||
r = _rpc(client, jeton, 2, "tools/list")
|
||||
noms = [t["name"] for t in r.json()["result"]["tools"]]
|
||||
assert noms == ["list_collections", "get_collection", "get_page",
|
||||
"search_pages", "list_repos"]
|
||||
"search_pages", "list_repos", "create_page",
|
||||
"update_page", "create_collection"]
|
||||
|
||||
|
||||
def test_mcp_collections_et_pages(client, jeton, donnees):
|
||||
@@ -84,3 +86,81 @@ def test_mcp_collections_et_pages(client, jeton, donnees):
|
||||
assert [p["title"] for p in res] == ["Page de cadrage"]
|
||||
res = _appel(client, jeton, 4, "get_collection", {"id": 99999})
|
||||
assert "introuvable" in res["erreur"]
|
||||
|
||||
|
||||
def test_mcp_create_page_puis_relecture(client, jeton, donnees):
|
||||
creee = _appel(client, jeton, 10, "create_page", {
|
||||
"collection_id": donnees, "title": "Page créée via MCP",
|
||||
"icon": "file", "properties": {"statut": "à faire", "priorite": "haute"}})
|
||||
assert creee["title"] == "Page créée via MCP"
|
||||
assert creee["collection_id"] == donnees
|
||||
assert creee["properties"] == {"statut": "à faire", "priorite": "haute"}
|
||||
detail = _appel(client, jeton, 11, "get_collection", {"id": donnees})
|
||||
titres = [p["title"] for p in detail["pages"]]
|
||||
assert "Page créée via MCP" in titres
|
||||
relue = [p for p in detail["pages"] if p["id"] == creee["id"]][0]
|
||||
assert relue["properties"] == {"statut": "à faire", "priorite": "haute"}
|
||||
|
||||
|
||||
def test_mcp_update_page_titre_et_proprietes(client, jeton, donnees):
|
||||
creee = _appel(client, jeton, 20, "create_page", {
|
||||
"collection_id": donnees, "title": "Titre initial",
|
||||
"properties": {"statut": "brouillon"}})
|
||||
maj = _appel(client, jeton, 21, "update_page", {
|
||||
"id": creee["id"], "title": "Titre mis à jour",
|
||||
"properties": {"statut": "publié"}})
|
||||
assert maj["title"] == "Titre mis à jour"
|
||||
# properties remplace property_values_json (pas de fusion)
|
||||
assert maj["properties"] == {"statut": "publié"}
|
||||
relue = _appel(client, jeton, 22, "get_page", {"id": creee["id"]})
|
||||
assert relue["title"] == "Titre mis à jour"
|
||||
assert relue["properties"] == {"statut": "publié"}
|
||||
|
||||
|
||||
def test_mcp_create_collection(client, jeton):
|
||||
creee = _appel(client, jeton, 30, "create_collection", {
|
||||
"name": "Projet Bêta", "description": "Créée via MCP", "icon": "🧪"})
|
||||
assert creee["name"] == "Projet Bêta"
|
||||
assert creee["description"] == "Créée via MCP"
|
||||
assert creee["icon"] == "🧪"
|
||||
cols = _appel(client, jeton, 31, "list_collections")
|
||||
beta = [c for c in cols if c["name"] == "Projet Bêta"]
|
||||
assert beta and beta[0]["id"] == creee["id"]
|
||||
assert beta[0]["page_count"] == 0
|
||||
detail = _appel(client, jeton, 32, "get_collection", {"id": creee["id"]})
|
||||
assert detail["collection"]["name"] == "Projet Bêta"
|
||||
assert detail["pages"] == []
|
||||
|
||||
|
||||
def test_mcp_erreurs_ecritures(client, jeton, donnees):
|
||||
res = _appel(client, jeton, 40, "create_page", {
|
||||
"collection_id": 99999, "title": "Orpheline"})
|
||||
assert "introuvable" in res["erreur"]
|
||||
res = _appel(client, jeton, 41, "create_page", {
|
||||
"collection_id": donnees, "title": " "})
|
||||
assert "erreur" in res
|
||||
res = _appel(client, jeton, 42, "update_page", {
|
||||
"id": 99999, "title": "Fantôme"})
|
||||
assert "introuvable" in res["erreur"]
|
||||
res = _appel(client, jeton, 43, "create_collection", {"name": " "})
|
||||
assert "erreur" in res
|
||||
|
||||
|
||||
def test_mcp_ecriture_sans_jeton_401(client, donnees):
|
||||
r = _rpc(client, None, 50, "tools/call",
|
||||
{"name": "create_page",
|
||||
"arguments": {"collection_id": donnees, "title": "Refusée"}})
|
||||
assert r.status_code == 401
|
||||
r = _rpc(client, "mauvais-jeton", 51, "tools/call",
|
||||
{"name": "create_collection", "arguments": {"name": "Refusée"}})
|
||||
assert r.status_code == 401
|
||||
|
||||
|
||||
def test_mcp_info_get(client, jeton):
|
||||
r = client.get("/mcp")
|
||||
assert r.status_code == 200
|
||||
infos = r.json()
|
||||
assert infos["read_only"] is False
|
||||
assert "create_page" in infos["tools"]
|
||||
assert "update_page" in infos["tools"]
|
||||
assert "create_collection" in infos["tools"]
|
||||
|
||||
Reference in New Issue
Block a user