MCP : délais de connexion et d'appel réglables dans l'interface

Repris d'AJEAN c05a0a9f et e44b6139. Les deux délais MCP, jusqu'ici codés en
dur (20 s pour se connecter, 120 s pour un appel d'outil), deviennent des
réglages globaux : clé bkState mcp_timeouts, bornes 1 à 3600 s, routes
GET /api/mcp/timeouts et POST /api/mcp/timeouts/save. L'enregistrement ferme
les sessions ouvertes pour que le nouveau délai de connexion s'applique dès
la suivante. Un lancement npx/uvx garde au moins mcpConnectTimeoutCold.

Un appel prend le réglage global, toujours remplacé par le callTimeoutSec du
serveur, puis allongé par l'argument timeout de l'outil. Le plafond d'un
appel suit la borne des réglages (1 h au lieu de 30 min) : un délai réglé à
3600 s n'est plus rogné en silence. Une valeur rangée hors bornes ou
illisible retombe sur la borne ou le défaut.

Le délai par serveur passe par /api/mcp/save (callTimeoutSec, 0 à 3600,
vide = réglage global), revient dans /api/mcp et n'est plus perdu à
l'enregistrement. L'interface gagne deux lignes dans la carte « Serveurs
MCP », au style des réglages (sans bordure, un seul enregistrement par
modification), et un champ « délai d'un appel d'outil » dans la fenêtre
d'édition d'un serveur. index.html régénéré par tools/assemble-ui.

Quand la façade tient les serveurs MCP (LOKI_MCP=rust), elle sert aussi
les nouvelles routes : c'est son pool qu'il faut fermer quand les délais
changent. Le journal de parité MCP couvre la lecture des délais, leur effet
sur la connexion et l'appel, et les deux routes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 5.5 committed 2026-10-19 11:23:14 +02:00
1 parent 6a45f19831
commit 2e28ca955e
13 files changed
+1007 -60

No files matched your search

+66 -1
View File
@@ -2773,6 +2773,17 @@ html[data-files="1"] #files-btn{color:var(--accent)}
<div id="mcp-block" class="gated-group">
<div class="subhead">Serveurs MCP<span id="mcp-badge" class="sbadge"></span><span class="help" tabindex="0" onclick="event.preventDefault();event.stopPropagation();this.classList.toggle('open')">?<span class="tip">Connecte des <b>serveurs MCP</b> (Model Context Protocol) pour donner de nouveaux outils à l'IA : accès fichiers, bases de données, APIs métier… <b>stdio</b> = programme lancé localement (npx/uvx/binaire) · <b>http</b> = serveur distant. ⚠ un serveur stdio exécute du code sur cette machine, au même titre que le shell.</span></span></div>
<div class="slist">
<!-- Délais communs à tous les serveurs (AJEAN c05a0a9f, e44b6139),
enregistrés à chaque modification. Un serveur peut remplacer le
délai d'appel dans sa fenêtre d'édition. -->
<div class="pe-row">
<span class="pe-row-l">Délai de connexion<span class="pe-sub">pour joindre un serveur ; au moins 3 min au premier lancement npx/uvx</span></span>
<span class="pe-row-c"><input id="mcp-timeout-connect" class="pe-val" type="number" min="1" max="3600" step="1" value="20" style="width:54px" onchange="saveMcpTimeouts()"><span class="pe-unit">s</span></span>
</div>
<div class="pe-row">
<span class="pe-row-l">Délai d'un appel d'outil<span class="pe-sub">réglable aussi serveur par serveur</span></span>
<span class="pe-row-c"><input id="mcp-timeout-call" class="pe-val" type="number" min="1" max="3600" step="1" value="120" style="width:54px" onchange="saveMcpTimeouts()"><span class="pe-unit">s</span></span>
</div>
<div class="rowbody">
<!-- Même texte que celui rendu après chargement quand il n'y a aucun
serveur : sinon la carte partait vide puis grandissait d'une ligne. -->
@@ -3292,6 +3303,10 @@ html[data-files="1"] #files-btn{color:var(--accent)}
<textarea id="mcp-headers" class="ctl ta" spellcheck="false" style="min-height:44px" placeholder="Authorization: Bearer sk-…"></textarea>
</div>
</div>
<div class="fld">
<label class="fld-l" for="mcp-call-timeout">Délai d'un appel d'outil <span class="opt">(secondes, 1 à 3600 — vide = réglage global)</span></label>
<input id="mcp-call-timeout" class="ctl" type="number" min="1" max="3600" step="1" inputmode="numeric" placeholder="réglage global">
</div>
<div id="mcp-modal-status" class="muted" style="font-size:12px;min-height:16px"></div>
<div id="mcp-tools-section" style="display:none">
<div class="subhead" style="margin-top:6px">Outils <span id="mcp-tools-count" class="muted" style="text-transform:none;letter-spacing:0;font-weight:400;font-family:var(--font)"></span><span class="help" tabindex="0" onclick="event.preventDefault();event.stopPropagation();this.classList.toggle('open')">?<span class="tip">Décoche un outil pour le masquer à l'IA sans déconnecter le serveur. Utile pour ne garder que ce dont tu as besoin.</span></span></div>
@@ -9502,7 +9517,47 @@ async function deleteExternal(){
let mcpServers = [];
let mcpEditing = null; // nom du serveur en cours d'édition ('' ou null = nouveau)
async function loadMCP(){ renderMCP(await jget('/api/mcp')); }
async function loadMCP(){
renderMCP(await jget('/api/mcp'));
loadMcpTimeouts();
}
// ─── Délais globaux ──────────────────────────────────────────────────────────
// Connexion et appel d'outil, communs à tous les serveurs (/api/mcp/timeouts).
// Un seul enregistrement par modification : « change » part à la validation
// ou à la perte du focus, pas à chaque frappe.
let mcpTimeouts = {connect: 20, call: 120};
async function loadMcpTimeouts(){
const r = await jget('/api/mcp/timeouts');
if(!r || !r.ok) return;
mcpTimeouts = {connect: r.connect, call: r.call};
document.getElementById('mcp-timeout-connect').value = r.connect;
document.getElementById('mcp-timeout-call').value = r.call;
mcpCallTimeoutPlaceholder();
}
async function saveMcpTimeouts(){
const connect = Number(document.getElementById('mcp-timeout-connect').value);
const call = Number(document.getElementById('mcp-timeout-call').value);
const ok = v => Number.isInteger(v) && v >= 1 && v <= 3600;
if(!ok(connect) || !ok(call)){
toast('délai invalide : de 1 à 3600 s');
loadMcpTimeouts(); // revient aux valeurs enregistrées
return;
}
const r = await jpost('/api/mcp/timeouts/save', {connect, call});
if(!r || !r.ok){ toast('erreur : '+((r && r.error) || 'délais non enregistrés')); loadMcpTimeouts(); return; }
mcpTimeouts = {connect: r.connect, call: r.call};
mcpCallTimeoutPlaceholder();
toast('délais MCP enregistrés');
}
// Le champ « délai d'un appel » de la fenêtre d'édition montre le réglage
// global qu'il remplacerait.
function mcpCallTimeoutPlaceholder(){
document.getElementById('mcp-call-timeout').placeholder = 'réglage global ('+mcpTimeouts.call+' s)';
}
function renderMCP(r){
mcpServers = (r && r.servers) || [];
@@ -9675,6 +9730,7 @@ function openMcpFromCatalog(e){
Object.entries(e.env||{}).map(([k,v])=>k+'='+v).join('\n');
document.getElementById('mcp-url').value = '';
document.getElementById('mcp-headers').value = '';
document.getElementById('mcp-call-timeout').value = '';
document.getElementById('mcp-modal-title').textContent = e.name+' — depuis le catalogue';
}
const st = document.getElementById('mcp-modal-status');
@@ -9702,6 +9758,8 @@ function openMcp(name){
document.getElementById('mcp-env').value = s && s.env ? Object.entries(s.env).map(([k,v])=>k+'='+v).join('\n') : '';
document.getElementById('mcp-url').value = s ? (s.url||'') : '';
document.getElementById('mcp-headers').value = s && s.headers ? Object.entries(s.headers).map(([k,v])=>k+': '+v).join('\n') : '';
document.getElementById('mcp-call-timeout').value = s && s.callTimeoutSec ? s.callTimeoutSec : '';
mcpCallTimeoutPlaceholder();
const st = document.getElementById('mcp-modal-status');
st.textContent = ''; st.style.color = '';
if(s && s.enabled && s.error){ st.textContent = '⚠ '+s.error; st.style.color = 'var(--err)'; }
@@ -9780,6 +9838,13 @@ async function saveMcp(){
body.headers = mcpParseKV(document.getElementById('mcp-headers').value, ':');
body.command = ''; body.args = undefined; body.env = undefined;
}
// Vide = réglage global (0 côté serveur).
const ct = document.getElementById('mcp-call-timeout').value.trim();
if(ct){
const n = Number(ct);
if(!Number.isInteger(n) || n < 1 || n > 3600){ toast("délai d'un appel d'outil : de 1 à 3600 s, ou vide"); return; }
body.callTimeoutSec = n;
}
const st = document.getElementById('mcp-modal-status');
st.textContent = 'connexion…'; st.style.color = '';
const r = await jpost('/api/mcp/save', body);