Skip to content

Commit 4801c2e

Browse files
committed
chore: updates for v2
1 parent 1da1ac6 commit 4801c2e

6 files changed

Lines changed: 60 additions & 27 deletions

File tree

packages/client/src/client/auth.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import type {
77
OAuthClientMetadata,
88
OAuthMetadata,
99
OAuthProtectedResourceMetadata,
10-
OAuthTokens
10+
OAuthTokens,
11+
UserAgentProvider
1112
} from '@modelcontextprotocol/core';
1213
import {
1314
checkResourceAllowed,
15+
createUserAgentProvider,
1416
InvalidClientError,
1517
InvalidClientMetadataError,
1618
InvalidGrantError,
@@ -29,9 +31,6 @@ import {
2931
} from '@modelcontextprotocol/core';
3032
import pkceChallenge from 'pkce-challenge';
3133

32-
export { createUserAgentProvider, UserAgentProvider } from '@modelcontextprotocol/core';
33-
import type { UserAgentProvider } from '@modelcontextprotocol/core';
34-
3534
/**
3635
* Function type for adding client authentication to token requests.
3736
*/
@@ -364,19 +363,23 @@ export async function auth(
364363
scope?: string;
365364
resourceMetadataUrl?: URL;
366365
fetchFn?: FetchLike;
367-
userAgentProvider: UserAgentProvider;
366+
userAgentProvider?: UserAgentProvider;
368367
}
369368
): Promise<AuthResult> {
369+
const optionsWithDefaults = {
370+
...options,
371+
userAgentProvider: options.userAgentProvider ?? createUserAgentProvider()
372+
};
370373
try {
371-
return await authInternal(provider, options);
374+
return await authInternal(provider, optionsWithDefaults);
372375
} catch (error) {
373376
// Handle recoverable error types by invalidating credentials and retrying
374377
if (error instanceof InvalidClientError || error instanceof UnauthorizedClientError) {
375378
await provider.invalidateCredentials?.('all');
376-
return await authInternal(provider, options);
379+
return await authInternal(provider, optionsWithDefaults);
377380
} else if (error instanceof InvalidGrantError) {
378381
await provider.invalidateCredentials?.('tokens');
379-
return await authInternal(provider, options);
382+
return await authInternal(provider, optionsWithDefaults);
380383
}
381384

382385
// Throw otherwise
@@ -1148,7 +1151,7 @@ export async function exchangeAuthorization(
11481151
resource?: URL;
11491152
addClientAuthentication?: OAuthClientProvider['addClientAuthentication'];
11501153
fetchFn?: FetchLike;
1151-
userAgentProvider: UserAgentProvider;
1154+
userAgentProvider?: UserAgentProvider;
11521155
}
11531156
): Promise<OAuthTokens> {
11541157
const tokenRequestParams = prepareAuthorizationCodeRequest(authorizationCode, codeVerifier, redirectUri);
@@ -1160,7 +1163,7 @@ export async function exchangeAuthorization(
11601163
addClientAuthentication,
11611164
resource,
11621165
fetchFn,
1163-
userAgentProvider
1166+
userAgentProvider: userAgentProvider ?? createUserAgentProvider()
11641167
});
11651168
}
11661169

