Skip to content

feat: surface OAuth validation on MCP Servers - #221

Merged
vishu-bh merged 4 commits into
mainfrom
feat/5722-oauth-servers-ui
Nov 6, 2026
Merged

vishu-bh merged 4 commits into
mainfrom
feat/5722-oauth-servers-ui

Conversation

@vishu-bh

@vishu-bh vishu-bh commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Surface an explicit Test connection action after OAuth authorization on MCP Servers. Share inline stage results between the table row and details panel, enforce read/update permission checks, and clear stale results on configuration or authorization changes. Map structured callback failures to localized guidance.

Part 3 of the frontend split for IBM/mcp-context-forge#5722. Based on #220 in native GitHub stack #223. Catalog integration follows separately.

Validation: production build, ESLint, full frontend coverage suite, and browser tests for row/details authorization, explicit validation, scope failure and reauthorization, read-only callers, and callback errors. Browser screenshots remain outside the repository.

Native GitHub stack: #223, rooted at main.

  1. feat: define OAuth connection validation contract #219 — Handwritten OAuth compatibility contract; base: main.
  2. feat: add shared OAuth connection validation #220 — Shared validation; base: feat: define OAuth connection validation contract #219's branch.
  3. feat: surface OAuth validation on MCP Servers #221 — MCP Servers UI; base: feat: add shared OAuth connection validation #220's branch.
  4. feat: surface OAuth validation on Catalog cards #222 — Catalog UI; base: feat: surface OAuth validation on MCP Servers #221's branch.

#218 contains the independent E2E typing fix and can merge separately. These PRs replace #216.

@vishu-bh
vishu-bh changed the base branch from feat/5722-oauth-validation-core to main November 3, 2026 12:15
@vishu-bh
vishu-bh changed the base branch from main to feat/5722-oauth-validation-core November 3, 2026 12:31
@vishu-bh
vishu-bh added this pull request to stack #223 November 3, 2026 12:31
@vishu-bh
vishu-bh requested review from a-effort, gcgoncalves and marekdano and removed request for a-effort November 3, 2026 12:46
@vishu-bh
vishu-bh marked this pull request as ready for review November 3, 2026 12:46

@marekdano marekdano left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

Suggestions

  1. Failure dialogs now drop the provider's description (src/pages/Servers.tsx, in handleAuthorize)
    The reason is now oauthErrorMessage(intl, err.errorCode), and errorCode falls back to code. Any code outside the allowlist maps to the generic "OAuth connection failed. Check the configuration and try again." That includes invalid_grant, oauth_error without a structured errorCode, and provider-specific codes. Before this change the user saw the sanitized errorDescription. The allowlist is the safe choice, but some failures become harder to diagnose. Consider falling back to the sanitized description (error.message) when the code isn't in the allowlist. This also affects the OAuth dialog flow Catalog will reuse in #222.

  2. The changed tests no longer prove what they claim (src/pages/Servers.test.tsx, e2e/mcp-oauth-outcomes.spec.ts)
    The mocks now pass the localized string as the OAuthAuthorizationError description, for example new OAuthAuthorizationError("access_denied", "The provider denied authorization…"). The description is ignored, so the tests pass only because of the code mapping. Keep the original provider description in the mock and assert that it is not displayed.

  3. The validation call uses the cached server.url, not a fresh saved gateway (src/pages/Servers.tsx, in renderOAuthValidation)
    onTest sends { gatewayId: server.id, baseUrl: server.url } from list or details state. If the server was edited in another tab or session, the request carries a stale URL. The hook from #220 supports a resolver function that fetches the saved gateway and cancels while doing so, and using it here would make the "exact saved gateway" guarantee hold. If the backend rejects a mismatch, the user gets request_invalid with no explanation. Is the plain object intentional?

  4. The result can reset when the details panel opens or closes (src/pages/Servers.tsx, validationTargets)
    validationTargets merges servers and panelServer by id, so the details copy overrides the list copy. If oauthConfigurationKey differs between the two payloads (for example, masked or omitted oauthConfig fields in the list response), opening or closing details changes the key and silently clears the result. Please verify that list and details produce identical keys for the same server. A test that opens details after a result and expects it to survive would cover this. This is also another reason to make the key canonical (see #220, suggestion 4).

  5. The read-only tests can pass before OAuth status has loaded (e2e/oauth-connection-validation.spec.ts, src/pages/Servers.test.tsx)
    The button only renders once the status is ready and the token is valid. Asserting its absence right after the server name appears passes trivially if the status request is still pending. Wait for the status response, or add a positive control, such as the same setup with full permissions in the same test. In e2e, use toHaveCount(0) instead of not.toBeVisible(), since the latter also passes for a missing element.

  6. Leftover split scaffolding in the e2e spec
    for (const catalog of [false]) makes all the catalog branches and the catalog route mocks dead code in this PR. They belong in #222. Remove them here to keep the spec readable.

  7. Possible duplicate "Test connection" button in the details panel (src/components/servers/MCPServerDetailsPanel.tsx)
    The stored-token validation sits directly above the existing TestConnectionPanel in the "Try it" tab. If that panel also has a "Test connection" button, there are two similarly named controls with different meanings: stored-token strict validation and a generic handshake. Consider distinguishing the label, for example "Test stored authorization". I couldn't confirm the existing label from my checkout.

@vishu-bh
vishu-bh force-pushed the feat/5722-oauth-servers-ui branch from 94773f2 to baa88dc Compare November 3, 2026 14:34
@gcgoncalves
gcgoncalves force-pushed the feat/5722-oauth-servers-ui branch from baa88dc to f74563f Compare November 4, 2026 09:09
@vishu-bh
vishu-bh force-pushed the feat/5722-oauth-servers-ui branch from f74563f to 05b2812 Compare November 4, 2026 11:19
{ name: server.name },
)}
iconClassName="h-3.5 w-3.5"
<Fragment key={server.id}>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is cool, I didn't know you could use a fragment like this to add attributes to it! 👏

