* Server Settings: disambiguate EMA OAuth credential fields (#1692) Under enterprise-managed authorization the OAuth Settings "Client ID" / "Client Secret" fields hold the *resource authorization server* credentials (EMA leg 3 — the Test Client from the xaa.dev resource registration), not the app/IdP pair configured in Client Settings. The unqualified labels sat under a toggle naming the enterprise IdP, pulling readers toward the wrong pair. - When enterpriseManaged is on, relabel to "Resource AS Client ID" / "Resource AS Client Secret" and add a description naming the resource authorization server and pointing the app pair back to Client Settings. - Document the Scopes delimiter: space-separated (RFC 6749), with a warning that commas produce one invalid token, plus a placeholder example. Adds tests for both label states and the scopes hint, plus an EnterpriseManagedOAuth story. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * pr-screenshots: OAuth EMA field disambiguation proof shots (#1692) Add before/after screenshots of the Server Settings → OAuth Settings fields (EMA off = plain Client ID/Secret; EMA on = Resource AS Client ID/Secret) plus the new space-separated Scopes hint, with a README section documenting them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Address review: genericize EMA copy, reuse enterpriseManaged const (#1692) - Drop the hardcoded xaa.dev staging host from the resource-AS field description (it's a general EMA concept, not env-specific); phrase it as "the resource authorization server's registered client credential (EMA leg 3)". Also trims the copy's overlap with the checkbox description. - Reuse the existing `enterpriseManaged` const for the EMA Checkbox's `checked` prop instead of recomputing `settings.enterpriseManaged ?? false`. - Update the test assertion and pr-screenshots README/EMA-on screenshot to match the new copy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Client Settings: mirror EMA disambiguation (IdP-qualified labels) (#1692) The Server Settings OAuth fields now label the leg-3 resource-AS pair explicitly; make Client Settings parallel so each modal names its pair and points at where the other lives. - Relabel the EMA credential fields to "IdP Client ID" / "IdP Client Secret" (parallel to Server Settings' "Resource AS Client ID/Secret"), so the label alone disambiguates without relying on the description. - Add the reciprocal cross-reference to each description: 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. - Update ClientSettingsForm/ClientSettingsModal tests for the new labels; add a pr-screenshots section + Client Settings screenshot. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c51d6aabd2
commit
402d7b204d
10 changed files with 131 additions and 12 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -177,8 +177,8 @@ export function ClientSettingsForm({
|
|||
}
|
||||
/>
|
||||
<TextInput
|
||||
label="Client ID"
|
||||
description="Client id registered with your enterprise IdP."
|
||||
label="IdP Client ID"
|
||||
description="Client id registered with your enterprise IdP (EMA legs 1–2) — not the per-server resource authorization server credentials, which go in Server Settings → OAuth Settings."
|
||||
value={settings.clientId}
|
||||
onChange={(e) => patch({ clientId: e.currentTarget.value })}
|
||||
error={showClientIdError}
|
||||
|
|
@ -190,8 +190,8 @@ export function ClientSettingsForm({
|
|||
}
|
||||
/>
|
||||
<TextInput
|
||||
label="Client Secret"
|
||||
description="Client secret for your enterprise IdP (if required)."
|
||||
label="IdP Client Secret"
|
||||
description="Client secret for your enterprise IdP, if required (EMA legs 1–2) — not the per-server resource authorization server credentials, which go in Server Settings → OAuth Settings."
|
||||
type="password"
|
||||
value={settings.clientSecret}
|
||||
onChange={(e) =>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -748,6 +748,57 @@ describe("ServerSettingsForm", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("uses unqualified Client ID / Client Secret labels when EMA is off (#1692)", () => {
|
||||
renderWithMantine(
|
||||
<ServerSettingsForm
|
||||
{...baseHandlers}
|
||||
settings={emptySettings}
|
||||
expandedSections={["oauth"]}
|
||||
/>,
|
||||
);
|
||||
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(
|
||||
<ServerSettingsForm
|
||||
{...baseHandlers}
|
||||
settings={{ ...emptySettings, enterpriseManaged: true }}
|
||||
expandedSections={["oauth"]}
|
||||
/>,
|
||||
);
|
||||
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(
|
||||
<ServerSettingsForm
|
||||
{...baseHandlers}
|
||||
settings={emptySettings}
|
||||
expandedSections={["oauth"]}
|
||||
/>,
|
||||
);
|
||||
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(
|
||||
<ServerSettingsForm
|
||||
|
|
|
|||
|
|
@ -305,6 +305,21 @@ export function ServerSettingsForm({
|
|||
onRootChange,
|
||||
}: ServerSettingsFormProps) {
|
||||
const oauthCapable = isOAuthCapableServerType(serverType);
|
||||
// Under enterprise-managed auth these fields hold the *resource authorization
|
||||
// server* credentials (leg 3 — its registered client), not the app/IdP pair
|
||||
// configured in Client Settings. The toggle's label mentions the IdP, which
|
||||
// pulls the reader toward the wrong pair — so qualify the labels and add a
|
||||
// description when EMA is on (#1692).
|
||||
const enterpriseManaged = settings.enterpriseManaged ?? false;
|
||||
const clientIdLabel = enterpriseManaged
|
||||
? "Resource AS Client ID"
|
||||
: "Client ID";
|
||||
const clientSecretLabel = enterpriseManaged
|
||||
? "Resource AS Client Secret"
|
||||
: "Client Secret";
|
||||
const resourceAsDescription = enterpriseManaged
|
||||
? "The resource authorization server's registered client credential (EMA leg 3) — not the app client id/secret, which belong in Client Settings."
|
||||
: undefined;
|
||||
// The modern per-request "Log Level per Request" control is meaningless on a
|
||||
// legacy connection (#1629). Hide it when legacy is pinned, or when an `auto`
|
||||
// server negotiated legacy at connect time. A pinned `modern` server, and an
|
||||
|
|
@ -591,7 +606,7 @@ export function ServerSettingsForm({
|
|||
<Checkbox
|
||||
label="Enterprise-managed authorization"
|
||||
description="Connect via the configured enterprise IdP instead of interactive OAuth to the MCP authorization server. OAuth fields below are resource authorization server credentials."
|
||||
checked={settings.enterpriseManaged ?? false}
|
||||
checked={enterpriseManaged}
|
||||
onChange={(e) =>
|
||||
onOAuthChange({
|
||||
...currentOAuth(),
|
||||
|
|
@ -604,7 +619,8 @@ export function ServerSettingsForm({
|
|||
authentication
|
||||
</HintText>
|
||||
<TextInput
|
||||
label="Client ID"
|
||||
label={clientIdLabel}
|
||||
description={resourceAsDescription}
|
||||
value={settings.oauthClientId ?? ""}
|
||||
onChange={(e) =>
|
||||
onOAuthChange({
|
||||
|
|
@ -624,7 +640,8 @@ export function ServerSettingsForm({
|
|||
}
|
||||
/>
|
||||
<TextInput
|
||||
label="Client Secret"
|
||||
label={clientSecretLabel}
|
||||
description={resourceAsDescription}
|
||||
value={settings.oauthClientSecret ?? ""}
|
||||
type="password"
|
||||
onChange={(e) =>
|
||||
|
|
@ -646,6 +663,8 @@ export function ServerSettingsForm({
|
|||
/>
|
||||
<TextInput
|
||||
label="Scopes"
|
||||
description="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."
|
||||
placeholder="mcp tools:read env:read"
|
||||
value={settings.oauthScopes ?? ""}
|
||||
onChange={(e) =>
|
||||
onOAuthChange({
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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
|
||||
|
|
|
|||
BIN
pr-screenshots/ema-client-settings-idp-fields.png
Normal file
BIN
pr-screenshots/ema-client-settings-idp-fields.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
BIN
pr-screenshots/ema-oauth-fields-plain.png
Normal file
BIN
pr-screenshots/ema-oauth-fields-plain.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
BIN
pr-screenshots/ema-oauth-fields-resource-as.png
Normal file
BIN
pr-screenshots/ema-oauth-fields-resource-as.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
Loading…
Reference in a new issue