From 17fe738659ee707d1e895abc0d73a27b77fc843a Mon Sep 17 00:00:00 2001 From: cryptoji <105292792+basedcryptoji@users.noreply.github.com> Date: Mon, 1 Jun 2026 12:23:05 +0800 Subject: [PATCH] =?UTF-8?q?fix(settings):=20MCP=20server=20add=20=E2=80=94?= =?UTF-8?q?=20POST=20as=20multipart/form-data,=20not=20JSON=20(#107)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit routes/mcp_routes.py declares POST /api/mcp/servers with FastAPI Form(...) params. The Save handler in static/js/settings.js was sending application/json, so the Form parser saw no fields and returned 422 with "Field required" for every input — clicking Save did nothing visible. Build a FormData object and let the browser set the multipart Content-Type. args/env are JSON-stringified per the controller contract (defaults "[]" / "{}"); bad JSON still falls back to defaults, same as before. Also check r.ok and surface non-2xx in the form-status span — the previous code never checked status, so a 422 looked like success. Matches the FormData pattern already used in this file (uf-mcp-toggle, ~L4036) for the toggle-enable PATCH against the same controller. Co-authored-by: Toji --- static/js/settings.js | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/static/js/settings.js b/static/js/settings.js index 47ccc18..2922f7b 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -4106,17 +4106,26 @@ async function initUnifiedIntegrations() { el('uf-mcp-cancel').addEventListener('click', () => { formEl.style.display = 'none'; }); el('uf-mcp-save').addEventListener('click', async () => { const transport = el('uf-mcp-transport').value; - const body = { name: el('uf-mcp-name').value, transport }; + // routes/mcp_routes.py uses FastAPI Form(...) — send multipart, not JSON. + const fd = new FormData(); + fd.append('name', el('uf-mcp-name').value); + fd.append('transport', transport); if (transport === 'stdio') { - body.command = el('uf-mcp-cmd').value; - try { body.args = JSON.parse(el('uf-mcp-args').value || '[]'); } catch (_) { body.args = []; } - try { body.env = JSON.parse(el('uf-mcp-env').value || '{}'); } catch (_) { body.env = {}; } + fd.append('command', el('uf-mcp-cmd').value); + let args = '[]'; try { args = JSON.stringify(JSON.parse(el('uf-mcp-args').value || '[]')); } catch (_) {} + let env = '{}'; try { env = JSON.stringify(JSON.parse(el('uf-mcp-env').value || '{}')); } catch (_) {} + fd.append('args', args); + fd.append('env', env); } else { - body.url = el('uf-mcp-url').value; + fd.append('url', el('uf-mcp-url').value); } try { - await fetch('/api/mcp/servers', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); - el('uf-mcp-msg').textContent = 'Saved'; formEl.style.display = 'none'; await renderList(); + const r = await fetch('/api/mcp/servers', { method: 'POST', credentials: 'same-origin', body: fd }); + if (r.ok) { + el('uf-mcp-msg').textContent = 'Saved'; formEl.style.display = 'none'; await renderList(); + } else { + el('uf-mcp-msg').textContent = `Failed (${r.status})`; + } } catch (_) { el('uf-mcp-msg').textContent = 'Failed'; } }); }