Comment thread src/pages/Servers.tsx Outdated
Comment on lines +336 to +338
err instanceof OAuthAuthorizationError
? oauthErrorMessage(intl, err.errorCode)
: sanitizeError(err),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're dropping the authorization server message here, replacing it with our fixed intl one. Can we add the original as part of the message?

e.g.: something like ${oauthErrorMessage(intl, err.errorCode)} ${intl.formatMessage(CONNECTOR_MESSAGE_ID_HERE)} ${oauthFailureReason(err)}

@vishu-bh
vishu-bh force-pushed the feat/5722-oauth-servers-ui branch from 05b2812 to 5ac91ec Compare November 4, 2026 13:15
Comment on lines 52 to 75

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For e-readers, add an aria-label with something like ${message(running ? "testing" : entry ? "retest" : "test")} ${mcpServer.name}. This will preserve the layout but add some more info for screen-reader users.

Comment thread src/components/servers/ServersTable.tsx Outdated
Comment on lines +222 to +225
{renderOAuthValidation?.(server, "result") && (
<TableRow>
<TableCell colSpan={7} className="bg-muted/30 px-4 py-3">
{renderOAuthValidation(server, "result")}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renderOAuthValidation?.(server, "result") is being called twice, here. Might be worth extracting to a const and then using for the check and for display.

Comment thread src/pages/Servers.tsx Outdated
if (
!canReadServers ||
!canUpdateServer ||
pendingAuthorizationIds.current.has(server.id) ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A better approach here is oauthAttempts[server.id]?.state === "pending", it's state derived and not ref-dependent.

@vishu-bh
vishu-bh force-pushed the feat/5722-oauth-servers-ui branch from 9bfb053 to 691362c Compare November 4, 2026 15:03

@marekdano marekdano left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR looks solid now!

LGTM 🚀

"Test connection"
(actionLabel ?? "Test connection")
)}
</Button>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing intl

Base automatically changed from feat/5722-oauth-validation-core to main November 5, 2026 16:06
@vishu-bh
vishu-bh force-pushed the feat/5722-oauth-servers-ui branch from 0877e53 to 0c46d1a Compare November 5, 2026 16:15
@marekdano
marekdano force-pushed the feat/5722-oauth-servers-ui branch from 177e944 to 060cc8b Compare November 6, 2026 09:51
Signed-off-by: Vishu Bhatnagar <vishu.bhatnagar@ibm.com>
Signed-off-by: Vishu Bhatnagar <vishu.bhatnagar@ibm.com>
Signed-off-by: Vishu Bhatnagar <vishu.bhatnagar@ibm.com>
Signed-off-by: Vishu Bhatnagar <vishu.bhatnagar@ibm.com>
@vishu-bh
vishu-bh force-pushed the feat/5722-oauth-servers-ui branch from 060cc8b to 5ed66d5 Compare November 6, 2026 10:47
@vishu-bh
vishu-bh merged commit acf04b7 into main Nov 6, 2026
3 checks passed
@vishu-bh
vishu-bh deleted the feat/5722-oauth-servers-ui branch November 6, 2026 10:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants