@@ -190,9 +190,26 @@ describe("McpOAuthClientProvider", () => {
190190
191191 expect ( metadata . client_name ) . toBe ( "Roo Code" )
192192 expect ( metadata . redirect_uris ) . toEqual ( [ "http://localhost:0/callback" ] )
193- expect ( metadata . grant_types ) . toContain ( "authorization_code" )
193+ expect ( metadata . grant_types ) . toEqual ( [ "authorization_code" , "refresh_token" ] )
194194 expect ( metadata . response_types ) . toContain ( "code" )
195195 expect ( metadata . token_endpoint_auth_method ) . toBe ( "none" )
196+ expect ( metadata ) . toMatchObject ( { application_type : "native" } )
197+ await provider . close ( )
198+ } )
199+
200+ it ( "should default to authorization code when server grant metadata is omitted" , async ( ) => {
201+ mockFetch . mockResolvedValueOnce ( {
202+ ok : true ,
203+ json : ( ) =>
204+ Promise . resolve ( {
205+ issuer : "https://auth.example.com" ,
206+ token_endpoint_auth_methods_supported : [ "none" ] ,
207+ } ) ,
208+ } )
209+
210+ const provider = await McpOAuthClientProvider . create ( "https://example.com/mcp" , createMockSecretStorage ( ) )
211+
212+ expect ( provider . clientMetadata . grant_types ) . toEqual ( [ "authorization_code" ] )
196213 await provider . close ( )
197214 } )
198215
@@ -207,6 +224,61 @@ describe("McpOAuthClientProvider", () => {
207224 expect ( provider . clientMetadata . client_name ) . toBe ( "figma" )
208225 await provider . close ( )
209226 } )
227+
228+ it ( "should exclude jwt-bearer from advertised grant types" , async ( ) => {
229+ mockFetch . mockResolvedValueOnce ( {
230+ ok : true ,
231+ json : ( ) =>
232+ Promise . resolve ( {
233+ issuer : "https://auth.example.com" ,
234+ token_endpoint_auth_methods_supported : [ "none" ] ,
235+ grant_types_supported : [
236+ "authorization_code" ,
237+ "refresh_token" ,
238+ "urn:ietf:params:oauth:grant-type:jwt-bearer" ,
239+ ] ,
240+ } ) ,
241+ } )
242+
243+ const provider = await McpOAuthClientProvider . create ( "https://example.com/mcp" , createMockSecretStorage ( ) )
244+
245+ expect ( provider . clientMetadata . grant_types ) . toEqual ( [ "authorization_code" , "refresh_token" ] )
246+ await provider . close ( )
247+ } )
248+
249+ it ( "should exclude unknown advertised grant types" , async ( ) => {
250+ mockFetch . mockResolvedValueOnce ( {
251+ ok : true ,
252+ json : ( ) =>
253+ Promise . resolve ( {
254+ issuer : "https://auth.example.com" ,
255+ token_endpoint_auth_methods_supported : [ "none" ] ,
256+ grant_types_supported : [ "authorization_code" , "urn:example:grant-type:foo" ] ,
257+ } ) ,
258+ } )
259+
260+ const provider = await McpOAuthClientProvider . create ( "https://example.com/mcp" , createMockSecretStorage ( ) )
261+
262+ expect ( provider . clientMetadata . grant_types ) . toEqual ( [ "authorization_code" ] )
263+ await provider . close ( )
264+ } )
265+
266+ it ( "should reject registration metadata when authorization code is unsupported" , async ( ) => {
267+ mockFetch . mockResolvedValueOnce ( {
268+ ok : true ,
269+ json : ( ) =>
270+ Promise . resolve ( {
271+ issuer : "https://auth.example.com" ,
272+ token_endpoint_auth_methods_supported : [ "none" ] ,
273+ grant_types_supported : [ "refresh_token" ] ,
274+ } ) ,
275+ } )
276+
277+ const provider = await McpOAuthClientProvider . create ( "https://example.com/mcp" , createMockSecretStorage ( ) )
278+
279+ expect ( ( ) => provider . clientMetadata ) . toThrow ( "authorization_code" )
280+ await provider . close ( )
281+ } )
210282 } )
211283
212284 describe ( "clientInformation / saveClientInformation" , ( ) => {
@@ -806,6 +878,47 @@ describe("McpOAuthClientProvider", () => {
806878 await provider . close ( )
807879 } )
808880
881+ it ( "should register when the endpoint rejects unsupported grant types" , async ( ) => {
882+ setupCallbackServerMock ( )
883+ const secretStorage = createMockSecretStorage ( )
884+
885+ mockFetch . mockClear ( )
886+ mockFetch . mockResolvedValueOnce ( {
887+ ok : true ,
888+ json : ( ) =>
889+ Promise . resolve ( {
890+ issuer : "https://auth.example.com" ,
891+ authorization_endpoint : "https://auth.example.com/authorize" ,
892+ token_endpoint : "https://auth.example.com/token" ,
893+ registration_endpoint : "https://auth.example.com/register" ,
894+ token_endpoint_auth_methods_supported : [ "none" ] ,
895+ grant_types_supported : [
896+ "authorization_code" ,
897+ "refresh_token" ,
898+ "urn:ietf:params:oauth:grant-type:jwt-bearer" ,
899+ ] ,
900+ } ) ,
901+ } )
902+ mockFetch . mockImplementationOnce ( ( _url , init ) => {
903+ const body = JSON . parse ( init ?. body as string )
904+ const hasUnsupportedGrant = body . grant_types . some (
905+ ( grantType : string ) => ! [ "authorization_code" , "refresh_token" ] . includes ( grantType ) ,
906+ )
907+
908+ return Promise . resolve ( {
909+ ok : ! hasUnsupportedGrant ,
910+ status : hasUnsupportedGrant ? 400 : 200 ,
911+ json : ( ) => Promise . resolve ( { client_id : "registered-client-id" } ) ,
912+ } )
913+ } )
914+
915+ const provider = await McpOAuthClientProvider . create ( "https://example.com/mcp" , secretStorage )
916+
917+ await expect ( provider . registerClientIfNeeded ( ) ) . resolves . toBeUndefined ( )
918+ expect ( ( await provider . clientInformation ( ) ) ?. client_id ) . toBe ( "registered-client-id" )
919+ await provider . close ( )
920+ } )
921+
809922 it ( "should use the same redirect URI in DCR and authorization flow" , async ( ) => {
810923 setupCallbackServerMock ( )
811924 const secretStorage = createMockSecretStorage ( )
0 commit comments