feat(auth): per-server authorization and token URL overrides
Adds two optional per-server fields — `oauth.authorizationUrl` and `oauth.tokenUrl` — that override the endpoints authorization-server metadata discovery resolved, so a server can be pointed at a development or staging authorization server without changing what it advertises. SDK v2 routes both endpoints through the discovered metadata document and neither through OAuthClientProvider, so the override is applied by wrapping the client's base fetch and patching the metadata document in flight (core/auth/endpointOverrides.ts). Wrapping the base fetch — rather than only the auth fetch — also covers the discovery the SDK runs from inside the transport on the 401/refresh path. The fields round-trip through mcp.json, the /api/servers routes, the runner (CLI/TUI), and the Server Settings → Authorization panel, where a value that is not an absolute http(s) URL is flagged inline; at runtime such a value is dropped with a single warning rather than failing the connection. Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
6bc355df63
commit
364d8c9dbc
1 changed files with 1212 additions and 6 deletions
|
|
@ -1131,11 +1131,22 @@ export function createRemoteApp(
|
|||
clientSecret?: string;
|
||||
scopes?: string;
|
||||
authorizationParams?: Record<string, string>;
|
||||
authorizationUrl?: string;
|
||||
tokenUrl?: string;
|
||||
enterpriseManaged?: boolean;
|
||||
} => {
|
||||
if (v === null || typeof v !== "object" || Array.isArray(v)) return false;
|
||||
const o = v as Record<string, unknown>;
|
||||
for (const k of ["clientId", "clientSecret", "scopes"] as const) {
|
||||
for (const k of [
|
||||
"clientId",
|
||||
"clientSecret",
|
||||
"scopes",
|
||||
// #1906 — shape only; the values are validated as absolute http(s) URLs
|
||||
// where they're applied (core/auth/endpointOverrides.ts), so a typo drops
|
||||
// one field with a warning instead of dropping the whole `oauth` block.
|
||||
"authorizationUrl",
|
||||
"tokenUrl",
|
||||
] as const) {
|
||||
if (o[k] !== undefined && typeof o[k] !== "string") return false;
|
||||
}
|
||||
if (
|
||||
|
|
@ -1255,7 +1266,7 @@ export function createRemoteApp(
|
|||
if ("oauth" in valObj && !isOauthObject(valObj.oauth)) {
|
||||
logWarn(
|
||||
{ route: "/api/servers", id, droppedKey: "oauth" },
|
||||
"Dropping malformed `oauth` field — expected `{ clientId?, clientSecret?, scopes?, authorizationParams?, enterpriseManaged?, onInsufficientScope? }`.",
|
||||
"Dropping malformed `oauth` field — expected `{ clientId?, clientSecret?, scopes?, authorizationParams?, authorizationUrl?, tokenUrl?, enterpriseManaged?, onInsufficientScope? }`.",
|
||||
);
|
||||
delete valObj.oauth;
|
||||
}
|
||||
|
|
@ -1522,6 +1533,10 @@ export function createRemoteApp(
|
|||
"oauthClientId",
|
||||
"oauthClientSecret",
|
||||
"oauthScopes",
|
||||
// #1906 — string-shaped on the wire; URL validity is checked where the
|
||||
// override is applied, not here.
|
||||
"oauthAuthorizationUrl",
|
||||
"oauthTokenUrl",
|
||||
] as const) {
|
||||
if (obj[optional] !== undefined && typeof obj[optional] !== "string") {
|
||||
return { ok: false, error: `settings.${optional} must be a string` };
|
||||
|
|
@ -1660,6 +1675,18 @@ export function createRemoteApp(
|
|||
if (isKvArray(obj.oauthAuthorizationParams)) {
|
||||
value.oauthAuthorizationParams = obj.oauthAuthorizationParams;
|
||||
}
|
||||
// Empty-string coerces to absent like the credential fields above: the form
|
||||
// emits `""` when a user clears the input, and an empty override on disk
|
||||
// would read back as a configured-but-blank endpoint. (#1906)
|
||||
if (
|
||||
typeof obj.oauthAuthorizationUrl === "string" &&
|
||||
obj.oauthAuthorizationUrl !== ""
|
||||
) {
|
||||
value.oauthAuthorizationUrl = obj.oauthAuthorizationUrl;
|
||||
}
|
||||
if (typeof obj.oauthTokenUrl === "string" && obj.oauthTokenUrl !== "") {
|
||||
value.oauthTokenUrl = obj.oauthTokenUrl;
|
||||
}
|
||||
if (obj.enterpriseManaged === true) {
|
||||
value.enterpriseManaged = true;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue