From f2d76bbec6a0bae16bc210fc12ec6de19ef6e1ab Mon Sep 17 00:00:00 2001 From: Patrick Buckley Date: Tue, 9 Jun 2026 23:05:48 -0700 Subject: [PATCH] =?UTF-8?q?feat(console):=20the=20MCP=20family=20onto=20th?= =?UTF-8?q?e=20shelf=20=E2=80=94=20create/edit=20merge,=20detail,=20import?= =?UTF-8?q?,=20install?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The four MCP surfaces leave their legacy overlays. The add/edit server editor collapses into ONE lg pane-scoped shelf (#mcp-shelf): a hidden mcp-edit-id decides POST vs PUT, and the title/tag/data-kind/submit-label flip between "Add MCP server"/MCP-NEW/create/Create and "Edit MCP server — name"/MCP-EDIT/ edit/Save. The transport-conditional stdio/http field groups and the OAuth subfield block toggle on the hidden attribute instead of style.display (which .hatch [hidden] enforces); the multitenant-auth segmented radio control carries over verbatim. The transport/auth onchange and submit move out of inline markup into _mcpWire (which also installs the audience-autofill listener once). mcp-import becomes a create shelf with the JSON paste as an sh-mono textarea; mcp-detail an inspect shelf carrying its setSafeHtml two-column population. The registry install flow moves onto the document-modal dialog tier per the confirm precedent: a STATIC #mcp-install-dialog whose summary/source/fields containers _showInstallMcpModal still populates — the dynamic overlay-shell construction is gone. _doRegistryInstall is shared by the one-click card path and the dialog submit, so its busy lock and inline-vs-toast error branch now key off the dialog's .open. Submits go busy via the LED lock; legacy trap/Escape/ID-list entries and the trap-handler lets are deleted. --- turnstone/console/static/admin.js | 205 +++++---- turnstone/console/static/index.html | 628 ++++++++++++++-------------- turnstone/console/static/style.css | 4 - 3 files changed, 435 insertions(+), 402 deletions(-) diff --git a/turnstone/console/static/admin.js b/turnstone/console/static/admin.js index 178d971d..2c9ded15 100644 --- a/turnstone/console/static/admin.js +++ b/turnstone/console/static/admin.js @@ -2633,10 +2633,6 @@ function _installTrap(overlayId, boxId, trapRef) { hideCreateTemplateModal(); else if (overlayId === "edit-template-overlay") hideEditTemplateModal(); else if (overlayId === "memory-detail-overlay") hideMemoryDetailModal(); - else if (overlayId === "mcp-create-overlay") hideCreateMcpModal(); - else if (overlayId === "mcp-import-overlay") hideImportMcpModal(); - else if (overlayId === "mcp-detail-overlay") hideMcpDetailModal(); - else if (overlayId === "mcp-install-overlay") hideInstallMcpModal(); else if (overlayId === "github-import-overlay") hideGitHubImportModal(); else if (overlayId === "create-ppolicy-overlay") hideCreatePromptPolicyModal(); @@ -2678,10 +2674,6 @@ document.addEventListener("keydown", function (e) { ["create-template-overlay", hideCreateTemplateModal], ["edit-template-overlay", hideEditTemplateModal], ["memory-detail-overlay", hideMemoryDetailModal], - ["mcp-install-overlay", hideInstallMcpModal], - ["mcp-detail-overlay", hideMcpDetailModal], - ["mcp-import-overlay", hideImportMcpModal], - ["mcp-create-overlay", hideCreateMcpModal], ["github-import-overlay", hideGitHubImportModal], ["create-ppolicy-overlay", hideCreatePromptPolicyModal], ["edit-ppolicy-overlay", hideEditPromptPolicyModal], @@ -3623,14 +3615,13 @@ function _showModalError(el, msg) { /* ── MCP Servers tab ─────────────────────────────────────────────────────── */ let _mcpServers = []; -let _mcpCreateTrap = null; -let _mcpCreateTrigger = null; -let _mcpImportTrap = null; -let _mcpImportTrigger = null; -let _mcpDetailTrap = null; -let _mcpDetailTrigger = null; -let _mcpInstallTrap = null; -let _mcpInstallTrigger = null; +let _mcpWired = false; +let _mcpShelfHandle = null; +let _mcpImportWired = false; +let _mcpImportShelfHandle = null; +let _mcpDetailShelfHandle = null; +let _mcpInstallWired = false; +let _mcpInstallHandle = null; let _mcpInstallServer = null; let _mcpCurrentView = "servers"; let _registryResults = []; @@ -3997,10 +3988,8 @@ function _renderMcpServers(items) { function toggleMcpTransport() { const v = document.getElementById("mcp-transport").value; - document.getElementById("mcp-stdio-fields").style.display = - v === "stdio" ? "" : "none"; - document.getElementById("mcp-http-fields").style.display = - v === "streamable-http" ? "" : "none"; + document.getElementById("mcp-stdio-fields").hidden = v !== "stdio"; + document.getElementById("mcp-http-fields").hidden = v !== "streamable-http"; // Re-evaluate auth-field visibility because the headers row lives // inside mcp-http-fields and gets toggled there. toggleMcpAuthFields(); @@ -4018,7 +4007,7 @@ function toggleMcpAuthFields() { const authType = _selectedMcpAuthType(); const oauthDiv = document.getElementById("mcp-oauth-fields"); if (oauthDiv) { - oauthDiv.style.display = authType === "oauth_user" ? "" : "none"; + oauthDiv.hidden = authType !== "oauth_user"; } // The "Headers" textarea (inside mcp-http-fields) is only meaningful // for static auth; hide it for 'none' / 'oauth_user' so operators @@ -4027,8 +4016,8 @@ function toggleMcpAuthFields() { if (headersInput) { const headersLabel = document.querySelector('label[for="mcp-headers"]'); const show = authType === "static"; - headersInput.style.display = show ? "" : "none"; - if (headersLabel) headersLabel.style.display = show ? "" : "none"; + headersInput.hidden = !show; + if (headersLabel) headersLabel.hidden = !show; } } @@ -4043,13 +4032,34 @@ function _wireMcpAudienceAutofill() { }); } -function showCreateMcpModal() { - _mcpCreateTrigger = document.activeElement; - const ov = document.getElementById("mcp-create-overlay"); - ov.style.display = "flex"; +function _mcpWire() { + if (_mcpWired) return; + _mcpWired = true; + document + .getElementById("mcp-transport") + .addEventListener("change", toggleMcpTransport); + const authRadios = document.getElementsByName("mcp-auth-type"); + for (let i = 0; i < authRadios.length; i++) { + authRadios[i].addEventListener("change", toggleMcpAuthFields); + } + document + .getElementById("mcp-create-submit") + .addEventListener("click", submitCreateMcp); + _wireMcpAudienceAutofill(); +} + +function _mcpOpen(title, tag, kind, submitLabel) { + const shelf = document.getElementById("mcp-shelf"); + document.getElementById("mcp-shelf-title").textContent = title; + document.getElementById("mcp-shelf-tag").textContent = tag; + shelf.setAttribute("data-kind", kind); + document.getElementById("mcp-create-submit").textContent = submitLabel; + _mcpShelfHandle = window.TurnstoneHatch.openShelf(shelf); + document.getElementById("mcp-name").focus(); +} + +function _mcpResetForm() { document.getElementById("mcp-edit-id").value = ""; - document.getElementById("mcp-create-title").textContent = "Add MCP Server"; - document.getElementById("mcp-create-submit").textContent = "Create"; document.getElementById("mcp-name").value = ""; document.getElementById("mcp-transport").value = "stdio"; document.getElementById("mcp-command").value = ""; @@ -4072,12 +4082,16 @@ function showCreateMcpModal() { document.getElementById("mcp-create-error").classList.remove("is-visible"); toggleMcpTransport(); toggleMcpAuthFields(); - _wireMcpAudienceAutofill(); - document.getElementById("mcp-name").focus(); - _mcpCreateTrap = _installTrap("mcp-create-overlay", "mcp-create-box"); +} + +function showCreateMcpModal() { + _mcpWire(); + _mcpResetForm(); + _mcpOpen("Add MCP server", "MCP-NEW", "create", "Create"); } function showEditMcpModal(serverId) { + _mcpWire(); // Fetch with reveal=true to get actual secret values for editing authFetch("/v1/api/admin/mcp-servers/" + serverId + "?reveal=true") .then(function (r) { @@ -4085,11 +4099,8 @@ function showEditMcpModal(serverId) { return r.json(); }) .then(function (s) { - showCreateMcpModal(); + _mcpResetForm(); document.getElementById("mcp-edit-id").value = serverId; - document.getElementById("mcp-create-title").textContent = - "Edit MCP Server"; - document.getElementById("mcp-create-submit").textContent = "Save"; document.getElementById("mcp-name").value = s.name; document.getElementById("mcp-transport").value = s.transport; document.getElementById("mcp-command").value = s.command || ""; @@ -4142,6 +4153,7 @@ function showEditMcpModal(serverId) { s.oauth_audience || ""; toggleMcpTransport(); toggleMcpAuthFields(); + _mcpOpen("Edit MCP server — " + s.name, "MCP-EDIT", "edit", "Save"); }) .catch(function () { showToast("Failed to load server details"); @@ -4149,10 +4161,8 @@ function showEditMcpModal(serverId) { } function hideCreateMcpModal() { - document.getElementById("mcp-create-overlay").style.display = "none"; - _mcpCreateTrap = _removeTrap(_mcpCreateTrap); - if (_mcpCreateTrigger && _mcpCreateTrigger.focus) _mcpCreateTrigger.focus(); - _mcpCreateTrigger = null; + window.TurnstoneHatch.closeShelf(document.getElementById("mcp-shelf")); + _mcpShelfHandle = null; } function _parseMcpForm() { @@ -4239,11 +4249,12 @@ function _parseMcpForm() { } function submitCreateMcp() { + const shelf = document.getElementById("mcp-shelf"); + const errEl = document.getElementById("mcp-create-error"); const form = _parseMcpForm(); if (form.error) { - const e = document.getElementById("mcp-create-error"); - e.textContent = form.error; - e.classList.add("is-visible"); + errEl.textContent = form.error; + errEl.classList.add("is-visible"); return; } const editId = document.getElementById("mcp-edit-id").value; @@ -4252,7 +4263,8 @@ function submitCreateMcp() { ? "/v1/api/admin/mcp-servers/" + editId : "/v1/api/admin/mcp-servers"; - document.getElementById("mcp-create-submit").disabled = true; + errEl.classList.remove("is-visible"); + window.TurnstoneHatch.setBusy(shelf, true); authFetch(url, { method: method, headers: { "Content-Type": "application/json" }, @@ -4266,18 +4278,16 @@ function submitCreateMcp() { return r.json(); }) .then(function () { + window.TurnstoneHatch.setBusy(shelf, false); hideCreateMcpModal(); showToast(editId ? "Server updated" : "Server created"); _flagMcpSyncPending(); loadAdminMcp(); }) .catch(function (e) { - const el = document.getElementById("mcp-create-error"); - el.textContent = e.message; - el.classList.add("is-visible"); - }) - .finally(function () { - document.getElementById("mcp-create-submit").disabled = false; + window.TurnstoneHatch.setBusy(shelf, false); + errEl.textContent = e.message; + errEl.classList.add("is-visible"); }); } @@ -4337,7 +4347,6 @@ function showMcpDetailModal(serverId) { function _openMcpDetail(s) { if (!s) return; - _mcpDetailTrigger = document.activeElement; let html = ' @@ -2890,6 +3176,42 @@ + + +
+ +

Install MCP server

+ + +
+
+ +
+
+
+
+
+
+ + +
+
+
- - - - - - - -