All of the mid-session and step-up auth work

This commit is contained in:
Bob Dickinson 2026-07-02 16:35:31 -07:00
commit 562bdd00f1

View file

@ -1,29 +1,48 @@
import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";
import type {
OAuthClientInformation,
OAuthClientMetadata,
OAuthTokens,
} from "@modelcontextprotocol/sdk/shared/auth.js";
import type { RemoteConnectRequest } from "../types.js";
import type { RemoteAuthState } from "../types.js";
const REMOTE_OAUTH_STUB_METADATA: OAuthClientMetadata = {
redirect_uris: [],
scope: "",
};
/**
* Simple OAuth client provider that just returns tokens.
* Used by the remote server to inject Bearer tokens into transport requests.
*
* The SDK may invoke {@link auth} on 401; stubs must satisfy the full provider
* surface so that path fails with a clear error instead of throwing on
* undefined `clientMetadata.scope`.
*/
export function createTokenAuthProvider(
tokens: RemoteConnectRequest["oauthTokens"],
): OAuthClientProvider | undefined {
if (!tokens) return undefined;
export interface RemoteAuthProviderHandle {
provider: OAuthClientProvider;
setAuthState: (authState: RemoteAuthState) => void;
getAuthState: () => RemoteAuthState;
}
function cloneAuthState(state: RemoteAuthState): RemoteAuthState {
return {
...(state.oauthTokens && {
oauthTokens: { ...state.oauthTokens },
}),
...(state.oauthClient && {
oauthClient: { ...state.oauthClient },
}),
};
}
/**
* Mutable OAuth provider for the remote backend MCP transport.
* Browser (or CLI) owns interactive OAuth; this injects Bearer tokens and can
* be hot-updated via {@link RemoteAuthProviderHandle.setAuthState}.
*/
export function createRemoteAuthProvider(
initialAuthState?: RemoteAuthState,
): RemoteAuthProviderHandle | undefined {
if (!initialAuthState?.oauthTokens && !initialAuthState?.oauthClient) {
return undefined;
}
let authState = cloneAuthState(initialAuthState);
const provider = {
get clientMetadata(): OAuthClientMetadata {
return REMOTE_OAUTH_STUB_METADATA;
},
@ -31,13 +50,31 @@ export function createTokenAuthProvider(
return "";
},
async tokens(): Promise<OAuthTokens | undefined> {
return tokens as OAuthTokens;
return authState.oauthTokens as OAuthTokens | undefined;
},
async clientInformation() {
return undefined;
async clientInformation(): Promise<OAuthClientInformation | undefined> {
if (!authState.oauthClient?.client_id) {
return undefined;
}
return {
client_id: authState.oauthClient.client_id,
...(authState.oauthClient.client_secret && {
client_secret: authState.oauthClient.client_secret,
}),
};
},
async saveTokens() {
// No-op
async saveTokens(tokens: OAuthTokens) {
authState = {
...authState,
oauthTokens: {
access_token: tokens.access_token,
token_type: tokens.token_type,
expires_in: tokens.expires_in,
refresh_token: tokens.refresh_token,
scope: tokens.scope,
id_token: tokens.id_token,
},
};
},
codeVerifier() {
return undefined;
@ -46,7 +83,7 @@ export function createTokenAuthProvider(
// No-op
},
clear() {
// No-op
authState = {};
},
async redirectToAuthorization() {
throw new Error(
@ -57,4 +94,14 @@ export function createTokenAuthProvider(
return "";
},
} as unknown as OAuthClientProvider;
return {
provider,
setAuthState(next: RemoteAuthState) {
authState = cloneAuthState(next);
},
getAuthState() {
return cloneAuthState(authState);
},
};
}