Merge branch 'main' into ihrpr/elicitation

This commit is contained in:
ihrpr 2025-06-10 14:43:35 +01:00
commit eed97c9d17
No known key found for this signature in database
3 changed files with 812 additions and 209 deletions

View file

@ -366,6 +366,31 @@ describe("OAuth Authorization", () => {
expect(authorizationUrl.searchParams.has("scope")).toBe(false);
});
it("includes state parameter when provided", async () => {
const { authorizationUrl } = await startAuthorization(
"https://auth.example.com",
{
clientInformation: validClientInfo,
redirectUrl: "http://localhost:3000/callback",
state: "foobar",
}
);
expect(authorizationUrl.searchParams.get("state")).toBe("foobar");
});
it("excludes state parameter when not provided", async () => {
const { authorizationUrl } = await startAuthorization(
"https://auth.example.com",
{
clientInformation: validClientInfo,
redirectUrl: "http://localhost:3000/callback",
}
);
expect(authorizationUrl.searchParams.has("state")).toBe(false);
});
it("uses metadata authorization_endpoint when provided", async () => {
const { authorizationUrl } = await startAuthorization(
"https://auth.example.com",

View file

@ -21,6 +21,11 @@ export interface OAuthClientProvider {
*/
get clientMetadata(): OAuthClientMetadata;
/**
* Returns a OAuth2 state parameter.
*/
state?(): string | Promise<string>;
/**
* Loads information about this OAuth client, as registered already with the
* server, or returns `undefined` if the client is not registered with the
@ -162,10 +167,13 @@ export async function auth(
}
}
const state = provider.state ? await provider.state() : undefined;
// Start new authorization flow
const { authorizationUrl, codeVerifier } = await startAuthorization(authorizationServerUrl, {
metadata,
clientInformation,
state,
redirectUrl: provider.redirectUrl,
scope: scope || provider.clientMetadata.scope,
});
@ -301,11 +309,13 @@ export async function startAuthorization(
clientInformation,
redirectUrl,
scope,
state,
}: {
metadata?: OAuthMetadata;
clientInformation: OAuthClientInformation;
redirectUrl: string | URL;
scope?: string;
state?: string;
},
): Promise<{ authorizationUrl: URL; codeVerifier: string }> {
const responseType = "code";
@ -347,6 +357,10 @@ export async function startAuthorization(
);
authorizationUrl.searchParams.set("redirect_uri", String(redirectUrl));
if (state) {
authorizationUrl.searchParams.set("state", state);
}
if (scope) {
authorizationUrl.searchParams.set("scope", scope);
}

View file

@ -99,11 +99,11 @@ export class SSEClientTransport implements Transport {
}
private async _commonHeaders(): Promise<HeadersInit> {
const headers: HeadersInit = {};
const headers: HeadersInit = { ...this._requestInit?.headers };
if (this._authProvider) {
const tokens = await this._authProvider.tokens();
if (tokens) {
headers["Authorization"] = `Bearer ${tokens.access_token}`;
(headers as Record<string, string>)["Authorization"] = `Bearer ${tokens.access_token}`;
}
}