@@ -341,6 +341,116 @@ func TestCreateOAuthConfig_AutoDetectsResource(t *testing.T) {
341341 assert .Equal (t , "https://api.example.com/mcp/v1" , resource , "Resource should be auto-detected from metadata" )
342342}
343343
344+ // T022: Test that manual extra_params.resource overrides auto-detected value
345+ func TestCreateOAuthConfig_ManualOverride (t * testing.T ) {
346+ // Variable to hold server URL for use in handler
347+ var serverURL string
348+
349+ // Create a mock server that returns Protected Resource Metadata with resource field
350+ mockMetadataServer := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
351+ if r .URL .Path == "/.well-known/oauth-protected-resource" {
352+ w .Header ().Set ("Content-Type" , "application/json" )
353+ w .WriteHeader (http .StatusOK )
354+ w .Write ([]byte (`{
355+ "resource": "https://auto-detected-resource.example.com/mcp",
356+ "authorization_servers": ["https://auth.example.com"],
357+ "scopes_supported": ["mcp"]
358+ }` ))
359+ return
360+ }
361+ // Return 401 with resource_metadata link in WWW-Authenticate header
362+ if r .Method == "HEAD" {
363+ w .Header ().Set ("WWW-Authenticate" , fmt .Sprintf (`Bearer error="invalid_request", resource_metadata="%s/.well-known/oauth-protected-resource"` , serverURL ))
364+ w .WriteHeader (http .StatusUnauthorized )
365+ return
366+ }
367+ w .WriteHeader (http .StatusNotFound )
368+ }))
369+ defer mockMetadataServer .Close ()
370+
371+ // Assign server URL after server is created
372+ serverURL = mockMetadataServer .URL
373+
374+ testStorage := setupTestStorage (t )
375+ serverConfig := & config.ServerConfig {
376+ Name : "test-manual-override" ,
377+ URL : mockMetadataServer .URL + "/mcp" ,
378+ OAuth : & config.OAuthConfig {
379+ ExtraParams : map [string ]string {
380+ "resource" : "https://manual-override.example.com/api" , // Manual override
381+ },
382+ },
383+ }
384+
385+ // Call CreateOAuthConfigWithExtraParams
386+ oauthConfig , extraParams := CreateOAuthConfigWithExtraParams (serverConfig , testStorage )
387+
388+ require .NotNil (t , oauthConfig , "OAuth config should be created" )
389+ require .NotNil (t , extraParams , "Extra params should be returned" )
390+
391+ // Verify manual resource overrides auto-detected value
392+ resource , hasResource := extraParams ["resource" ]
393+ assert .True (t , hasResource , "extraParams should contain 'resource' key" )
394+ assert .Equal (t , "https://manual-override.example.com/api" , resource , "Manual resource should override auto-detected value" )
395+ }
396+
397+ // T023: Test that manual extra_params are merged with auto-detected params
398+ func TestCreateOAuthConfig_MergesExtraParams (t * testing.T ) {
399+ // Variable to hold server URL for use in handler
400+ var serverURL string
401+
402+ // Create a mock server that returns Protected Resource Metadata
403+ mockMetadataServer := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
404+ if r .URL .Path == "/.well-known/oauth-protected-resource" {
405+ w .Header ().Set ("Content-Type" , "application/json" )
406+ w .WriteHeader (http .StatusOK )
407+ w .Write ([]byte (`{
408+ "resource": "https://auto-detected.example.com/mcp",
409+ "authorization_servers": ["https://auth.example.com"],
410+ "scopes_supported": ["mcp"]
411+ }` ))
412+ return
413+ }
414+ if r .Method == "HEAD" {
415+ w .Header ().Set ("WWW-Authenticate" , fmt .Sprintf (`Bearer error="invalid_request", resource_metadata="%s/.well-known/oauth-protected-resource"` , serverURL ))
416+ w .WriteHeader (http .StatusUnauthorized )
417+ return
418+ }
419+ w .WriteHeader (http .StatusNotFound )
420+ }))
421+ defer mockMetadataServer .Close ()
422+
423+ serverURL = mockMetadataServer .URL
424+
425+ testStorage := setupTestStorage (t )
426+ serverConfig := & config.ServerConfig {
427+ Name : "test-merge-params" ,
428+ URL : mockMetadataServer .URL + "/mcp" ,
429+ OAuth : & config.OAuthConfig {
430+ ExtraParams : map [string ]string {
431+ "tenant_id" : "12345" , // Additional param (should be preserved)
432+ "audience" : "custom-audience" , // Additional param (should be preserved)
433+ // Note: no "resource" - should be auto-detected
434+ },
435+ },
436+ }
437+
438+ // Call CreateOAuthConfigWithExtraParams
439+ oauthConfig , extraParams := CreateOAuthConfigWithExtraParams (serverConfig , testStorage )
440+
441+ require .NotNil (t , oauthConfig , "OAuth config should be created" )
442+ require .NotNil (t , extraParams , "Extra params should be returned" )
443+
444+ // Verify manual params are preserved
445+ assert .Equal (t , "12345" , extraParams ["tenant_id" ], "Manual tenant_id should be preserved" )
446+ assert .Equal (t , "custom-audience" , extraParams ["audience" ], "Manual audience should be preserved" )
447+
448+ // Verify resource is auto-detected since not manually specified
449+ resource , hasResource := extraParams ["resource" ]
450+ assert .True (t , hasResource , "extraParams should contain auto-detected 'resource' key" )
451+ assert .Equal (t , "https://auto-detected.example.com/mcp" , resource , "Resource should be auto-detected" )
452+ }
453+
344454// T008: Test CreateOAuthConfig falls back to server URL when metadata lacks resource field
345455func TestCreateOAuthConfig_FallsBackToServerURL (t * testing.T ) {
346456 // Variable to hold server URL for use in handler
0 commit comments