From 85a752f71f333e3e4f90a08a49b993f13022ca68 Mon Sep 17 00:00:00 2001
From: The-AarushiSingh <175547726+The-AarushiSingh@users.noreply.github.com>
Date: Fri, 28 Aug 2026 16:37:12 +0530
Subject: [PATCH] fix(mcp): stop validating URL while typing
- Remove debounced auto-probing useEffect that triggered on every keystroke
- Remove useRef and canAdd
- Add submit-time URL validation
- Add explicit probing when no probe exists
- Change button to 'Check server' before probing
- Users can now type freely without annoying validation
Fixes #1762
---
.../mcp/src/react/AddMcpIntegration.tsx | 54 +++++++++----------
1 file changed, 27 insertions(+), 27 deletions(-)
diff --git a/packages/plugins/mcp/src/react/AddMcpIntegration.tsx b/packages/plugins/mcp/src/react/AddMcpIntegration.tsx
index 16aecb08b1..8cf6e6faef 100644
--- a/packages/plugins/mcp/src/react/AddMcpIntegration.tsx
+++ b/packages/plugins/mcp/src/react/AddMcpIntegration.tsx
@@ -1,4 +1,4 @@
-import { useReducer, useCallback, useEffect, useMemo, useRef, useState } from "react";
+import { useReducer, useCallback, useMemo, useState } from "react";
import { useAtomSet } from "@effect/atom-react";
import * as Exit from "effect/Exit";
import * as Match from "effect/Match";
@@ -262,7 +262,6 @@ export default function AddMcpIntegration(props: {
const remoteSlugExists = useSlugAlreadyExists(remoteSlug);
const stdioSlugExists = useSlugAlreadyExists(stdioSlug);
- const canAdd = Boolean(probe) && !isAdding && !remoteSlugExists;
// Probe failures are shown inline on the URL field; other failures
// (add server) render in the bottom error block.
const probeError = state.step === "error" && state.probe === null ? state.error : null;
@@ -285,24 +284,6 @@ export default function AddMcpIntegration(props: {
dispatch({ type: "probe-ok", probe: exit.value });
}, [state.url, doProbe]);
- // Keep the latest handleProbe in a ref so the debounced effect can call it
- // without depending on its identity (which changes every render).
- const handleProbeRef = useRef(handleProbe);
- handleProbeRef.current = handleProbe;
-
- // Auto-probe whenever the URL changes (debounced) while we're on the
- // remote transport and not already probing/probed.
- useEffect(() => {
- if (transport !== "remote") return;
- if (state.step !== "url") return;
- const trimmed = state.url.trim();
- if (!trimmed) return;
- const handle = setTimeout(() => {
- handleProbeRef.current();
- }, 400);
- return () => clearTimeout(handle);
- }, [transport, state.step, state.url]);
-
// Register the integration with the declared auth methods, returning the
// assigned slug (or null on failure — an error is dispatched in that case).
const registerIntegration = useCallback(
@@ -335,7 +316,23 @@ export default function AddMcpIntegration(props: {
);
const handleAddRemote = useCallback(async () => {
- if (!probe) return;
+ const url = state.url.trim();
+ if (!url) {
+ dispatch({ type: "probe-fail", error: "Enter an MCP server URL." });
+ return;
+ }
+ // URL parsing is a browser API boundary; invalid user input is expected.
+ // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: URL constructor validates user input
+ try {
+ new URL(url);
+ } catch {
+ dispatch({ type: "probe-fail", error: "Enter a valid MCP server URL." });
+ return;
+ }
+ if (!probe) {
+ await handleProbe();
+ return;
+ }
dispatch({ type: "add-start" });
// Every row registers as a declared method (a lone no-auth row registers
// the open-server method). Slugs are assigned server-side by kind.
@@ -347,7 +344,7 @@ export default function AddMcpIntegration(props: {
);
if (slug === null) return;
props.onComplete(slug);
- }, [probe, authMethodList.rows, registerIntegration, props]);
+ }, [probe, authMethodList.rows, handleProbe, registerIntegration, props, state.url]);
// ---- Stdio actions ----
@@ -487,11 +484,14 @@ export default function AddMcpIntegration(props: {
>
Cancel
- {(probe || isProbing) && (
-
- )}
+
>
) : (