Merge branch 'worktree-111-mcp-connecteur' into dev (connecteur MCP, scope B #111)
This commit is contained in:
@@ -17,6 +17,12 @@ SECTIONS = [
|
||||
"href": "/compte/roadmap",
|
||||
"require_subscription": True,
|
||||
},
|
||||
{
|
||||
"key": "mcp",
|
||||
"label": "Connecteur MCP",
|
||||
"href": "/compte/mcp",
|
||||
"require_subscription": True,
|
||||
},
|
||||
{
|
||||
"key": "abonnement",
|
||||
"label": "Abonnement",
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
import os
|
||||
|
||||
import dash_bootstrap_components as dbc
|
||||
from dash import dcc, html, register_page
|
||||
from flask import session
|
||||
from flask_login import current_user
|
||||
|
||||
from src.api import tokens_db
|
||||
from src.pages._compte_shell import account_guard, account_shell
|
||||
|
||||
register_page(
|
||||
__name__,
|
||||
path="/compte/mcp",
|
||||
title="Connecteur MCP | colibre",
|
||||
name="Connecteur MCP",
|
||||
description="Connectez votre agent IA aux données colibre via le protocole MCP.",
|
||||
)
|
||||
|
||||
MCP_ENABLED = os.getenv("DASH_MCP_ENABLED") == "true"
|
||||
_TOKEN_PLACEHOLDER = "<VOTRE_JETON>"
|
||||
|
||||
|
||||
def _mcp_url() -> str:
|
||||
base = os.getenv("APP_BASE_URL", "").rstrip("/")
|
||||
return f"{base}/_mcp" if base else "/_mcp"
|
||||
|
||||
|
||||
def _csrf(index: str):
|
||||
return dcc.Input(
|
||||
type="hidden",
|
||||
id={"type": "csrf-input", "index": index},
|
||||
name="csrf_token",
|
||||
)
|
||||
|
||||
|
||||
def client_instructions(url: str, token: str):
|
||||
"""Construit les blocs d'instructions par client (fonction pure, testable)."""
|
||||
claude = f'claude mcp add colibre --transport http {url} --header "Authorization: Bearer {token}"'
|
||||
gemini = f'gemini mcp add --transport http --header "Authorization: Bearer {token}" colibre {url}'
|
||||
return dbc.Accordion(
|
||||
start_collapsed=True,
|
||||
always_open=False,
|
||||
children=[
|
||||
dbc.AccordionItem(
|
||||
title="Claude (Code / Desktop)",
|
||||
children=html.Pre(html.Code(claude)),
|
||||
),
|
||||
dbc.AccordionItem(
|
||||
title="Gemini CLI",
|
||||
children=[
|
||||
html.Pre(html.Code(gemini)),
|
||||
html.P(
|
||||
"Ou dans ~/.gemini/settings.json : un serveur mcpServers.colibre "
|
||||
f'avec "httpUrl": "{url}" et "headers": '
|
||||
f'{{"Authorization": "Bearer {token}"}}.'
|
||||
),
|
||||
],
|
||||
),
|
||||
dbc.AccordionItem(
|
||||
title="Mistral Le Chat",
|
||||
children=html.P(
|
||||
"Dans les connecteurs MCP, ajoutez un serveur HTTP d'URL "
|
||||
f"{url}, authentification « API Token », en-tête "
|
||||
f"« Authorization » = « Bearer {token} »."
|
||||
),
|
||||
),
|
||||
dbc.AccordionItem(
|
||||
title="ChatGPT",
|
||||
children=[
|
||||
html.P(
|
||||
"L'app ChatGPT grand public exige le flux OAuth 2.1 et "
|
||||
"n'accepte pas de jeton statique. En attendant le connecteur "
|
||||
"OAuth (itération future), utilisez la voie développeur "
|
||||
"(API / Agents SDK OpenAI), qui accepte un serveur MCP distant "
|
||||
f"d'URL {url} avec l'en-tête « Authorization: Bearer {token} »."
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def _token_row(row: dict):
|
||||
statut = "Révoqué" if row["revoked_at"] else "Actif"
|
||||
actions = []
|
||||
if not row["revoked_at"]:
|
||||
actions = [
|
||||
html.Form(
|
||||
method="POST",
|
||||
action=f"/compte/mcp/revoquer/{row['id']}",
|
||||
children=[
|
||||
_csrf(f"revoke-{row['id']}"),
|
||||
dbc.Button(
|
||||
"Révoquer",
|
||||
type="submit",
|
||||
color="danger",
|
||||
size="sm",
|
||||
outline=True,
|
||||
),
|
||||
],
|
||||
)
|
||||
]
|
||||
return html.Tr(
|
||||
[
|
||||
html.Td(row["label"]),
|
||||
html.Td(row["created_at"]),
|
||||
html.Td(row["last_used_at"] or "—"),
|
||||
html.Td(statut),
|
||||
html.Td(actions),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def layout(**_):
|
||||
guard = account_guard("/compte/mcp", require_subscription=True)
|
||||
if guard is not None:
|
||||
return guard
|
||||
|
||||
if not MCP_ENABLED:
|
||||
contenu = html.Div(
|
||||
[
|
||||
html.H2("Connecteur MCP"),
|
||||
dbc.Alert(
|
||||
"Le connecteur MCP sera bientôt disponible. Revenez prochainement.",
|
||||
color="info",
|
||||
),
|
||||
]
|
||||
)
|
||||
return account_shell("mcp", contenu)
|
||||
|
||||
url = _mcp_url()
|
||||
new_token = session.pop("mcp_new_token", None)
|
||||
|
||||
alerts = []
|
||||
if new_token:
|
||||
alerts.append(
|
||||
dbc.Alert(
|
||||
[
|
||||
html.Strong("Votre nouveau jeton (copiez-le maintenant, "),
|
||||
html.Strong("il ne sera plus affiché) :"),
|
||||
html.Pre(html.Code(new_token)),
|
||||
],
|
||||
color="success",
|
||||
)
|
||||
)
|
||||
|
||||
tokens = tokens_db.list_user_tokens(
|
||||
os.environ["USERS_DB_PATH"], current_user.id, "mcp"
|
||||
)
|
||||
table = (
|
||||
dbc.Table(
|
||||
[
|
||||
html.Thead(
|
||||
html.Tr(
|
||||
[
|
||||
html.Th("Nom"),
|
||||
html.Th("Créé le"),
|
||||
html.Th("Dernière utilisation"),
|
||||
html.Th("Statut"),
|
||||
html.Th(""),
|
||||
]
|
||||
)
|
||||
),
|
||||
html.Tbody([_token_row(dict(r)) for r in tokens]),
|
||||
],
|
||||
striped=True,
|
||||
bordered=False,
|
||||
hover=True,
|
||||
)
|
||||
if tokens
|
||||
else html.P("Aucun jeton pour le moment.")
|
||||
)
|
||||
|
||||
create_form = html.Form(
|
||||
method="POST",
|
||||
action="/compte/mcp/creer",
|
||||
className="mb-4",
|
||||
children=[
|
||||
_csrf("mcp-create"),
|
||||
dbc.Label("Nom du jeton (ex. « Claude sur mon portable »)"),
|
||||
dbc.Input(type="text", name="label", required=True, className="mb-2"),
|
||||
dbc.Button("Générer un jeton", type="submit", color="primary"),
|
||||
],
|
||||
)
|
||||
|
||||
snippet_token = new_token or _TOKEN_PLACEHOLDER
|
||||
contenu = html.Div(
|
||||
[
|
||||
html.H2("Connecteur MCP"),
|
||||
html.P(
|
||||
"Générez un jeton pour connecter votre agent IA (Claude, Gemini, "
|
||||
"Mistral…) aux données colibre via le protocole MCP. Le jeton vaut "
|
||||
"votre identité : gardez-le secret. Un abonnement actif est requis."
|
||||
),
|
||||
*alerts,
|
||||
html.H4("Générer un jeton", className="mt-3"),
|
||||
create_form,
|
||||
html.H4("Mes jetons", className="mt-4"),
|
||||
table,
|
||||
html.H4("Connecter un client", className="mt-4"),
|
||||
html.P(f"URL du serveur MCP : {url}"),
|
||||
client_instructions(url, snippet_token),
|
||||
]
|
||||
)
|
||||
return account_shell("mcp", contenu)
|
||||
Reference in New Issue
Block a user