diff --git a/frontend/src/pages/Aide.jsx b/frontend/src/pages/Aide.jsx index 67486df..65a52fe 100644 --- a/frontend/src/pages/Aide.jsx +++ b/frontend/src/pages/Aide.jsx @@ -179,6 +179,136 @@ export default function Aide() {

+ +

+ Le serveur MCP tourne déjà en continu en production (service Docker crowdlending-mcp, + exposé via Traefik) — contrairement au développement local, vous n'avez rien à démarrer ni + à laisser tourner sur votre machine. Il suffit de connecter Claude Desktop à l'URL publique. +

+ +

Prérequis

+ + +

Étapes

+
    +
  1. Générez une clé API dédiée : Mon compte → Clés API → Nouvelle clé (par exemple nommée « MCP Prod »).
  2. +
  3. Allez dans Mon compte → Serveur MCP et renseignez l'URL publique + (https://mcp.<votre domaine>/mcp) ainsi que la clé générée. L'environnement + se détecte automatiquement sur « PROD » dès que l'URL ne contient ni localhost ni + dev — cochez « Forcer manuellement » si votre domaine de test prête à confusion.
  4. +
  5. Copiez la configuration générée dans claude_desktop_config.json (Réglages + → Développeur → Serveurs MCP locaux → Modifier la config), exactement comme en développement — seule l'URL change, + le mécanisme mcp-remote (et le wrapper cmd /c sous + Windows) reste identique.
  6. +
  7. Redémarrez complètement Claude Desktop.
  8. +
+

+ Vous pouvez connecter dev et prod simultanément : répétez ces étapes une seconde fois + avec l'URL locale et une clé distincte, les deux entrées de config (crowdlending-dev / + crowdlending-prod) coexistent sans collision. +

+ +

Sécurité

+ +

+ Si la connexion reste bloquée sans erreur visible, la cause est presque toujours la même qu'en développement local + (npx qui échoue silencieusement à joindre le registre npm) — voir la + section Dépannage de Mon compte → Serveur MCP. +

+
+ + +

+ Une fois connecté, Claude (Desktop ou tout autre client MCP) peut consulter votre portefeuille en langage + naturel — il choisit lui-même le bon outil selon votre question. Le serveur est strictement + en lecture seule : aucune donnée n'est jamais créée, modifiée ou supprimée depuis une conversation. Toute + saisie (nouvel investissement, remboursement…) reste manuelle dans l'application. +

+ +

Fonctions disponibles

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ crowdlending_get_investisseur + Profil de l'investisseur lié à la clé API (nom, type famille/entreprise, régime fiscal).
+ crowdlending_get_dashboard + KPIs du portefeuille : capital investi, capital en risque, montant remboursé, intérêts bruts/nets, dépôts/retraits — filtrable par année.
+ crowdlending_list_investissements + Liste des investissements (projet, émetteur, plateforme, montant, taux, durée, statut), filtrable par statut.
+ crowdlending_get_investissement + Détail complet d'un investissement (par id), y compris la liste de ses remboursements réels perçus.
+ crowdlending_list_remboursements + Historique des remboursements perçus (toutes plateformes), filtrable par période.
+ crowdlending_list_depots_retraits + Historique des mouvements de cash (dépôts et retraits), du plus récent au plus ancien.
+ crowdlending_fetch_url + + Développement local uniquement, désactivé par défaut. Lit une page web (ex. annonce de projet sur + une plateforme) et en extrait le texte propre — c'est à vous d'en reprendre les informations utiles pour + créer l'investissement manuellement, l'outil ne saisit rien lui-même. +
+ +

Exemples de questions

+ +

+ Ces exemples fonctionnent aussi bien en dev qu'en prod dès lors que le serveur correspondant est connecté + (voir les FAQ de configuration ci-dessus) — les six premiers outils sont identiques dans les deux environnements. +

+
+ )} diff --git a/frontend/src/pages/MonCompte.jsx b/frontend/src/pages/MonCompte.jsx index 0a386b0..cddfe16 100644 --- a/frontend/src/pages/MonCompte.jsx +++ b/frontend/src/pages/MonCompte.jsx @@ -1152,6 +1152,7 @@ function McpServerSection({ goToApiKeys }) { const [mcpUrl, setMcpUrl] = useState(guessMcpUrl()); const [apiKey, setApiKey] = useState(''); const [manualLabel, setManualLabel] = useState(null); // null = auto-détecté depuis mcpUrl, sinon override manuel + const [debugFlag, setDebugFlag] = useState(false); // ajoute --debug : génère un fichier mcp-server-.log dédié (voir Dépannage) const detectedLabel = detectLabelFromUrl(mcpUrl); const label = manualLabel ?? detectedLabel; @@ -1164,14 +1165,23 @@ function McpServerSection({ goToApiKeys }) { // API passe en variable d'environnement plutôt que directement dans args // (bug connu de Claude Desktop Windows qui tronque les valeurs à espaces). const envVarName = `${label.toUpperCase()}_API_KEY`; + const mcpRemoteArgs = [ + '-y', 'mcp-remote', mcpUrl, '--header', `X-API-Key:\${${envVarName}}`, + ...(debugFlag ? ['--debug'] : []), + ]; + + // Sur Windows, npx est en réalité npx.cmd (un script) : child_process.spawn, + // utilisé par Claude Desktop, ne sait pas l'exécuter directement sans passer + // par l'interpréteur de commandes — le serveur reste bloqué sur "running" + // sans jamais répondre. Il faut donc l'appeler via cmd /c. Sans risque sur + // macOS/Linux, qui n'ont pas ce problème (npx s'exécute nativement). + const isWindows = typeof navigator !== 'undefined' && /win/i.test(navigator.platform || navigator.userAgent || ''); const configJson = JSON.stringify({ mcpServers: { - [serverKey]: { - command: 'npx', - args: ['-y', 'mcp-remote', mcpUrl, '--header', `X-API-Key:\${${envVarName}}`], - env: { [envVarName]: apiKey || '' }, - }, + [serverKey]: isWindows + ? { command: 'cmd', args: ['/c', 'npx', ...mcpRemoteArgs], env: { [envVarName]: apiKey || '' } } + : { command: 'npx', args: mcpRemoteArgs, env: { [envVarName]: apiKey || '' } }, }, }, null, 2); @@ -1261,18 +1271,34 @@ function McpServerSection({ goToApiKeys }) {

3. Copier la configuration

Dans Claude Desktop : Réglages → Développeur → Serveurs MCP locaux → Modifier la config. - Ce bouton ouvre le bon fichier quelle que soit votre installation — le chemin diffère en effet - selon que Claude Desktop vient de claude.ai (%APPDATA%\Claude\claude_desktop_config.json) - ou du Microsoft Store (dossier virtualisé sous ...\Packages\Claude_*\LocalCache\Roaming\Claude\). - Si le fichier contient déjà une clé "mcpServers", ajoutez-y seulement l'entrée - "{serverKey}" ci-dessous sans écraser le reste ; sinon collez le bloc entier. + Selon l'installation (même téléchargée directement depuis anthropic.com — l'origine ne garantit + rien), Claude Desktop peut être packagé en MSIX et virtualiser ce fichier : le bouton ouvre parfois + une copie sous ...\Packages\Claude_*\LocalCache\Roaming\Claude\claude_desktop_config.json + alors que l'app tourne réellement avec %APPDATA%\Claude\claude_desktop_config.json (ou + l'inverse). Si vos outils crowdlending_* n'apparaissent jamais après configuration, + vérifiez les deux emplacements et éditez celui qui correspond au dossier où + logs\mcp.log se met réellement à jour quand vous relancez l'app (voir Dépannage + ci-dessous). Si le fichier contient déjà une clé "mcpServers", ajoutez-y seulement + l'entrée "{serverKey}" sans écraser le reste ; sinon collez le bloc entier.

+ {isWindows && ( +

+ La config ci-dessous passe par cmd /c npx plutôt que npx directement : + nécessaire sous Windows, où Claude Desktop ne sait pas lancer npx (script + .cmd) sans passer par l'interpréteur de commandes — sinon le serveur reste bloqué + sur « running » sans jamais répondre. +

+ )} Claude Desktop — Réglages → Développeur → Serveurs MCP locaux → Modifier la config { e.currentTarget.style.display = 'none'; }} /> +

4. Redémarrer Claude Desktop

@@ -1281,12 +1307,47 @@ function McpServerSection({ goToApiKeys }) {

5. Vérifier

-

+

Dans la liste des outils MCP de Claude Desktop, les outils crowdlending_* doivent apparaître (6 en production, 7 en développement local si crowdlending_fetch_url est activé — voir mcp-server/README.md). Testez avec une question du type « Quel est mon encours de crowdlending actuellement ? ».

+ +

6. Dépannage

+ +

+ Le serveur reste sur « running » indéfiniment, aucun outil n'apparaît, aucune erreur visible +

+

+ Cause la plus fréquente sous Windows, même avec cmd /c déjà en place : npx + recontacte le registre npm (registry.npmjs.org) à chaque lancement pour vérifier la + version, et un antivirus ou un proxy avec inspection HTTPS (Avast, Kaspersky, ESET, proxy + d'entreprise…) fait échouer cette requête avec une erreur de certificat — invisible depuis Claude + Desktop, qui attend simplement une réponse jamais reçue jusqu'à expirer au bout d'une minute. + Confirmez en cherchant UNABLE_TO_VERIFY_LEAF_SIGNATURE dans les logs (voir plus bas). + Solution : installez mcp-remote une bonne fois pour toutes (npm install -g + mcp-remote) puis redémarrez Claude Desktop — npx utilisera alors le binaire déjà + installé sans repasser par le registre à chaque fois. +

+ +

Où trouver les logs

+

+ logs\mcp.log trace les échanges entre Claude Desktop et le process local (tous serveurs + confondus) ; logs\mcp-server-{serverKey}.log contient la sortie détaillée de ce serveur + précis, uniquement si --debug est activé ci-dessus (case à cocher, étape 3). Le bouton + « Afficher les journaux » de Claude Desktop peut ne pas s'ouvrir sous Windows (bug connu) — allez + chercher directement dans le dossier logs, à l'un des deux emplacements mentionnés à + l'étape 3 (essayez l'autre si l'un des deux est vide ou ne se met pas à jour). +

+ +

Vérifier côté serveur

+

+ La console du serveur (le terminal où tourne npm run dev) affiche désormais une ligne + par requête reçue — session, outil appelé, statut, durée. Si rien n'y apparaît alors qu'un appel a + été fait depuis Claude Desktop, la requête n'arrive jamais jusqu'ici : le problème est côté + npx/mcp-remote (voir ci-dessus), pas dans server.js. +

); } diff --git a/mcp-server/README.md b/mcp-server/README.md index dcf697e..3c8304a 100644 --- a/mcp-server/README.md +++ b/mcp-server/README.md @@ -41,6 +41,7 @@ Connectez ensuite Claude Desktop : Réglages → Développeur → Serveurs MCP locaux → **Modifier la config**, puis ajoutez dans `mcpServers` : ```json +// macOS / Linux { "mcpServers": { "crowdlending-dev": { @@ -60,6 +61,28 @@ locaux → **Modifier la config**, puis ajoutez dans `mcpServers` : } ``` +```json +// Windows +{ + "mcpServers": { + "crowdlending-dev": { + "command": "cmd", + "args": [ + "/c", "npx", + "-y", + "mcp-remote", + "http://localhost:4100/mcp", + "--header", + "X-API-Key:${CROWDLENDING_API_KEY}" + ], + "env": { + "CROWDLENDING_API_KEY": "clk_live_..." + } + } + } +} +``` + **Important :** `claude_desktop_config.json` n'accepte que des entrées `command`/`args` — il n'existe pas de champ `url`/`headers` natif dans ce fichier (contrairement à d'autres clients MCP). Pour un serveur HTTP comme @@ -70,7 +93,16 @@ qui se charge de parler HTTP à notre serveur en coulisses, en-tête `X-API-Key` inclus. Aucune installation manuelle requise, `npx` le télécharge à la volée. -Notez l'absence d'espace autour du `:` dans `--header` : Claude Desktop +**Sous Windows, le wrapper `cmd /c` est obligatoire** (voir le second bloc +ci-dessus) : `npx` y est en réalité `npx.cmd` (un script), et la façon dont +Claude Desktop lance les process (`spawn` sans interpréteur de commandes) ne +sait pas l'exécuter directement — sans ce wrapper, le serveur reste affiché +comme « running » dans Claude Desktop mais ne répond jamais (blocage +silencieux, pas d'erreur explicite). C'est un problème Node.js/Windows +connu, pas spécifique à ce serveur — la config générée automatiquement dans +Mon compte → Serveur MCP l'applique déjà si elle détecte Windows. + +Notez aussi l'absence d'espace autour du `:` dans `--header` : Claude Desktop (Windows) a un bug connu qui tronque les arguments contenant un espace — on passe donc la valeur réelle (avec l'espace éventuel) via une variable d'environnement dans `env` plutôt que directement dans `args`. @@ -123,6 +155,9 @@ possible, même si les noms d'outils sont identiques des deux côtés. } ``` +Sous Windows, remplacez `"command": "npx"` par `"command": "cmd", "args": ["/c", "npx", ...]` +sur chacune des deux entrées (voir Développement local plus haut). + Utilisez deux clés API différentes (une par instance) : ça permet de révoquer l'une sans affecter l'autre. Réglez `MCP_LABEL=dev` (côté serveur local, dans votre `.env` ou variable d'environnement au lancement) pour que @@ -216,6 +251,12 @@ Garde-fous : votre config utilise un bloc `url`/`headers` directement, non supporté par `claude_desktop_config.json` (voir Développement local plus haut) : il faut passer par `command: "npx"` + `mcp-remote`. +- **Le serveur reste sur « running » indéfiniment, « Preparing session… » puis + « Could not attach »** — sous Windows, il manque le wrapper `cmd /c` (voir + Développement local). C'est un problème de fond très courant : `npx` y est + un script `.cmd` que `spawn()` ne sait pas exécuter directement, donc le + process est lancé mais ne communique jamais réellement. Symptôme + caractéristique : aucune erreur explicite, juste un blocage silencieux. - **`Clé API invalide ou révoquée`** — régénérez une clé dans Mon compte → Clés API et mettez à jour la config. - **`Impossible de joindre l'API`** — le backend n'est pas démarré, ou diff --git a/mcp-server/server.js b/mcp-server/server.js index 641e973..326b8fd 100644 --- a/mcp-server/server.js +++ b/mcp-server/server.js @@ -17,10 +17,11 @@ * des outils de CETTE session uniquement (voir createSession). Deux sessions * ne partagent jamais d'état ni de données. * - * Connexion depuis Claude Desktop (dev comme prod) : - * { "url": "http://localhost:4100/mcp", "headers": { "X-API-Key": "..." } } - * (remplacer l'URL par https://mcp.crowdlending.croguennec.net/mcp pour la - * prod). Voir README.md pour le détail. + * Connexion depuis Claude Desktop (dev comme prod) : ce fichier expose du + * HTTP pur, mais claude_desktop_config.json n'accepte que des entrées + * command/args — la connexion passe donc par le pont mcp-remote + * (https://github.com/geelen/mcp-remote), voir README.md pour la config + * exacte (et le wrapper cmd /c obligatoire sous Windows). */ import { randomUUID } from 'node:crypto'; @@ -63,6 +64,17 @@ const SESSION_IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 min console.error(`[crowdlending-mcp${LABEL ? `-${LABEL}` : ''}] démarrage — API cible : ${API_BASE}, hôtes autorisés : ${ALLOWED_HOSTS.join(', ')}, fetch_url : ${ENABLE_FETCH_URL ? 'activé' : 'désactivé'}`); +/** Préfixe commun à toutes les lignes de log applicatif. */ +const LOG_PREFIX = `[crowdlending-mcp${LABEL ? `-${LABEL}` : ''}]`; + +/** Résumé lisible d'une requête JSON-RPC entrante (nom d'outil pour un + * tools/call, méthode sinon), pour le log applicatif — jamais la clé API. */ +function describeRequest(body) { + if (!body || typeof body !== 'object') return 'requête inconnue'; + if (body.method === 'tools/call') return `tools/call ${body.params?.name || '?'}`; + return body.method || 'requête inconnue'; +} + /** Ajoute le libellé d'environnement au titre d'un outil (ex. "[PROD]"). */ const withLabel = (title) => LABEL ? `${title} [${LABEL.toUpperCase()}]` : title; @@ -118,7 +130,7 @@ setInterval(() => { const now = Date.now(); for (const [sessionId, s] of sessions.entries()) { if (now - s.lastActivity > SESSION_IDLE_TIMEOUT_MS) { - console.error(`[crowdlending-mcp${LABEL ? `-${LABEL}` : ''}] session ${sessionId} inactive depuis plus de 30 min, fermeture.`); + console.error(`${LOG_PREFIX} session ${sessionId.slice(0, 8)} inactive depuis plus de 30 min, fermeture.`); s.transport.close(); sessions.delete(sessionId); } @@ -179,7 +191,10 @@ app.post('/mcp', async (req, res) => { return; } touchSession(sessionId); + const label = describeRequest(req.body); + const startedAt = Date.now(); await session.transport.handleRequest(req, res, req.body); + console.error(`${LOG_PREFIX} session ${sessionId.slice(0, 8)} — ${label} — ${res.statusCode} (${Date.now() - startedAt}ms)`); return; } @@ -195,9 +210,11 @@ app.post('/mcp', async (req, res) => { } const transport = await createSession(apiKey); + const startedAt = Date.now(); await transport.handleRequest(req, res, req.body); + console.error(`${LOG_PREFIX} nouvelle session ${transport.sessionId ? transport.sessionId.slice(0, 8) : '?'} — initialize — ${res.statusCode} (${Date.now() - startedAt}ms)`); } catch (e) { - console.error(`[crowdlending-mcp${LABEL ? `-${LABEL}` : ''}] erreur /mcp POST :`, e); + console.error(`${LOG_PREFIX} erreur /mcp POST :`, e); if (!res.headersSent) res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: e.message }, id: null }); } }); @@ -220,5 +237,5 @@ app.get('/mcp', handleExistingSession); app.delete('/mcp', handleExistingSession); app.listen(PORT, '0.0.0.0', () => { - console.error(`[crowdlending-mcp${LABEL ? `-${LABEL}` : ''}] à l'écoute sur le port ${PORT}`); + console.error(`${LOG_PREFIX} à l'écoute sur le port ${PORT}`); });