@@ -1193,7 +1196,7 @@ export async function refreshAuthorization(
11931196
resource?: URL;
11941197
addClientAuthentication?: OAuthClientProvider['addClientAuthentication'];
11951198
fetchFn?: FetchLike;
1196-
userAgentProvider: UserAgentProvider;
1199+
userAgentProvider?: UserAgentProvider;
11971200
}
11981201
): Promise<OAuthTokens> {
11991202
const tokenRequestParams = new URLSearchParams({
@@ -1208,7 +1211,7 @@ export async function refreshAuthorization(
12081211
addClientAuthentication,
12091212
resource,
12101213
fetchFn,
1211-
userAgentProvider
1214+
userAgentProvider: userAgentProvider ?? createUserAgentProvider()
12121215
});
12131216

12141217
// Preserve original refresh token if server didn't return a new one

packages/core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"dependencies": {
3636
"ajv": "catalog:runtimeShared",
3737
"ajv-formats": "catalog:runtimeShared",
38+
"bowser": "catalog:runtimeShared",
3839
"json-schema-typed": "catalog:runtimeShared",
3940
"zod": "catalog:runtimeShared",
4041
"zod-to-json-schema": "catalog:runtimeShared"
Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,31 @@
1-
import { createUserAgentProvider } from './userAgent.js';
2-
import packageJson from '../../package.json';
31
import { platform, release } from 'node:os';
42
import { versions } from 'node:process';
53

4+
import packageJson from '../../package.json' with { type: 'json' };
5+
import { createUserAgentProvider } from './userAgent.js';
6+
7+
// Type for mocking window in tests
8+
type MockWindow = { navigator?: { userAgent?: string } };
9+
10+
// Augment globalThis for test mocking
11+
declare global {
12+
// eslint-disable-next-line no-var
13+
var window: MockWindow | undefined;
14+
}
15+
616
describe('createUserAgent', () => {
717
describe('browser', () => {
8-
let windowOriginal: Window & typeof globalThis;
18+
let windowOriginal: MockWindow | undefined;
919

1020
beforeEach(() => {
1121
windowOriginal = globalThis.window;
12-
globalThis.window = {} as Window & typeof globalThis;
13-
globalThis.window.navigator = {
14-
get userAgent() {
15-
return 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36';
22+
globalThis.window = {
23+
navigator: {
24+
get userAgent() {
25+
return 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36';
26+
}
1627
}
17-
} as Navigator;
28+
};
1829
});
1930

2031
afterEach(async () => {
@@ -30,7 +41,7 @@ describe('createUserAgent', () => {
3041
describe('Node', () => {
3142
it('should generate user agent in a Node environment', async () => {
3243
const ua = await createUserAgentProvider()();
33-
expect(ua).toBe(`mcp-sdk-ts/${packageJson.version} os/${platform()}#${release} lang/js md/nodejs#${versions.node}`);
44+
expect(ua).toBe(`mcp-sdk-ts/${packageJson.version} os/${platform()}#${release()} lang/js md/nodejs#${versions.node}`);
3445
});
3546
});
3647
});

packages/core/src/shared/userAgent.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1-
import * as Bowser from 'bowser';
2-
import packageJson from '../../package.json';
1+
import Bowser from 'bowser';
2+
3+
import packageJson from '../../package.json' with { type: 'json' };
34

45
export type UserAgentProvider = () => Promise<string>;
56

67
const UA_LANG = 'lang/js';
78

8-
function isBrowser() {
9+
// Declare window for browser environment detection
10+
declare const window: { navigator?: { userAgent?: string } } | undefined;
11+
12+
function isBrowser(): boolean {
913
return typeof window !== 'undefined';
1014
}
1115

12-
function uaProduct() {
16+
function uaProduct(): string {
1317
return `mcp-sdk-ts/${packageJson.version}`;
1418
}
1519

@@ -31,8 +35,10 @@ function uaNode(version: string | undefined) {
3135
}
3236
}
3337

34-
function browserUserAgent() {
35-
const ua = window.navigator?.userAgent ? Bowser.parse(window.navigator.userAgent) : undefined;
38+
function browserUserAgent(): string {
39+
// window is guaranteed to exist when this function is called (checked by isBrowser())
40+
const userAgent = window?.navigator?.userAgent;
41+
const ua = userAgent ? Bowser.parse(userAgent) : undefined;
3642
return `${uaProduct()} ${uaOS(ua?.os.name, ua?.os.version)} ${UA_LANG}`;
3743
}
3844

pnpm-lock.yaml

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pnpm-workspace.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ catalogs:
88
runtimeShared:
99
ajv: ^8.17.1
1010
ajv-formats: ^3.0.1
11+
bowser: ^2.12.0
1112
json-schema-typed: ^8.0.2
1213
pkce-challenge: ^5.0.0
1314
zod: ^3.25 || ^4.0

0 commit comments

Comments
 (0)