diff --git a/clients/web/src/components/groups/ClientSettingsForm/ClientSettingsForm.test.tsx b/clients/web/src/components/groups/ClientSettingsForm/ClientSettingsForm.test.tsx index 9945bc36..9f5c92ce 100644 --- a/clients/web/src/components/groups/ClientSettingsForm/ClientSettingsForm.test.tsx +++ b/clients/web/src/components/groups/ClientSettingsForm/ClientSettingsForm.test.tsx @@ -475,7 +475,7 @@ describe("ClientSettingsForm interactions", () => { />, ); - await user.type(screen.getByLabelText("Client ID"), "x"); + await user.type(screen.getByLabelText("IdP Client ID"), "x"); expect(onSettingsChange).toHaveBeenLastCalledWith(expect.any(Function)); expect( resolveSettingsChange( @@ -485,7 +485,7 @@ describe("ClientSettingsForm interactions", () => { ).toBe("x"); onSettingsChange.mockClear(); - await user.type(screen.getByLabelText("Client Secret"), "s"); + await user.type(screen.getByLabelText("IdP Client Secret"), "s"); expect(onSettingsChange).toHaveBeenLastCalledWith(expect.any(Function)); expect( resolveSettingsChange( diff --git a/clients/web/src/components/groups/ClientSettingsForm/ClientSettingsForm.tsx b/clients/web/src/components/groups/ClientSettingsForm/ClientSettingsForm.tsx index c8d8ca77..7614cae0 100644 --- a/clients/web/src/components/groups/ClientSettingsForm/ClientSettingsForm.tsx +++ b/clients/web/src/components/groups/ClientSettingsForm/ClientSettingsForm.tsx @@ -177,8 +177,8 @@ export function ClientSettingsForm({ } /> patch({ clientId: e.currentTarget.value })} error={showClientIdError} @@ -190,8 +190,8 @@ export function ClientSettingsForm({ } /> diff --git a/clients/web/src/components/groups/ClientSettingsModal/ClientSettingsModal.test.tsx b/clients/web/src/components/groups/ClientSettingsModal/ClientSettingsModal.test.tsx index 5b2a2740..19cc880b 100644 --- a/clients/web/src/components/groups/ClientSettingsModal/ClientSettingsModal.test.tsx +++ b/clients/web/src/components/groups/ClientSettingsModal/ClientSettingsModal.test.tsx @@ -95,8 +95,8 @@ describe("ClientSettingsModal", () => { />, ); expect(screen.getByLabelText("Issuer")).toBeInTheDocument(); - expect(screen.getByLabelText("Client ID")).toBeInTheDocument(); - expect(screen.getByLabelText("Client Secret")).toBeInTheDocument(); + expect(screen.getByLabelText("IdP Client ID")).toBeInTheDocument(); + expect(screen.getByLabelText("IdP Client Secret")).toBeInTheDocument(); }); it("calls onSettingsChange when typing issuer", async () => { @@ -260,7 +260,7 @@ describe("ClientSettingsModal", () => { await clickClose(user); // blocked on blank clientId expect(onClose).not.toHaveBeenCalled(); - await user.type(screen.getByLabelText("Client ID"), "client-1"); + await user.type(screen.getByLabelText("IdP Client ID"), "client-1"); await clickClose(user); // now complete -> closes expect(onClose).toHaveBeenCalledTimes(1); }); diff --git a/clients/web/src/components/groups/ServerSettingsForm/ServerSettingsForm.stories.tsx b/clients/web/src/components/groups/ServerSettingsForm/ServerSettingsForm.stories.tsx index f0db4756..19c54849 100644 --- a/clients/web/src/components/groups/ServerSettingsForm/ServerSettingsForm.stories.tsx +++ b/clients/web/src/components/groups/ServerSettingsForm/ServerSettingsForm.stories.tsx @@ -257,6 +257,19 @@ export const WithOAuth: Story = { }, }; +export const EnterpriseManagedOAuth: Story = { + args: { + expandedSections: ["oauth"], + settings: { + ...defaultSettings, + enterpriseManaged: true, + oauthClientId: "resource-test-client-id", + oauthClientSecret: "resource-test-client-secret", + oauthScopes: "mcp tools:read env:read", + }, + }, +}; + export const AllConfigured: Story = { args: { settings: { diff --git a/clients/web/src/components/groups/ServerSettingsForm/ServerSettingsForm.test.tsx b/clients/web/src/components/groups/ServerSettingsForm/ServerSettingsForm.test.tsx index 8a6f81c1..43b400bf 100644 --- a/clients/web/src/components/groups/ServerSettingsForm/ServerSettingsForm.test.tsx +++ b/clients/web/src/components/groups/ServerSettingsForm/ServerSettingsForm.test.tsx @@ -748,6 +748,57 @@ describe("ServerSettingsForm", () => { }); }); + it("uses unqualified Client ID / Client Secret labels when EMA is off (#1692)", () => { + renderWithMantine( + , + ); + expect(screen.getByLabelText("Client ID")).toBeInTheDocument(); + expect(screen.getByLabelText("Client Secret")).toBeInTheDocument(); + expect(screen.queryByLabelText("Resource AS Client ID")).toBeNull(); + }); + + it("relabels the OAuth fields to Resource AS credentials when EMA is on (#1692)", () => { + renderWithMantine( + , + ); + expect(screen.getByLabelText("Resource AS Client ID")).toBeInTheDocument(); + expect( + screen.getByLabelText("Resource AS Client Secret"), + ).toBeInTheDocument(); + expect(screen.queryByLabelText("Client ID")).toBeNull(); + // The resource-authorization-server description is present (rendered twice — + // once per field). + expect( + screen.getAllByText( + /resource authorization server's registered client credential/i, + ).length, + ).toBeGreaterThan(0); + }); + + it("documents the space-separated Scopes delimiter (#1692)", () => { + renderWithMantine( + , + ); + expect( + screen.getByText(/Space-separated OAuth scopes/i), + ).toBeInTheDocument(); + expect( + screen.getByPlaceholderText("mcp tools:read env:read"), + ).toBeInTheDocument(); + }); + it("hides the OAuth Settings section for stdio servers", () => { renderWithMantine( onOAuthChange({ ...currentOAuth(), @@ -604,7 +619,8 @@ export function ServerSettingsForm({ authentication onOAuthChange({ @@ -624,7 +640,8 @@ export function ServerSettingsForm({ } /> @@ -646,6 +663,8 @@ export function ServerSettingsForm({ /> onOAuthChange({ diff --git a/pr-screenshots/README.md b/pr-screenshots/README.md index 8fa09a67..e1275ec7 100644 --- a/pr-screenshots/README.md +++ b/pr-screenshots/README.md @@ -1,3 +1,39 @@ +# Server Settings OAuth field disambiguation under EMA (#1692) — proof screenshots + +The **Server Settings → OAuth Settings** fields, rendered from the +`ServerSettingsForm` Storybook stories. Under enterprise-managed authorization +(EMA) these fields hold the *resource authorization server* credentials (leg 3 — +its registered client), **not** the app/IdP pair configured in Client Settings. +The unqualified labels sat directly under a toggle naming the enterprise IdP, +making it easy to paste the wrong pair. See #1692. + +![OAuth Settings with EMA off — plain Client ID / Client Secret](ema-oauth-fields-plain.png) + +Enterprise-managed authorization **off** (the common case): the fields keep the +plain **Client ID** / **Client Secret** labels. The **Scopes** field now +documents its delimiter — _"Space-separated OAuth scopes (RFC 6749). Do not use +commas — a comma-separated entry is sent as one invalid token and rejected by +the authorization server."_ — with a `mcp tools:read env:read` placeholder. + +![OAuth Settings with EMA on — Resource AS Client ID / Secret](ema-oauth-fields-resource-as.png) + +Enterprise-managed authorization **on**: the same two fields relabel to +**Resource AS Client ID** / **Resource AS Client Secret**, each gaining a +description that names the resource authorization server (its registered client, +EMA leg 3) and points the app client id/secret back to Client Settings. + +![Client Settings modal — IdP Client ID / IdP Client Secret](ema-client-settings-idp-fields.png) + +The reciprocal side in the **Client Settings** modal (Enterprise-Managed +Authorization section). Its credential fields are relabeled **IdP Client ID** / +**IdP Client Secret** — parallel to Server Settings' `Resource AS …` — and each +description now points the *other* way: these are the enterprise IdP pair (EMA +legs 1–2), **not** the per-server resource authorization server credentials, +which go in Server Settings → OAuth Settings. Together the two modals close the +loop: each names its pair and where the other lives. + +--- + # Connection Info extensions + `io.modelcontextprotocol/ui` (#1740) — proof screenshots End-to-end verification of the Phase 3 Connection Info UI against the legacy diff --git a/pr-screenshots/ema-client-settings-idp-fields.png b/pr-screenshots/ema-client-settings-idp-fields.png new file mode 100644 index 00000000..7a2d013f Binary files /dev/null and b/pr-screenshots/ema-client-settings-idp-fields.png differ diff --git a/pr-screenshots/ema-oauth-fields-plain.png b/pr-screenshots/ema-oauth-fields-plain.png new file mode 100644 index 00000000..44f733e2 Binary files /dev/null and b/pr-screenshots/ema-oauth-fields-plain.png differ diff --git a/pr-screenshots/ema-oauth-fields-resource-as.png b/pr-screenshots/ema-oauth-fields-resource-as.png new file mode 100644 index 00000000..267dafc5 Binary files /dev/null and b/pr-screenshots/ema-oauth-fields-resource-as.png differ