diff --git a/app/src/components/channels/mcp/InstalledServerDetail.test.tsx b/app/src/components/channels/mcp/InstalledServerDetail.test.tsx index dafabb5c03..c999230f2c 100644 --- a/app/src/components/channels/mcp/InstalledServerDetail.test.tsx +++ b/app/src/components/channels/mcp/InstalledServerDetail.test.tsx @@ -262,6 +262,30 @@ describe('InstalledServerDetail', () => { expect(screen.getByText('Timed out')).toBeInTheDocument(); }); + it('renders a graceful auth notice (not a raw error) for unauthorized status', () => { + render( + {}} + /> + ); + // Friendly "sign in needed" badge + actionable notice, no raw HTTP string. + expect(screen.getByText('Sign in needed')).toBeInTheDocument(); + expect(screen.getByText(/needs you to sign in or add an access token/i)).toBeInTheDocument(); + expect(screen.queryByText(/HTTP 401/i)).not.toBeInTheDocument(); + // The primary action is relabelled "Sign in" (it opens the auth modal). + expect(screen.getByRole('button', { name: 'Sign in' })).toBeInTheDocument(); + }); + // ---------------------------------------------------------------------- // Env reconfiguration (issue #3039) // ---------------------------------------------------------------------- diff --git a/app/src/components/channels/mcp/InstalledServerDetail.tsx b/app/src/components/channels/mcp/InstalledServerDetail.tsx index e0103b6d34..90eaaaf9a3 100644 --- a/app/src/components/channels/mcp/InstalledServerDetail.tsx +++ b/app/src/components/channels/mcp/InstalledServerDetail.tsx @@ -245,8 +245,21 @@ const InstalledServerDetail = ({ - {/* Error */} - {(error || connStatus?.last_error) && ( + {/* Auth required — a graceful, actionable notice rather than the raw HTTP + 401 string. The core reports `unauthorized` (no raw error) for a 401; + the Connect button below opens the auth modal, which probes the server + and offers browser sign-in or a token field as appropriate (#3719). */} + {status === 'unauthorized' && ( +
+ {t('mcp.detail.authRequired')} +
+ )} + + {/* Error — a genuine (non-auth) connect failure. Suppressed entirely while + `unauthorized`: the amber notice above is the only message shown, so a + local action error (e.g. a reconfigure reconnect that re-hits the 401) + can't re-expose raw transport/auth text in this state (#3719). */} + {status !== 'unauthorized' && (error || connStatus?.last_error) && (
{error ?? connStatus?.last_error}
@@ -270,7 +283,11 @@ const InstalledServerDetail = ({ disabled={busy || status === 'connecting'} onClick={handleConnect} className="rounded-lg bg-primary-500 px-3 py-1.5 text-xs font-medium text-white hover:bg-primary-600 disabled:opacity-50 transition-colors"> - {status === 'connecting' ? t('mcp.detail.connecting') : t('mcp.detail.connect')} + {status === 'connecting' + ? t('mcp.detail.connecting') + : status === 'unauthorized' + ? t('mcp.detail.authenticate') + : t('mcp.detail.connect')} ) : (