@@ -1261,4 +1261,108 @@ public async Task CanAuthenticate_WithLegacyServerUsingDefaultEndpointFallback()
12611261 await using var client = await McpClient . CreateAsync (
12621262 transport , loggerFactory : LoggerFactory , cancellationToken : TestContext . Current . CancellationToken ) ;
12631263 }
1264+
1265+ [ Fact ]
1266+ public async Task AuthorizationFlow_AppendsOfflineAccess_WhenServerAdvertisesIt ( )
1267+ {
1268+ TestOAuthServer . IncludeOfflineAccessInMetadata = true ;
1269+ await using var app = await StartMcpServerAsync ( ) ;
1270+
1271+ string ? requestedScope = null ;
1272+
1273+ await using var transport = new HttpClientTransport ( new ( )
1274+ {
1275+ Endpoint = new ( McpServerUrl ) ,
1276+ OAuth = new ( )
1277+ {
1278+ ClientId = "demo-client" ,
1279+ ClientSecret = "demo-secret" ,
1280+ RedirectUri = new Uri ( "http://localhost:1179/callback" ) ,
1281+ AuthorizationRedirectDelegate = ( uri , redirect , ct ) =>
1282+ {
1283+ var query = QueryHelpers . ParseQuery ( uri . Query ) ;
1284+ requestedScope = query [ "scope" ] . ToString ( ) ;
1285+ return HandleAuthorizationUrlAsync ( uri , redirect , ct ) ;
1286+ } ,
1287+ } ,
1288+ } , HttpClient , LoggerFactory ) ;
1289+
1290+ await using var client = await McpClient . CreateAsync (
1291+ transport , loggerFactory : LoggerFactory , cancellationToken : TestContext . Current . CancellationToken ) ;
1292+
1293+ Assert . NotNull ( requestedScope ) ;
1294+ Assert . Contains ( "offline_access" , requestedScope ! . Split ( ' ' ) ) ;
1295+ }
1296+
1297+ [ Fact ]
1298+ public async Task AuthorizationFlow_DoesNotAppendOfflineAccess_WhenServerDoesNotAdvertiseIt ( )
1299+ {
1300+ // IncludeOfflineAccessInMetadata defaults to false, so the AS will not advertise offline_access.
1301+ await using var app = await StartMcpServerAsync ( ) ;
1302+
1303+ string ? requestedScope = null ;
1304+
1305+ await using var transport = new HttpClientTransport ( new ( )
1306+ {
1307+ Endpoint = new ( McpServerUrl ) ,
1308+ OAuth = new ( )
1309+ {
1310+ ClientId = "demo-client" ,
1311+ ClientSecret = "demo-secret" ,
1312+ RedirectUri = new Uri ( "http://localhost:1179/callback" ) ,
1313+ AuthorizationRedirectDelegate = ( uri , redirect , ct ) =>
1314+ {
1315+ var query = QueryHelpers . ParseQuery ( uri . Query ) ;
1316+ requestedScope = query [ "scope" ] . ToString ( ) ;
1317+ return HandleAuthorizationUrlAsync ( uri , redirect , ct ) ;
1318+ } ,
1319+ } ,
1320+ } , HttpClient , LoggerFactory ) ;
1321+
1322+ await using var client = await McpClient . CreateAsync (
1323+ transport , loggerFactory : LoggerFactory , cancellationToken : TestContext . Current . CancellationToken ) ;
1324+
1325+ Assert . NotNull ( requestedScope ) ;
1326+ Assert . DoesNotContain ( "offline_access" , requestedScope ! . Split ( ' ' ) ) ;
1327+ }
1328+
1329+ [ Fact ]
1330+ public async Task AuthorizationFlow_DoesNotDuplicateOfflineAccess_WhenAlreadyPresent ( )
1331+ {
1332+ TestOAuthServer . IncludeOfflineAccessInMetadata = true ;
1333+
1334+ // Configure the PRM to already include offline_access in its scopes.
1335+ Builder . Services . Configure < McpAuthenticationOptions > ( McpAuthenticationDefaults . AuthenticationScheme , options =>
1336+ {
1337+ options . ResourceMetadata ! . ScopesSupported = [ "mcp:tools" , "offline_access" ] ;
1338+ } ) ;
1339+
1340+ await using var app = await StartMcpServerAsync ( ) ;
1341+
1342+ string ? requestedScope = null ;
1343+
1344+ await using var transport = new HttpClientTransport ( new ( )
1345+ {
1346+ Endpoint = new ( McpServerUrl ) ,
1347+ OAuth = new ( )
1348+ {
1349+ ClientId = "demo-client" ,
1350+ ClientSecret = "demo-secret" ,
1351+ RedirectUri = new Uri ( "http://localhost:1179/callback" ) ,
1352+ AuthorizationRedirectDelegate = ( uri , redirect , ct ) =>
1353+ {
1354+ var query = QueryHelpers . ParseQuery ( uri . Query ) ;
1355+ requestedScope = query [ "scope" ] . ToString ( ) ;
1356+ return HandleAuthorizationUrlAsync ( uri , redirect , ct ) ;
1357+ } ,
1358+ } ,
1359+ } , HttpClient , LoggerFactory ) ;
1360+
1361+ await using var client = await McpClient . CreateAsync (
1362+ transport , loggerFactory : LoggerFactory , cancellationToken : TestContext . Current . CancellationToken ) ;
1363+
1364+ Assert . NotNull ( requestedScope ) ;
1365+ var scopeTokens = requestedScope ! . Split ( ' ' ) ;
1366+ Assert . Single ( scopeTokens , t => t == "offline_access" ) ;
1367+ }
12641368}
0 commit comments