MAj MCP Serveur

This commit is contained in:
ocroguennec committed 2026-07-20 17:30:08 +02:00
1 parent 6e58731a20
commit 9eb19efd92
4 files changed
+268 -19

No files matched your search

+130
View File
@@ -179,6 +179,136 @@ export default function Aide() {
</p> </p>
</FaqItem> </FaqItem>
<FaqItem question="Comment configurer le serveur MCP en production (sans passer par le développement local) ?">
<p style={{ marginTop: 0 }}>
Le serveur MCP tourne déjà en continu en production (service Docker <code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4 }}>crowdlending-mcp</code>,
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.
</p>
<h4 style={{ margin: '16px 0 8px', color: 'var(--text)' }}>Prérequis</h4>
<ul style={{ margin: '0 0 12px 16px', paddingLeft: 0, lineHeight: 1.8 }}>
<li>Le service <code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4 }}>crowdlending-mcp</code> du
<code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4 }}> docker-compose.yml</code> doit être déployé, avec un enregistrement
DNS pour <code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4 }}>mcp.&lt;votre domaine&gt;</code> pointant vers la même IP que l'app
(certificat TLS automatique via Traefik).</li>
<li>Une clé API <strong style={{ color: 'var(--text)' }}>dédiée à la production</strong>, distincte de celle utilisée en développement
local si vous en avez une — cela permet de révoquer l'une sans affecter l'autre.</li>
</ul>
<h4 style={{ margin: '16px 0 8px', color: 'var(--text)' }}>Étapes</h4>
<ol style={{ margin: '0 0 12px 16px', paddingLeft: 0, lineHeight: 1.8 }}>
<li>Générez une clé API dédiée : <strong style={{ color: 'var(--text)' }}>Mon compte → Clés API → Nouvelle clé</strong> (par exemple nommée « MCP Prod »).</li>
<li>Allez dans <strong style={{ color: 'var(--text)' }}>Mon compte → Serveur MCP</strong> et renseignez l'URL publique
(<code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4 }}>https://mcp.&lt;votre domaine&gt;/mcp</code>) ainsi que la clé générée. L'environnement
se détecte automatiquement sur « PROD » dès que l'URL ne contient ni <code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4 }}>localhost</code> ni
<code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4 }}> dev</code> — cochez « Forcer manuellement » si votre domaine de test prête à confusion.</li>
<li>Copiez la configuration générée dans <code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4 }}>claude_desktop_config.json</code> (Réglages
→ Développeur → Serveurs MCP locaux → Modifier la config), exactement comme en développement — seule l'URL change,
le mécanisme <code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4 }}>mcp-remote</code> (et le wrapper <code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4 }}>cmd /c</code> sous
Windows) reste identique.</li>
<li>Redémarrez complètement Claude Desktop.</li>
</ol>
<p>
Vous pouvez connecter dev et prod <strong style={{ color: 'var(--text)' }}>simultanément</strong> : répétez ces étapes une seconde fois
avec l'URL locale et une clé distincte, les deux entrées de config (<code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4 }}>crowdlending-dev</code> /
<code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4 }}> crowdlending-prod</code>) coexistent sans collision.
</p>
<h4 style={{ margin: '16px 0 8px', color: 'var(--text)' }}>Sécurité</h4>
<ul style={{ margin: '0 0 12px 16px', paddingLeft: 0, lineHeight: 1.8 }}>
<li>Le point d'entrée public n'a <strong style={{ color: 'var(--text)' }}>volontairement aucune restriction d'IP</strong> (accessible
depuis n'importe où, pour un usage nomade) : la clé API est donc la seule barrière d'accès. Traitez-la comme un
mot de passe, et révoquez-la immédiatement en cas de doute (Mon compte → Clés API).</li>
<li>Limite de 60 requêtes/minute par IP (au-delà, erreur 429) et fermeture automatique des sessions inactives
depuis plus de 30 minutes — aucune donnée de session n'est conservée entre deux connexions.</li>
<li>L'outil <code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4 }}>crowdlending_fetch_url</code> (lecture d'une page web arbitraire) reste
<strong style={{ color: 'var(--text)' }}> désactivé en production</strong>, même s'il est activé chez vous en développement local.</li>
</ul>
<p style={{ marginBottom: 0 }}>
Si la connexion reste bloquée sans erreur visible, la cause est presque toujours la même qu'en développement local
(<code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4 }}>npx</code> qui échoue silencieusement à joindre le registre npm) — voir la
section Dépannage de <strong style={{ color: 'var(--text)' }}>Mon compte → Serveur MCP</strong>.
</p>
</FaqItem>
<FaqItem question="Comment utiliser le serveur MCP au quotidien ? (fonctions disponibles et exemples)">
<p style={{ marginTop: 0 }}>
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 <strong style={{ color: 'var(--text)' }}>strictement
en lecture seule</strong> : 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.
</p>
<h4 style={{ margin: '16px 0 8px', color: 'var(--text)' }}>Fonctions disponibles</h4>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 'var(--fs-sm)', margin: '0 0 14px' }}>
<tbody>
<tr style={{ borderBottom: '1px solid var(--border)' }}>
<td style={{ padding: '8px 10px 8px 0', verticalAlign: 'top', whiteSpace: 'nowrap' }}>
<code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4 }}>crowdlending_get_investisseur</code>
</td>
<td style={{ padding: '8px 0', verticalAlign: 'top' }}>Profil de l'investisseur lié à la clé API (nom, type famille/entreprise, régime fiscal).</td>
</tr>
<tr style={{ borderBottom: '1px solid var(--border)' }}>
<td style={{ padding: '8px 10px 8px 0', verticalAlign: 'top', whiteSpace: 'nowrap' }}>
<code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4 }}>crowdlending_get_dashboard</code>
</td>
<td style={{ padding: '8px 0', verticalAlign: 'top' }}>KPIs du portefeuille : capital investi, capital en risque, montant remboursé, intérêts bruts/nets, dépôts/retraits — filtrable par année.</td>
</tr>
<tr style={{ borderBottom: '1px solid var(--border)' }}>
<td style={{ padding: '8px 10px 8px 0', verticalAlign: 'top', whiteSpace: 'nowrap' }}>
<code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4 }}>crowdlending_list_investissements</code>
</td>
<td style={{ padding: '8px 0', verticalAlign: 'top' }}>Liste des investissements (projet, émetteur, plateforme, montant, taux, durée, statut), filtrable par statut.</td>
</tr>
<tr style={{ borderBottom: '1px solid var(--border)' }}>
<td style={{ padding: '8px 10px 8px 0', verticalAlign: 'top', whiteSpace: 'nowrap' }}>
<code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4 }}>crowdlending_get_investissement</code>
</td>
<td style={{ padding: '8px 0', verticalAlign: 'top' }}>Détail complet d'un investissement (par id), y compris la liste de ses remboursements réels perçus.</td>
</tr>
<tr style={{ borderBottom: '1px solid var(--border)' }}>
<td style={{ padding: '8px 10px 8px 0', verticalAlign: 'top', whiteSpace: 'nowrap' }}>
<code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4 }}>crowdlending_list_remboursements</code>
</td>
<td style={{ padding: '8px 0', verticalAlign: 'top' }}>Historique des remboursements perçus (toutes plateformes), filtrable par période.</td>
</tr>
<tr style={{ borderBottom: '1px solid var(--border)' }}>
<td style={{ padding: '8px 10px 8px 0', verticalAlign: 'top', whiteSpace: 'nowrap' }}>
<code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4 }}>crowdlending_list_depots_retraits</code>
</td>
<td style={{ padding: '8px 0', verticalAlign: 'top' }}>Historique des mouvements de cash (dépôts et retraits), du plus récent au plus ancien.</td>
</tr>
<tr>
<td style={{ padding: '8px 10px 8px 0', verticalAlign: 'top', whiteSpace: 'nowrap' }}>
<code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4 }}>crowdlending_fetch_url</code>
</td>
<td style={{ padding: '8px 0', verticalAlign: 'top' }}>
<em>Développement local uniquement</em>, 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.
</td>
</tr>
</tbody>
</table>
<h4 style={{ margin: '16px 0 8px', color: 'var(--text)' }}>Exemples de questions</h4>
<ul style={{ margin: '0 0 12px 16px', paddingLeft: 0, lineHeight: 2 }}>
<li>« Quel est mon encours de crowdlending actuellement ? »</li>
<li>« Quel a été mon rendement (intérêts nets) en 2026 ? »</li>
<li>« Liste-moi les investissements en retard ou en procédure. »</li>
<li>« Donne-moi le détail de l'investissement 42, avec ses remboursements. »</li>
<li>« Quels remboursements ai-je reçus entre le 1er et le 30 juin 2026 ? »</li>
<li>« Quels ont été mes derniers dépôts et retraits ? »</li>
<li>« Regarde cette annonce de projet et propose-moi les infos pour créer l'investissement : [URL] »
(développement local, avec <code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4 }}>crowdlending_fetch_url</code> activé)</li>
</ul>
<p style={{ marginBottom: 0 }}>
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.
</p>
</FaqItem>
</div> </div>
)} )}
+72 -11
View File
@@ -1152,6 +1152,7 @@ function McpServerSection({ goToApiKeys }) {
const [mcpUrl, setMcpUrl] = useState(guessMcpUrl()); const [mcpUrl, setMcpUrl] = useState(guessMcpUrl());
const [apiKey, setApiKey] = useState(''); const [apiKey, setApiKey] = useState('');
const [manualLabel, setManualLabel] = useState(null); // null = auto-détecté depuis mcpUrl, sinon override manuel 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-<nom>.log dédié (voir Dépannage)
const detectedLabel = detectLabelFromUrl(mcpUrl); const detectedLabel = detectLabelFromUrl(mcpUrl);
const label = manualLabel ?? detectedLabel; const label = manualLabel ?? detectedLabel;
@@ -1164,14 +1165,23 @@ function McpServerSection({ goToApiKeys }) {
// API passe en variable d'environnement plutôt que directement dans args // API passe en variable d'environnement plutôt que directement dans args
// (bug connu de Claude Desktop Windows qui tronque les valeurs à espaces). // (bug connu de Claude Desktop Windows qui tronque les valeurs à espaces).
const envVarName = `${label.toUpperCase()}_API_KEY`; 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({ const configJson = JSON.stringify({
mcpServers: { mcpServers: {
[serverKey]: { [serverKey]: isWindows
command: 'npx', ? { command: 'cmd', args: ['/c', 'npx', ...mcpRemoteArgs], env: { [envVarName]: apiKey || '<VOTRE_CLE_API>' } }
args: ['-y', 'mcp-remote', mcpUrl, '--header', `X-API-Key:\${${envVarName}}`], : { command: 'npx', args: mcpRemoteArgs, env: { [envVarName]: apiKey || '<VOTRE_CLE_API>' } },
env: { [envVarName]: apiKey || '<VOTRE_CLE_API>' },
},
}, },
}, null, 2); }, null, 2);
@@ -1261,18 +1271,34 @@ function McpServerSection({ goToApiKeys }) {
<h4 style={{ margin: '0 0 6px', fontSize: 'var(--fs-sm)' }}>3. Copier la configuration</h4> <h4 style={{ margin: '0 0 6px', fontSize: 'var(--fs-sm)' }}>3. Copier la configuration</h4>
<p style={{ margin: '0 0 10px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}> <p style={{ margin: '0 0 10px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
Dans Claude Desktop : Réglages → Développeur → Serveurs MCP locaux → <strong>Modifier la config</strong>. Dans Claude Desktop : Réglages → Développeur → Serveurs MCP locaux → <strong>Modifier la config</strong>.
Ce bouton ouvre le bon fichier quelle que soit votre installation — le chemin diffère en effet Selon l'installation (même téléchargée directement depuis anthropic.com — l'origine ne garantit
selon que Claude Desktop vient de claude.ai (<code>%APPDATA%\Claude\claude_desktop_config.json</code>) rien), Claude Desktop peut être packagé en MSIX et virtualiser ce fichier : le bouton ouvre parfois
ou du Microsoft Store (dossier virtualisé sous <code>...\Packages\Claude_*\LocalCache\Roaming\Claude\</code>). une copie sous <code>...\Packages\Claude_*\LocalCache\Roaming\Claude\claude_desktop_config.json</code>
Si le fichier contient déjà une clé <code>"mcpServers"</code>, ajoutez-y seulement l'entrée alors que l'app tourne réellement avec <code>%APPDATA%\Claude\claude_desktop_config.json</code> (ou
<code>"{serverKey}"</code> ci-dessous sans écraser le reste ; sinon collez le bloc entier. l'inverse). Si vos outils <code>crowdlending_*</code> n'apparaissent jamais après configuration,
vérifiez les <strong>deux emplacements</strong> et éditez celui qui correspond au dossier où
<code>logs\mcp.log</code> se met réellement à jour quand vous relancez l'app (voir Dépannage
ci-dessous). Si le fichier contient déjà une clé <code>"mcpServers"</code>, ajoutez-y seulement
l'entrée <code>"{serverKey}"</code> sans écraser le reste ; sinon collez le bloc entier.
</p> </p>
{isWindows && (
<p style={{ margin: '0 0 10px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
La config ci-dessous passe par <code>cmd /c npx</code> plutôt que <code>npx</code> directement :
nécessaire sous Windows, où Claude Desktop ne sait pas lancer <code>npx</code> (script
<code>.cmd</code>) sans passer par l'interpréteur de commandes — sinon le serveur reste bloqué
sur « running » sans jamais répondre.
</p>
)}
<img <img
src="/mcp/claude-desktop-developer-settings.png" src="/mcp/claude-desktop-developer-settings.png"
alt="Claude Desktop — Réglages → Développeur → Serveurs MCP locaux → Modifier la config" alt="Claude Desktop — Réglages → Développeur → Serveurs MCP locaux → Modifier la config"
style={{ width: '100%', maxWidth: 520, borderRadius: 8, border: '1px solid var(--border)', display: 'block', margin: '0 auto 16px' }} style={{ width: '100%', maxWidth: 520, borderRadius: 8, border: '1px solid var(--border)', display: 'block', margin: '0 auto 16px' }}
onError={(e) => { e.currentTarget.style.display = 'none'; }} onError={(e) => { e.currentTarget.style.display = 'none'; }}
/> />
<label style={{ display: 'flex', alignItems: 'center', gap: 6, margin: '0 0 10px', fontWeight: 400, fontSize: 'var(--fs-sm)', cursor: 'pointer' }}>
<input type="checkbox" checked={debugFlag} onChange={e => setDebugFlag(e.target.checked)} style={{ width: 'auto' }} />
Ajouter <code>--debug</code> (recommandé en cas de problème — voir Dépannage ci-dessous)
</label>
<CopyBlock text={configJson} /> <CopyBlock text={configJson} />
<h4 style={{ margin: '20px 0 6px', fontSize: 'var(--fs-sm)' }}>4. Redémarrer Claude Desktop</h4> <h4 style={{ margin: '20px 0 6px', fontSize: 'var(--fs-sm)' }}>4. Redémarrer Claude Desktop</h4>
@@ -1281,12 +1307,47 @@ function McpServerSection({ goToApiKeys }) {
</p> </p>
<h4 style={{ margin: '0 0 6px', fontSize: 'var(--fs-sm)' }}>5. Vérifier</h4> <h4 style={{ margin: '0 0 6px', fontSize: 'var(--fs-sm)' }}>5. Vérifier</h4>
<p style={{ margin: 0, fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}> <p style={{ margin: '0 0 20px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
Dans la liste des outils MCP de Claude Desktop, les outils <code>crowdlending_*</code> doivent Dans la liste des outils MCP de Claude Desktop, les outils <code>crowdlending_*</code> doivent
apparaître (6 en production, 7 en développement local si <code>crowdlending_fetch_url</code> est apparaître (6 en production, 7 en développement local si <code>crowdlending_fetch_url</code> est
activé — voir <code>mcp-server/README.md</code>). Testez avec une question du type « Quel est mon activé — voir <code>mcp-server/README.md</code>). Testez avec une question du type « Quel est mon
encours de crowdlending actuellement ? ». encours de crowdlending actuellement ? ».
</p> </p>
<h4 style={{ margin: '0 0 6px', fontSize: 'var(--fs-sm)' }}>6. Dépannage</h4>
<p style={{ margin: '0 0 4px', fontSize: 'var(--fs-sm)', fontWeight: 600 }}>
Le serveur reste sur « running » indéfiniment, aucun outil n'apparaît, aucune erreur visible
</p>
<p style={{ margin: '0 0 14px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
Cause la plus fréquente sous Windows, même avec <code>cmd /c</code> déjà en place : <code>npx</code>
recontacte le registre npm (<code>registry.npmjs.org</code>) à 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 <code>UNABLE_TO_VERIFY_LEAF_SIGNATURE</code> dans les logs (voir plus bas).
Solution : installez <code>mcp-remote</code> une bonne fois pour toutes (<code>npm install -g
mcp-remote</code>) puis redémarrez Claude Desktop — <code>npx</code> utilisera alors le binaire déjà
installé sans repasser par le registre à chaque fois.
</p>
<p style={{ margin: '0 0 4px', fontSize: 'var(--fs-sm)', fontWeight: 600 }}>Où trouver les logs</p>
<p style={{ margin: '0 0 14px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
<code>logs\mcp.log</code> trace les échanges entre Claude Desktop et le process local (tous serveurs
confondus) ; <code>logs\mcp-server-{serverKey}.log</code> contient la sortie détaillée de ce serveur
précis, uniquement si <code>--debug</code> 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 <code>logs</code>, à 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).
</p>
<p style={{ margin: '0 0 4px', fontSize: 'var(--fs-sm)', fontWeight: 600 }}>Vérifier côté serveur</p>
<p style={{ margin: 0, fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
La console du serveur (le terminal où tourne <code>npm run dev</code>) 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é
<code>npx</code>/<code>mcp-remote</code> (voir ci-dessus), pas dans <code>server.js</code>.
</p>
</div> </div>
); );
} }
+42 -1
View File
@@ -41,6 +41,7 @@ Connectez ensuite Claude Desktop : Réglages → Développeur → Serveurs MCP
locaux → **Modifier la config**, puis ajoutez dans `mcpServers` : locaux → **Modifier la config**, puis ajoutez dans `mcpServers` :
```json ```json
// macOS / Linux
{ {
"mcpServers": { "mcpServers": {
"crowdlending-dev": { "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 **Important :** `claude_desktop_config.json` n'accepte que des entrées
`command`/`args` — il n'existe pas de champ `url`/`headers` natif dans ce `command`/`args` — il n'existe pas de champ `url`/`headers` natif dans ce
fichier (contrairement à d'autres clients MCP). Pour un serveur HTTP comme 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 `X-API-Key` inclus. Aucune installation manuelle requise, `npx` le télécharge
à la volée. à 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 (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 passe donc la valeur réelle (avec l'espace éventuel) via une variable
d'environnement dans `env` plutôt que directement dans `args`. 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 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 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 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 votre config utilise un bloc `url`/`headers` directement, non supporté par
`claude_desktop_config.json` (voir Développement local plus haut) : il faut `claude_desktop_config.json` (voir Développement local plus haut) : il faut
passer par `command: "npx"` + `mcp-remote`. 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é API invalide ou révoquée`** — régénérez une clé dans Mon compte →
Clés API et mettez à jour la config. Clés API et mettez à jour la config.
- **`Impossible de joindre l'API`** — le backend n'est pas démarré, ou - **`Impossible de joindre l'API`** — le backend n'est pas démarré, ou
+24 -7
View File
@@ -17,10 +17,11 @@
* des outils de CETTE session uniquement (voir createSession). Deux sessions * des outils de CETTE session uniquement (voir createSession). Deux sessions
* ne partagent jamais d'état ni de données. * ne partagent jamais d'état ni de données.
* *
* Connexion depuis Claude Desktop (dev comme prod) : * Connexion depuis Claude Desktop (dev comme prod) : ce fichier expose du
* { "url": "http://localhost:4100/mcp", "headers": { "X-API-Key": "..." } } * HTTP pur, mais claude_desktop_config.json n'accepte que des entrées
* (remplacer l'URL par https://mcp.crowdlending.croguennec.net/mcp pour la * command/args — la connexion passe donc par le pont mcp-remote
* prod). Voir README.md pour le détail. * (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'; 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é'}`); 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]"). */ /** Ajoute le libellé d'environnement au titre d'un outil (ex. "[PROD]"). */
const withLabel = (title) => LABEL ? `${title} [${LABEL.toUpperCase()}]` : title; const withLabel = (title) => LABEL ? `${title} [${LABEL.toUpperCase()}]` : title;
@@ -118,7 +130,7 @@ setInterval(() => {
const now = Date.now(); const now = Date.now();
for (const [sessionId, s] of sessions.entries()) { for (const [sessionId, s] of sessions.entries()) {
if (now - s.lastActivity > SESSION_IDLE_TIMEOUT_MS) { 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(); s.transport.close();
sessions.delete(sessionId); sessions.delete(sessionId);
} }
@@ -179,7 +191,10 @@ app.post('/mcp', async (req, res) => {
return; return;
} }
touchSession(sessionId); touchSession(sessionId);
const label = describeRequest(req.body);
const startedAt = Date.now();
await session.transport.handleRequest(req, res, req.body); 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; return;
} }
@@ -195,9 +210,11 @@ app.post('/mcp', async (req, res) => {
} }
const transport = await createSession(apiKey); const transport = await createSession(apiKey);
const startedAt = Date.now();
await transport.handleRequest(req, res, req.body); 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) { } 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 }); 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.delete('/mcp', handleExistingSession);
app.listen(PORT, '0.0.0.0', () => { 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}`);
}); });