From e247997a14b42fba18f18c1d866f103b419da1de Mon Sep 17 00:00:00 2001 From: Rohit C Prasad Date: Fri, 21 Aug 2026 18:25:57 -0700 Subject: [PATCH] MCP add flow: poll while a probe is in flight; name prefills from the URL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Testing chip froze forever on guarded servers — the page never refetched after the probe settled server-side. Opener renamed to Add custom MCP server. --- surfaces/gui/e2e/mcp-add-test.spec.ts | 15 ++++++++ .../components/connectors/ConnectorsList.tsx | 2 +- .../src/components/connectors/CustomMcp.tsx | 38 ++++++++++++++++++- 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/surfaces/gui/e2e/mcp-add-test.spec.ts b/surfaces/gui/e2e/mcp-add-test.spec.ts index 9f8a8e3e..c429d03f 100644 --- a/surfaces/gui/e2e/mcp-add-test.spec.ts +++ b/surfaces/gui/e2e/mcp-add-test.spec.ts @@ -80,3 +80,18 @@ test("JSON tab adds stdio as Not tested; detail Test flips it to Live", async ({ await detail.getByTestId("mcp-remove-files").click(); await expect(page.getByTestId("mcp-row-files")).toHaveCount(0); }); + +test("the name field prefills from the URL's distinctive host label", async ({ page }) => { + await openConnectors(page); + await page.getByTestId("add-custom-server").click(); + const modal = page.getByTestId("add-mcp-modal"); + + // Generic labels (mcp/api/data/www) are skipped; the first distinctive one wins. + await modal.getByTestId("mcp-add-url").fill("https://data.dlai.link/api/mcp"); + await expect(modal.getByTestId("mcp-add-name")).toHaveValue("dlai"); + + // Never overwrite what the user typed. + await modal.getByTestId("mcp-add-name").fill("warehouse"); + await modal.getByTestId("mcp-add-url").fill("https://mcp.linear.app/mcp"); + await expect(modal.getByTestId("mcp-add-name")).toHaveValue("warehouse"); +}); diff --git a/surfaces/gui/src/components/connectors/ConnectorsList.tsx b/surfaces/gui/src/components/connectors/ConnectorsList.tsx index 7dac8cda..8657d729 100644 --- a/surfaces/gui/src/components/connectors/ConnectorsList.tsx +++ b/surfaces/gui/src/components/connectors/ConnectorsList.tsx @@ -49,7 +49,7 @@ export function ConnectorsList({ onClick={() => setAddingMcp(true)} data-testid="add-custom-server" > - + Add custom server + + Add custom MCP server void; onChanged: () => void; }) { + const servers = serversProp; + // A probe in flight ("Testing…" / "Signing in…") settles server-side within seconds, + // but this page has no standing MCP poll — the chip froze on Testing forever + // (owner-hit 2026-08-21, add-by-URL against a guarded server). While any row is + // authorizing, poll the parent's refresh until every row settles. + const anyAuthorizing = servers.some((s) => s.status === "authorizing"); + useEffect(() => { + if (!anyAuthorizing) return; + const t = setInterval(onChanged, 1000); + return () => clearInterval(t); + }, [anyAuthorizing, onChanged]); const presets = MCP_PRESETS.filter((p) => !servers.some((s) => s.name === p.name)); if (servers.length === 0 && presets.length === 0) return null; @@ -157,6 +168,24 @@ const EXAMPLE = `{ const INPUT = "w-full text-[13px] px-3 py-2 rounded-lg border border-line bg-paper text-ink outline-none focus:border-accent"; +// A friendly default server name from its URL: walk the hostname's labels left to +// right, skip the generic ones (mcp/api/data/www…), take the first distinctive label +// (mcp.linear.app → "linear", data.dlai.link → "dlai"); fall back to the 2nd-level +// domain. The user can always overtype it. +const GENERIC_LABELS = new Set(["www", "mcp", "api", "data", "remote", "server", "agent", "app"]); +function nameFromUrl(raw: string): string { + try { + const host = new URL(raw).hostname.toLowerCase(); + const labels = host.split(".").filter(Boolean); + if (labels.length < 2) return ""; + const candidates = labels.slice(0, -1); // drop the TLD + const pick = candidates.find((l) => !GENERIC_LABELS.has(l)) || candidates[candidates.length - 1]; + return pick.replace(/[^a-z0-9-]/g, ""); + } catch { + return ""; + } +} + export function AddMcpModal({ onClose, onChanged, @@ -258,7 +287,12 @@ export function AddMcpModal({ /> setUrl(e.target.value)} + onChange={(e) => { + const u = e.target.value; + setUrl(u); + // Prefill the name once the URL looks real — never overwrite typing. + if (!name.trim()) setName(nameFromUrl(u)); + }} placeholder="https://mcp.example.com/mcp" spellCheck={false} className={INPUT + " font-mono text-[12px]"}