fix(settings): MCP server add — POST as multipart/form-data, not JSON (#107)

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 <ccryptoji@gmail.com>
This commit is contained in:
cryptoji
2026-06-01 12:23:05 +08:00
committed by GitHub
parent 791fd4711b
commit 17fe738659

View File

@@ -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'; }
});
}