API V1 Public + preparation serveur MCP
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user