fix: lift useTestMCPConnection to parent to eliminate duplicate requests, clear keyTools on manual spec URL edit

This commit is contained in:
Ishaan Jaffer 2026-03-10 13:19:35 -07:00
parent 5f4ad441d0
commit c96f62d9f4
4 changed files with 61 additions and 24 deletions

View file

@ -65,6 +65,12 @@ const OpenAPIFormSection: React.FC<OpenAPIFormSectionProps> = ({
<Input
placeholder="https://petstore3.swagger.io/api/v3/openapi.json"
className="rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500"
onChange={() => {
// Clear the preset selection when the user manually edits the spec URL
// so stale suggested tools from a previous preset don't persist.
setSelectedPreset(null);
onKeyToolsChange?.([]);
}}
/>
</Form.Item>
</>

View file

@ -15,6 +15,7 @@ import { isAdminRole } from "@/utils/roles";
import { validateMCPServerUrl, validateMCPServerName } from "./utils";
import NotificationsManager from "../molecules/notifications_manager";
import { useMcpOAuthFlow } from "@/hooks/useMcpOAuthFlow";
import { useTestMCPConnection } from "@/hooks/useTestMCPConnection";
const asset_logos_folder = "../ui/assets/logos/";
export const mcpLogoImg = `${asset_logos_folder}mcp_logo.png`;
@ -53,7 +54,6 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
transport?: string;
} | null>(null);
const [aliasManuallyEdited, setAliasManuallyEdited] = useState(false);
const [tools, setTools] = useState<any[]>([]);
const [allowedTools, setAllowedTools] = useState<string[]>([]);
const [toolNameToDisplayName, setToolNameToDisplayName] = useState<Record<string, string>>({});
const [toolNameToDescription, setToolNameToDescription] = useState<Record<string, string>>({});
@ -61,6 +61,15 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
const [keyTools, setKeyTools] = useState<OpenAPIKeyTool[]>([]);
const [searchValue, setSearchValue] = useState<string>("");
const [oauthAccessToken, setOauthAccessToken] = useState<string | null>(null);
// Single hook call shared by MCPConnectionStatus and MCPToolConfiguration to avoid duplicate requests.
const { tools, isLoadingTools, toolsError, toolsErrorStackTrace, canFetchTools, fetchTools, clearTools } = useTestMCPConnection({
accessToken,
oauthAccessToken,
formValues,
enabled: true,
});
const authType = formValues.auth_type as string | undefined;
const shouldShowAuthValueField = authType ? AUTH_TYPES_REQUIRING_AUTH_VALUE.includes(authType) : false;
const isOAuthAuthType = authType === AUTH_TYPE.OAUTH2;
@ -380,7 +389,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
NotificationsManager.success("MCP Server created successfully");
form.resetFields();
setCostConfig({});
setTools([]);
clearTools();
setAllowedTools([]);
setAliasManuallyEdited(false);
setModalVisible(false);
@ -777,10 +786,13 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
{/* Connection Status Section */}
<div className="mt-8 pt-6 border-t border-gray-200">
<MCPConnectionStatus
accessToken={accessToken}
oauthAccessToken={oauthAccessToken}
formValues={formValues}
onToolsLoaded={setTools}
tools={tools}
isLoadingTools={isLoadingTools}
toolsError={toolsError}
toolsErrorStackTrace={toolsErrorStackTrace}
canFetchTools={canFetchTools}
fetchTools={fetchTools}
/>
</div>
@ -798,6 +810,10 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
onToolNameToDisplayNameChange={setToolNameToDisplayName}
onToolNameToDescriptionChange={setToolNameToDescription}
keyTools={keyTools}
externalTools={tools}
externalIsLoading={isLoadingTools}
externalError={toolsError}
externalCanFetch={canFetchTools}
/>
</div>

View file

@ -1,28 +1,27 @@
import React, { useEffect } from "react";
import React from "react";
import { Button, Spin, Alert, Collapse } from "antd";
import { CheckCircleOutlined, ExclamationCircleOutlined, ReloadOutlined, ToolOutlined } from "@ant-design/icons";
import { Card, Title, Text } from "@tremor/react";
import { useTestMCPConnection } from "../../hooks/useTestMCPConnection";
interface MCPConnectionStatusProps {
accessToken: string | null;
oauthAccessToken?: string | null;
formValues: Record<string, any>;
onToolsLoaded?: (tools: any[]) => void;
tools: any[];
isLoadingTools: boolean;
toolsError: string | null;
toolsErrorStackTrace: string | null;
canFetchTools: boolean;
fetchTools: () => Promise<void>;
}
const MCPConnectionStatus: React.FC<MCPConnectionStatusProps> = ({ accessToken, oauthAccessToken, formValues, onToolsLoaded }) => {
const { tools, isLoadingTools, toolsError, toolsErrorStackTrace, canFetchTools, fetchTools } = useTestMCPConnection({
accessToken,
oauthAccessToken,
formValues,
enabled: true, // Auto-fetch when required fields are available
});
// Notify parent component when tools change
useEffect(() => {
onToolsLoaded?.(tools);
}, [tools, onToolsLoaded]);
const MCPConnectionStatus: React.FC<MCPConnectionStatusProps> = ({
formValues,
tools,
isLoadingTools,
toolsError,
toolsErrorStackTrace,
canFetchTools,
fetchTools,
}) => {
// Don't show anything if required fields aren't filled
if (!canFetchTools && !formValues.url && !formValues.spec_path) {

View file

@ -22,6 +22,11 @@ interface MCPToolConfigurationProps {
onToolNameToDescriptionChange: (map: Record<string, string>) => void;
/** Curated key tools from the OpenAPI registry preset (shown before spec loads). */
keyTools?: KeyTool[];
/** External tool state lifted from parent to avoid duplicate fetch requests. */
externalTools?: any[];
externalIsLoading?: boolean;
externalError?: string | null;
externalCanFetch?: boolean;
}
interface ToolEntry {
@ -148,6 +153,10 @@ const MCPToolConfiguration: React.FC<MCPToolConfigurationProps> = ({
onToolNameToDisplayNameChange,
onToolNameToDescriptionChange,
keyTools,
externalTools,
externalIsLoading,
externalError,
externalCanFetch,
}) => {
const previousToolsRef = useRef<ToolEntry[]>([]);
const [toolSearchTerm, setToolSearchTerm] = useState("");
@ -155,12 +164,19 @@ const MCPToolConfiguration: React.FC<MCPToolConfigurationProps> = ({
const previousSuggestedToolNamesRef = useRef<string>("");
const [expandedTools, setExpandedTools] = useState<Set<string>>(new Set());
const { tools, isLoadingTools, toolsError, canFetchTools } = useTestMCPConnection({
// Use external tool state when provided (avoids duplicate fetch with MCPConnectionStatus).
// Fall back to internal hook when used standalone (e.g., edit flow).
const hasExternalState = externalTools !== undefined;
const internalHook = useTestMCPConnection({
accessToken,
oauthAccessToken,
formValues,
enabled: true,
enabled: !hasExternalState,
});
const tools: ToolEntry[] = hasExternalState ? externalTools : internalHook.tools;
const isLoadingTools = hasExternalState ? (externalIsLoading ?? false) : internalHook.isLoadingTools;
const toolsError = hasExternalState ? (externalError ?? null) : internalHook.toolsError;
const canFetchTools = hasExternalState ? (externalCanFetch ?? false) : internalHook.canFetchTools;
// Fuzzy-match curated key tool names against actual loaded tool names
const suggestedTools = useMemo(() => {