@@ -917,6 +917,66 @@ describe("OAuth-enabled Worker fetch handler", () => {
917917 expect ( rejected . status ) . toBe ( 401 ) ;
918918 } ) ;
919919
920+ it ( "accepts Claude's published CIMD metadata with an optional JWT grant" , async ( ) => {
921+ const clientId = "https://claude.ai/oauth/mcp-oauth-client-metadata" ;
922+ const metadata = {
923+ client_id : clientId ,
924+ client_name : "Claude" ,
925+ client_uri : "https://claude.ai" ,
926+ redirect_uris : [ redirectUri ] ,
927+ grant_types : [
928+ "authorization_code" ,
929+ "refresh_token" ,
930+ "urn:ietf:params:oauth:grant-type:jwt-bearer" ,
931+ ] ,
932+ response_types : [ "code" ] ,
933+ token_endpoint_auth_method : "none" ,
934+ } ;
935+ const fetchMock = vi . fn ( ( _input : RequestInfo | URL ) =>
936+ Promise . resolve ( Response . json ( metadata ) ) ,
937+ ) ;
938+ vi . stubGlobal ( "fetch" , fetchMock ) ;
939+ const { handler, env } = createHandlerWithEnv ( ) ;
940+ const verifier = base64UrlEncode (
941+ crypto . getRandomValues ( new Uint8Array ( 32 ) ) ,
942+ ) ;
943+ const challenge = base64UrlEncode (
944+ new Uint8Array (
945+ await crypto . subtle . digest (
946+ "SHA-256" ,
947+ new TextEncoder ( ) . encode ( verifier ) ,
948+ ) ,
949+ ) ,
950+ ) ;
951+ const authorizeUrl = new URL ( "https://worker.example/authorize" ) ;
952+ authorizeUrl . searchParams . set ( "response_type" , "code" ) ;
953+ authorizeUrl . searchParams . set ( "client_id" , clientId ) ;
954+ authorizeUrl . searchParams . set ( "redirect_uri" , redirectUri ) ;
955+ authorizeUrl . searchParams . set ( "code_challenge" , challenge ) ;
956+ authorizeUrl . searchParams . set ( "code_challenge_method" , "S256" ) ;
957+ authorizeUrl . searchParams . set ( "state" , "claude-state" ) ;
958+ authorizeUrl . searchParams . set ( "scope" , "mcp" ) ;
959+ authorizeUrl . searchParams . set ( "resource" , "https://worker.example/mcp" ) ;
960+
961+ const result = await handler ( new Request ( authorizeUrl ) , env , { } ) ;
962+
963+ // Regression coverage for issue #942: provider 0.10.0 rejected
964+ // Claude's optional JWT grant; 0.10.2 negotiates it away and renders
965+ // the consent page.
966+ expect ( result . status ) . toBe ( 200 ) ;
967+ expect ( await result . text ( ) ) . toContain ( "Claude" ) ;
968+ expect ( fetchMock ) . toHaveBeenCalled ( ) ;
969+ for ( const [ input ] of fetchMock . mock . calls ) {
970+ const requestedUrl =
971+ input instanceof Request
972+ ? input . url
973+ : input instanceof URL
974+ ? input . href
975+ : input ;
976+ expect ( requestedUrl ) . toBe ( clientId ) ;
977+ }
978+ } ) ;
979+
920980 it ( "completes the CIMD OAuth flow and serves MCP requests" , async ( ) => {
921981 const clientId = "https://chatgpt.com/oauth/hevy-mcp/client.json" ;
922982 const cimdRedirectUri = "https://chatgpt.com/connector/oauth/test-callback" ;
0 commit comments