Repository navigation
Conversation
Signed-off-by: Vishu Bhatnagar <vishu.bhatnagar@ibm.com>
ceb9a29 to
54fada7
Compare
| {componentRefreshError && ( | ||
| <InlineNotification | ||
| type="warning" | ||
| message={`${intl.formatMessage({ id: "mcpServer.oauth.discoveryError" })} ${componentRefreshError}`} |
There was a problem hiding this comment.
Servers.tsx:311 already falls back to "Components could not be refreshed." when result.error is missing, so this line repeats the sentence. Store only the error reason; this line can add the sentence.
| if (result && result.success === false) { | ||
| setComponentRefreshErrors((prev) => ({ | ||
| ...prev, | ||
| [id]: result.error ?? intl.formatMessage({ id: "mcpServer.oauth.discoveryError" }), | ||
| })); | ||
| toast.error(intl.formatMessage({ id: "mcpServer.refresh.errorTitle" }), { | ||
| description: result.error ?? intl.formatMessage({ id: "mcpServer.refresh.errorTitle" }), | ||
| }); | ||
| return; |
There was a problem hiding this comment.
A panel refresh now shows both a toast here and the inline warning in the Components tab. To reduce noise: only use the inline warning next to the list (includes the retry action). Only use the toast for the table action.
Line 380 writes sanitizeError(err) to the inline error before the 409 check at line 381. On a conflict, the toast says "A refresh is already running for X," but the inline text shows a generic error and a Retry refresh button while the refresh is still running. Move the write below the 409 branch so a conflict shows only the toast. Retrying cannot resolve a conflict.
| {needsAuthorizationAfterFailure ? ( | ||
| <> | ||
| <span className="text-warning"> | ||
| {intl.formatMessage({ id: "mcpServer.oauth.needed" })} | ||
| </span> | ||
| {onAuthorize && ( | ||
| <Button | ||
| variant="outline" | ||
| size="xs" | ||
| disabled={isAuthorizing} | ||
| onClick={() => void onAuthorize()} | ||
| > | ||
| {intl.formatMessage({ id: "mcpServer.oauth.authorize" })} | ||
| </Button> | ||
| )} |
There was a problem hiding this comment.
Suggestion: route this through ServerStatusIndicator instead. The bare span drops the status icon, the popover, and the ServerStatusDetail copy explaining that authorization is per person. The PR already adds authorizationNeeded and the table uses it, so the panel should too. #130 made this indicator the shared surface for server status and #160 is making its presentation reusable, so new status states belong inside it.
| const showAuthorizationNeeded = authorizationNeeded && needsOAuthAuthorization(availability); | ||
| const statusLabel = intl.formatMessage({ | ||
| id: compact ? presentation.shortLabelId : presentation.labelId, | ||
| id: showAuthorizationNeeded | ||
| ? "mcpServer.oauth.needed" | ||
| : compact | ||
| ? presentation.shortLabelId | ||
| : presentation.labelId, | ||
| }); |
There was a problem hiding this comment.
If the page needs a separate state after a failed attempt, add it to ServerAvailability and PRESENTATION so every view uses. Otherwise, use authorization_required.
-->
Details:
This prop lets a caller replace the shared labels. When a caller sets authorizationNeeded, the component skips labelId and shortLabelId from PRESENTATION in serverStatus.ts and uses a fixed key instead, so the page chooses the wording rather than the server status. ServersTable is the sole caller, so this adds page-specific logic to the shared component, which #160 removes.
The override also catches the wrong cases. needsOAuthAuthorization includes authorization_expired, so an expired token shows "Authorization needed" instead of "Authorization expired." It also skips the compact branch, so the table cell shows the full text instead of "Auth."
| "mcpServer.oauth.discoveryError": "Components could not be refreshed.", | ||
| "mcpServer.oauth.retryRefresh": "Retry refresh", | ||
| "mcpServer.oauth.readError": "Tools, resources, or prompts could not be loaded.", | ||
| "mcpServer.oauth.needed": "Authorization needed" |
There was a problem hiding this comment.
Drop this and reuse mcpServer.status.authRequired ("Authorization required").
| function oauthFailureReason(error: unknown): string { | ||
| return error instanceof OAuthAuthorizationError ? error.message : sanitizeError(error); | ||
| } |
There was a problem hiding this comment.
Suggestion: add a length limit. This returns the provider's errorDescription unchanged, which bypasses sanitizeError. The popup handler in servers.ts checks event.source but not event.origin, so any page loaded last in the popup can set this string. React renders it as text, so it cannot inject code, but an attacker can still place text of any length in the dialog.
| const supportsAuthorization = | ||
| oauthStatus?.state === "ready" || server?.oauthConfig?.grant_type === "authorization_code"; |
There was a problem hiding this comment.
supportsAuthorization only confirms that the server supports authorization_code OAuth, so it passes when oauthStatus.state is "ready" with a valid token, and the oauthConfig fallback passes while the status loads. An active server then shows an authorize button at line 437. Use needsOAuthAuthorization, as ServerStatusIndicator does, so the button appears only when the server needs authorization.
Servers.tsx uses line 437 as the focus target for onCloseAutoFocus, so closing the failure dialog needs a different target when the token is valid.
Summary
OAuth authorization on the MCP Servers page now opens the server's Components view, shows success inline, and reloads its tools, resources, and prompts. Failures display the provider's reason in a dialog with retry; dismissal leaves an Authorization needed status and an adjacent authorization action when no valid token exists.
Fixes IBM/mcp-context-forge#4684.
Validation
npm run buildandnpm run lintpassed.git diff --checkpassed for the changes.e2e/tools.spec.tsandsrc/utils/errors.ts.Manual verification
On an existing OAuth authorization-code server, deny consent, dismiss the failure dialog, retry from Authorization needed, and complete authorization. Confirm inline success and freshly loaded components, then use Refresh components without reopening OAuth. Also verify Escape dismisses only the dialog and closing a pending popup produces no failure dialog.