Repository navigation
feat: surface OAuth validation on MCP Servers - #221
Conversation
marekdano
left a comment
There was a problem hiding this comment.
Findings
Suggestions
-
Failure dialogs now drop the provider's description (
src/pages/Servers.tsx, inhandleAuthorize)
The reason is nowoauthErrorMessage(intl, err.errorCode), anderrorCodefalls back tocode. Any code outside the allowlist maps to the generic "OAuth connection failed. Check the configuration and try again." That includesinvalid_grant,oauth_errorwithout a structurederrorCode, and provider-specific codes. Before this change the user saw the sanitizederrorDescription. 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. -
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 theOAuthAuthorizationErrordescription, for examplenew 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. -
The validation call uses the cached
server.url, not a fresh saved gateway (src/pages/Servers.tsx, inrenderOAuthValidation)
onTestsends{ 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 getsrequest_invalidwith no explanation. Is the plain object intentional? -
The result can reset when the details panel opens or closes (
src/pages/Servers.tsx,validationTargets)
validationTargetsmergesserversandpanelServerby id, so the details copy overrides the list copy. IfoauthConfigurationKeydiffers between the two payloads (for example, masked or omittedoauthConfigfields 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). -
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 isreadyand 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, usetoHaveCount(0)instead ofnot.toBeVisible(), since the latter also passes for a missing element. -
Leftover split scaffolding in the e2e spec
for (const catalog of [false])makes all thecatalogbranches and the catalog route mocks dead code in this PR. They belong in #222. Remove them here to keep the spec readable. -
Possible duplicate "Test connection" button in the details panel (
src/components/servers/MCPServerDetailsPanel.tsx)
The stored-token validation sits directly above the existingTestConnectionPanelin 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.
94773f2 to
baa88dc
Compare
baa88dc to
f74563f
Compare
f74563f to
05b2812
Compare
| { name: server.name }, | ||
| )} | ||
| iconClassName="h-3.5 w-3.5" | ||
| <Fragment key={server.id}> |
There was a problem hiding this comment.
This is cool, I didn't know you could use a fragment like this to add attributes to it! 👏
| err instanceof OAuthAuthorizationError | ||
| ? oauthErrorMessage(intl, err.errorCode) | ||
| : sanitizeError(err), |
There was a problem hiding this comment.
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)}
05b2812 to
5ac91ec
Compare
There was a problem hiding this comment.
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.
| {renderOAuthValidation?.(server, "result") && ( | ||
| <TableRow> | ||
| <TableCell colSpan={7} className="bg-muted/30 px-4 py-3"> | ||
| {renderOAuthValidation(server, "result")} |
There was a problem hiding this comment.
renderOAuthValidation?.(server, "result") is being called twice, here. Might be worth extracting to a const and then using for the check and for display.
| if ( | ||
| !canReadServers || | ||
| !canUpdateServer || | ||
| pendingAuthorizationIds.current.has(server.id) || |
There was a problem hiding this comment.
A better approach here is oauthAttempts[server.id]?.state === "pending", it's state derived and not ref-dependent.
9bfb053 to
691362c
Compare
marekdano
left a comment
There was a problem hiding this comment.
The PR looks solid now!
LGTM 🚀
| "Test connection" | ||
| (actionLabel ?? "Test connection") | ||
| )} | ||
| </Button> |
0877e53 to
0c46d1a
Compare
177e944 to
060cc8b
Compare
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>
060cc8b to
5ed66d5
Compare
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.main.#218 contains the independent E2E typing fix and can merge separately. These PRs replace #216.