API V1 Public + preparation serveur MCP

This commit is contained in:
2026-07-15 19:33:13 +02:00
parent c843464ccd
commit 4d8fb9bab8
8 changed files with 2541 additions and 9 deletions
+190
View File
@@ -22,6 +22,9 @@ function IconKey() {
function IconTrash() {
return <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/></svg>;
}
function IconServer() {
return <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><rect x="2" y="3" width="20" height="7" rx="1.5"/><rect x="2" y="14" width="20" height="7" rx="1.5"/><path d="M6 6.5h.01"/><path d="M6 17.5h.01"/></svg>;
}
/* ── Dropdown custom style Finary ────────────────────────────── */
const LANGUES = [
@@ -1087,6 +1090,191 @@ function ApiKeysSection() {
);
}
/* ── Serveur MCP ──────────────────────────────────────────────
Guide pas-à-pas pour connecter Claude Desktop au serveur MCP local
(mcp-server/ à la racine du projet). Le JSON de config est généré
côté client à partir des champs ci-dessous ; la clé API saisie ici
reste uniquement en mémoire du navigateur, elle n'est jamais envoyée
au backend — seulement utilisée pour composer l'aperçu à copier. ── */
/** Devine une URL d'API raisonnable selon l'environnement courant :
* en dev (Vite sur :5173), le process Node du serveur MCP ne passe pas
* par le proxy Vite, il faut donc viser directement le port du backend (4000).
* En prod (nginx sert front + /api sur la même origine), l'origine courante convient. */
function guessMcpApiUrl() {
if (typeof window === 'undefined') return 'http://localhost:4000/api/v1';
const { hostname, port, origin } = window.location;
if (port === '5173') return `http://${hostname}:4000/api/v1`;
return `${origin}/api/v1`;
}
/** Détecte automatiquement l'environnement ('dev' ou 'prod') à partir de
* l'URL de l'API : localhost/IP locale ou nom d'hôte contenant "dev" →
* dev, tout le reste → prod. Best-effort — reste modifiable manuellement
* pour les cas particuliers (domaine de test qui ne contient pas "dev"...). */
function detectLabelFromUrl(url) {
if (!url) return 'prod';
let hostname;
try { hostname = new URL(url).hostname.toLowerCase(); }
catch { hostname = url.toLowerCase(); }
const isLocal = hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1'
|| hostname.endsWith('.local');
return (isLocal || hostname.includes('dev')) ? 'dev' : 'prod';
}
function CopyBlock({ text }) {
const [copied, setCopied] = useState(false);
const copy = async () => {
try {
await navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch { /* clipboard indisponible */ }
};
return (
<div style={{
display: 'flex', alignItems: 'flex-start', gap: 8, padding: '10px 12px',
borderRadius: 8, border: '1px solid var(--border)', background: 'var(--surface-2, #f9fafb)',
fontFamily: 'monospace', fontSize: 12,
}}>
<pre style={{ flex: 1, margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}>{text}</pre>
<button type="button" className="ghost" onClick={copy} style={{ flexShrink: 0 }}>
{copied ? 'Copié ✓' : 'Copier'}
</button>
</div>
);
}
function McpServerSection({ goToApiKeys }) {
const [mcpPath, setMcpPath] = useState('C:\\dev\\crowdlending-app\\mcp-server\\index.js');
const [apiUrl, setApiUrl] = useState(guessMcpApiUrl());
const [apiKey, setApiKey] = useState('');
const [manualLabel, setManualLabel] = useState(null); // null = auto-détecté depuis apiUrl, sinon override manuel
const detectedLabel = detectLabelFromUrl(apiUrl);
const label = manualLabel ?? detectedLabel;
const serverKey = `crowdlending-${label}`;
const env = {
CROWDLENDING_API_KEY: apiKey || '<VOTRE_CLE_API>',
CROWDLENDING_API_URL: apiUrl,
CROWDLENDING_LABEL: label,
};
const configJson = JSON.stringify({
mcpServers: {
[serverKey]: {
command: 'node',
args: [mcpPath],
env,
},
},
}, null, 2);
return (
<div className="card" style={{ marginTop: 20 }}>
<h3 style={{ margin: '0 0 4px' }}>Serveur MCP</h3>
<p className="text-muted" style={{ margin: '0 0 20px', fontSize: 'var(--fs-sm)' }}>
Permet à Claude Desktop (ou tout client MCP) de consulter votre portefeuille en lecture seule.
Le serveur (dossier <code>mcp-server/</code> du projet) doit être installé sur cette machine
(<code>npm install</code>) voir <code>mcp-server/README.md</code> pour le détail.
</p>
<h4 style={{ margin: '0 0 6px', fontSize: 'var(--fs-sm)' }}>1. Créer une clé API dédiée</h4>
<p style={{ margin: '0 0 10px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
Utilisez une clé distincte pour le serveur MCP (ex. nommée « MCP Desktop »), pour pouvoir la
révoquer indépendamment des autres usages.
</p>
{goToApiKeys && (
<button type="button" className="ghost" onClick={goToApiKeys} style={{ marginBottom: 20 }}>
Aller créer une clé
</button>
)}
<h4 style={{ margin: '0 0 10px', fontSize: 'var(--fs-sm)' }}>2. Renseigner les paramètres</h4>
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, marginBottom: 20, maxWidth: 520 }}>
<div>
<label>Chemin vers mcp-server/index.js</label>
<input value={mcpPath} onChange={e => setMcpPath(e.target.value)} />
</div>
<div>
<label>URL de l'API</label>
<input value={apiUrl} onChange={e => setApiUrl(e.target.value)} />
</div>
<div>
<label>Clé API</label>
<input value={apiKey} onChange={e => setApiKey(e.target.value)}
placeholder="clk_live_… (collée ici juste pour générer l'aperçu ci-dessous, non envoyée)" />
</div>
<div>
<label>Environnement détecté</label>
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 4 }}>
<span style={{
fontSize: 12, fontWeight: 600, padding: '3px 10px', borderRadius: 10,
background: label === 'dev' ? 'var(--warning-bg, #fffbeb)' : 'var(--success-bg, #f0fdf4)',
color: label === 'dev' ? 'var(--warning-text, #92400e)' : 'var(--success, #16a34a)',
}}>
{label.toUpperCase()}
</span>
<span style={{ fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
{manualLabel === null
? <>déduit de l'URL de l'API ci-dessus (localhost / « dev » → dev, sinon prod)</>
: <>forcé manuellement</>}
</span>
</div>
<label style={{ display: 'flex', alignItems: 'center', gap: 6, marginTop: 8, fontWeight: 400, fontSize: 'var(--fs-sm)', cursor: 'pointer' }}>
<input type="checkbox" checked={manualLabel !== null}
onChange={e => setManualLabel(e.target.checked ? detectedLabel : null)}
style={{ width: 'auto' }} />
Forcer manuellement (URL ambiguë, ex. domaine de test sans « dev » dans le nom)
</label>
{manualLabel !== null && (
<select value={manualLabel} onChange={e => setManualLabel(e.target.value)} style={{ marginTop: 8 }}>
<option value="dev">dev</option>
<option value="prod">prod</option>
</select>
)}
</div>
</div>
<p style={{ margin: '-8px 0 20px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
Claude Desktop peut se connecter à plusieurs serveurs MCP en même temps : pour avoir dev et
prod accessibles simultanément, répétez ces étapes une deuxième fois avec une URL d'API pointant
vers l'autre environnement (et une clé API distincte) — l'étiquette et la clé de config
(<code>{serverKey}</code> ci-dessous) s'ajustent automatiquement. Elle apparaît dans le titre et
la description de chaque outil, pour que l'agent ne confonde jamais les deux portefeuilles.
</p>
<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)' }}>
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 que Claude Desktop vient de claude.ai (<code>%APPDATA%\Claude\claude_desktop_config.json</code>)
ou du Microsoft Store (dossier virtualisé sous <code>...\Packages\Claude_*\LocalCache\Roaming\Claude\</code>).
Si le fichier contient déjà une clé <code>"mcpServers"</code>, ajoutez-y seulement l'entrée
<code>"{serverKey}"</code> ci-dessous sans écraser le reste ; sinon collez le bloc entier.
</p>
<img
src="/mcp/claude-desktop-developer-settings.png"
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' }}
onError={(e) => { e.currentTarget.style.display = 'none'; }}
/>
<CopyBlock text={configJson} />
<h4 style={{ margin: '20px 0 6px', fontSize: 'var(--fs-sm)' }}>4. Redémarrer Claude Desktop</h4>
<p style={{ margin: '0 0 10px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
Quittez complètement l'application (pas juste fermer la fenêtre) puis rouvrez-la.
</p>
<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)' }}>
Dans la liste des outils MCP de Claude Desktop, les 7 outils <code>crowdlending_*</code> doivent
apparaître. Testez avec une question du type « Quel est mon encours de crowdlending actuellement ? ».
</p>
</div>
);
}
/* ── Page principale ─────────────────────────────────────────── */
export default function MonCompte() {
const { search } = useLocation();
@@ -1100,6 +1288,7 @@ export default function MonCompte() {
{ id: 'profil', label: 'Mon compte', icon: <IconUser /> },
{ id: 'securite', label: 'Sécurité', icon: <IconLock /> },
{ id: 'api-keys', label: 'Clés API', icon: <IconKey /> },
{ id: 'mcp', label: 'Serveur MCP', icon: <IconServer /> },
];
return (
@@ -1125,6 +1314,7 @@ export default function MonCompte() {
{section === 'profil' && <><AccountForm /><DeleteAccountSection /></>}
{section === 'securite' && <><SecurityForm /><TwoFASection user={user} /><TrustedDevicesSection /></>}
{section === 'api-keys' && <ApiKeysSection />}
{section === 'mcp' && <McpServerSection goToApiKeys={() => setSection('api-keys')} />}
</div>
</div>