⚒️ fix: MCP Initialization Flows (#8734)

* fix: add OAuth flow back in to success state

* feat: disable server clicks during initialization to prevent spam

* fix: correct new tab behavior for OAuth between one-click and normal initialization flows

* fix: stop polling on error during oauth (was infinite popping toasts because we didn't clear interval)

* fix: cleanupServerState should be called after successful cancelOauth, not before

* fix: change from completeFlow to failFlow to avoid stale client IDs on OAuth after cancellation

* fix: add logic to differentiate between cancelled and failed flows when checking status for indicators (so error triangle indicator doesn't show up on cancellaiton)
This commit is contained in:
Dustin Healy 2025-07-29 11:54:07 -07:00 committed by GitHub
parent 6671fcb714
commit 6fd3b569ac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 73 additions and 23 deletions

View file

@ -171,6 +171,7 @@ export function useMCPServerManager() {
message: localize('com_ui_mcp_oauth_timeout', { 0: serverName }),
status: 'error',
});
clearInterval(pollInterval);
cleanupServerState(serverName);
return;
}
@ -180,10 +181,15 @@ export function useMCPServerManager() {
message: localize('com_ui_mcp_init_failed'),
status: 'error',
});
clearInterval(pollInterval);
cleanupServerState(serverName);
return;
}
} catch (error) {
console.error(`[MCP Manager] Error polling server ${serverName}:`, error);
clearInterval(pollInterval);
cleanupServerState(serverName);
return;
}
}, 3500);
@ -201,7 +207,7 @@ export function useMCPServerManager() {
);
const initializeServer = useCallback(
async (serverName: string) => {
async (serverName: string, autoOpenOAuth: boolean = true) => {
updateServerState(serverName, { isInitializing: true });
try {
@ -216,7 +222,9 @@ export function useMCPServerManager() {
isInitializing: true,
});
window.open(response.oauthUrl, '_blank', 'noopener,noreferrer');
if (autoOpenOAuth) {
window.open(response.oauthUrl, '_blank', 'noopener,noreferrer');
}
startServerPolling(serverName);
} else {
@ -265,13 +273,25 @@ export function useMCPServerManager() {
const cancelOAuthFlow = useCallback(
(serverName: string) => {
queryClient.invalidateQueries([QueryKeys.mcpConnectionStatus]);
cleanupServerState(serverName);
cancelOAuthMutation.mutate(serverName);
// Call backend cancellation first, then clean up frontend state on success
cancelOAuthMutation.mutate(serverName, {
onSuccess: () => {
// Only clean up frontend state after backend confirms cancellation
cleanupServerState(serverName);
queryClient.invalidateQueries([QueryKeys.mcpConnectionStatus]);
showToast({
message: localize('com_ui_mcp_oauth_cancelled', { 0: serverName }),
status: 'warning',
showToast({
message: localize('com_ui_mcp_oauth_cancelled', { 0: serverName }),
status: 'warning',
});
},
onError: (error) => {
console.error(`[MCP Manager] Failed to cancel OAuth for ${serverName}:`, error);
showToast({
message: localize('com_ui_mcp_init_failed', { 0: serverName }),
status: 'error',
});
},
});
},
[queryClient, cleanupServerState, showToast, localize, cancelOAuthMutation],
@ -309,6 +329,10 @@ export function useMCPServerManager() {
const disconnectedServers: string[] = [];
serverNames.forEach((serverName) => {
if (isInitializing(serverName)) {
return;
}
const serverStatus = connectionStatus[serverName];
if (serverStatus?.connectionState === 'connected') {
connectedServers.push(serverName);
@ -323,11 +347,15 @@ export function useMCPServerManager() {
initializeServer(serverName);
});
},
[connectionStatus, setMCPValues, initializeServer],
[connectionStatus, setMCPValues, initializeServer, isInitializing],
);
const toggleServerSelection = useCallback(
(serverName: string) => {
if (isInitializing(serverName)) {
return;
}
const currentValues = mcpValues ?? [];
const isCurrentlySelected = currentValues.includes(serverName);
@ -343,7 +371,7 @@ export function useMCPServerManager() {
}
}
},
[mcpValues, setMCPValues, connectionStatus, initializeServer],
[mcpValues, setMCPValues, connectionStatus, initializeServer, isInitializing],
);
const handleConfigSave = useCallback(