@@ -229,8 +229,6 @@ let firstCredentialToken = '';
229229
230230 async function createAuthorizedMcpGrant ( options : {
231231 requestId : string ;
232- requestedScopes : string [ ] ;
233- grantedScopes : string [ ] ;
234232 now : number ;
235233 } ) : Promise < {
236234 grantId : string ;
@@ -243,7 +241,8 @@ let firstCredentialToken = '';
243241 authorizationRequestId : options . requestId ,
244242 oauthClientId : `chatgpt-${ options . requestId } ` ,
245243 oauthClientLabel : 'ChatGPT' ,
246- scopes : options . requestedScopes ,
244+ scopes : [ 'feed:read' , 'posts:write' , 'replies:write' ] ,
245+ scopeBundleVersion : 1 ,
247246 } ,
248247 { authorization : `Bearer ${ MCP_SERVICE_SECRET } ` } ,
249248 options . now ,
@@ -256,7 +255,6 @@ let firstCredentialToken = '';
256255 {
257256 agentId : sponsoredAgentId ,
258257 ticket : ticketBody . ticket ,
259- scopes : options . grantedScopes ,
260258 } ,
261259 authenticatedHeaders ( sponsorCookies , true ) ,
262260 options . now + 1 ,
@@ -266,7 +264,10 @@ let firstCredentialToken = '';
266264 authorization : { id : string ; scopes : string [ ] } ;
267265 delegation : { code : string } ;
268266 } ;
269- assert . deepEqual ( body . authorization . scopes , options . grantedScopes ) ;
267+ assert . deepEqual (
268+ body . authorization . scopes ,
269+ [ 'feed:read' , 'posts:write' , 'replies:write' ] ,
270+ ) ;
270271 return {
271272 grantId : body . authorization . id ,
272273 delegationCode : body . delegation . code ,
@@ -508,12 +509,13 @@ let firstCredentialToken = '';
508509 assert . deepEqual ( meBody . sponsoredAgents . map ( ( agent ) => agent . id ) , [ sponsoredAgentId ] ) ;
509510 } ) ;
510511
511- test ( 'MCP authorization uses CSRF, least privilege , one-time exchange and revocation' , async ( ) => {
512+ test ( 'MCP authorization uses CSRF, a versioned permission bundle , one-time exchange and revocation' , async ( ) => {
512513 const ticketRequest = {
513514 authorizationRequestId : mcpAuthorizationRequestId ,
514515 oauthClientId : 'chatgpt-dynamic-client-001' ,
515516 oauthClientLabel : 'ChatGPT' ,
516517 scopes : [ 'feed:read' , 'posts:write' , 'replies:write' ] ,
518+ scopeBundleVersion : 1 ,
517519 } ;
518520
519521 const unauthenticatedList = await request ( '/v1/mcp/authorizations' , { } , NOW + 47 ) ;
@@ -535,7 +537,7 @@ let firstCredentialToken = '';
535537 ) ;
536538 assert . equal ( elevated . status , 400 ) ;
537539 const elevatedBody = await elevated . json ( ) as { error : { code : string } } ;
538- assert . equal ( elevatedBody . error . code , 'invalid_mcp_authorization_scope ' ) ;
540+ assert . equal ( elevatedBody . error . code , 'invalid_mcp_authorization_scope_bundle ' ) ;
539541
540542 const ticketResponse = await postJson (
541543 '/v1/mcp/authorization-tickets' ,
@@ -550,6 +552,7 @@ let firstCredentialToken = '';
550552 id : string ;
551553 oauthClient : { id : string ; label : string } ;
552554 scopes : string [ ] ;
555+ scopeBundleVersion : number ;
553556 issuedAt : number ;
554557 expiresAt : number ;
555558 } ;
@@ -561,6 +564,7 @@ let firstCredentialToken = '';
561564 ticketBody . authorizationRequest . scopes ,
562565 [ 'feed:read' , 'posts:write' , 'replies:write' ] ,
563566 ) ;
567+ assert . equal ( ticketBody . authorizationRequest . scopeBundleVersion , 1 ) ;
564568 assert . equal ( ticketBody . authorizationRequest . issuedAt , NOW + 49 ) ;
565569 assert . equal ( ticketBody . authorizationRequest . expiresAt , NOW + 49 + 10 * 60 * 1000 ) ;
566570
@@ -580,7 +584,12 @@ let firstCredentialToken = '';
580584 ) ;
581585 assert . equal ( inspected . status , 200 , await inspected . clone ( ) . text ( ) ) ;
582586 const inspectedBody = await inspected . json ( ) as {
583- authorizationRequest : { id : string ; oauthClient : { label : string } ; scopes : string [ ] } ;
587+ authorizationRequest : {
588+ id : string ;
589+ oauthClient : { label : string } ;
590+ scopes : string [ ] ;
591+ scopeBundleVersion : number ;
592+ } ;
584593 manageableAgents : Array < {
585594 id : string ;
586595 handle : string ;
@@ -594,6 +603,7 @@ let firstCredentialToken = '';
594603 inspectedBody . authorizationRequest . scopes ,
595604 [ 'feed:read' , 'posts:write' , 'replies:write' ] ,
596605 ) ;
606+ assert . equal ( inspectedBody . authorizationRequest . scopeBundleVersion , 1 ) ;
597607 assert . deepEqual (
598608 inspectedBody . manageableAgents . map ( ( agent ) => agent . id ) ,
599609 [ sponsoredAgentId ] ,
@@ -611,36 +621,23 @@ let firstCredentialToken = '';
611621 ) ;
612622 assert . equal ( tamperedInspect . status , 400 ) ;
613623
614- const readOnlyTicket = await postJson (
624+ const partialBundleTicket = await postJson (
615625 '/v1/mcp/authorization-tickets' ,
616626 {
617627 ...ticketRequest ,
618- authorizationRequestId : 'chatgpt-read-only -request' ,
628+ authorizationRequestId : 'chatgpt-partial-bundle -request' ,
619629 scopes : [ 'feed:read' ] ,
620630 } ,
621631 { authorization : `Bearer ${ MCP_SERVICE_SECRET } ` } ,
622632 NOW + 49 ,
623633 ) ;
624- assert . equal ( readOnlyTicket . status , 201 , await readOnlyTicket . clone ( ) . text ( ) ) ;
625- const readOnlyTicketBody = await readOnlyTicket . json ( ) as { ticket : string } ;
626- const widenedGrant = await postJson (
627- '/v1/mcp/authorizations' ,
628- {
629- agentId : sponsoredAgentId ,
630- ticket : readOnlyTicketBody . ticket ,
631- scopes : [ 'feed:read' , 'posts:write' ] ,
632- } ,
633- authenticatedHeaders ( sponsorCookies , true ) ,
634- NOW + 50 ,
635- ) ;
636- assert . equal ( widenedGrant . status , 400 ) ;
637- const widenedGrantBody = await widenedGrant . json ( ) as { error : { code : string } } ;
638- assert . equal ( widenedGrantBody . error . code , 'invalid_mcp_authorization_scope' ) ;
634+ assert . equal ( partialBundleTicket . status , 400 ) ;
635+ const partialBundleBody = await partialBundleTicket . json ( ) as { error : { code : string } } ;
636+ assert . equal ( partialBundleBody . error . code , 'invalid_mcp_authorization_scope_bundle' ) ;
639637
640638 const authorizationBody = {
641639 agentId : sponsoredAgentId ,
642640 ticket : ticketBody . ticket ,
643- scopes : [ 'feed:read' ] ,
644641 } ;
645642 const noCsrf = await postJson (
646643 '/v1/mcp/authorizations' ,
@@ -686,7 +683,7 @@ let firstCredentialToken = '';
686683 mcpDelegationCode = createdBody . delegation . code ;
687684 assert . ok ( mcpGrantId ) ;
688685 assert . ok ( mcpDelegationCode . startsWith ( 'orb_mcp_v1_' ) ) ;
689- assert . deepEqual ( createdBody . authorization . scopes , [ 'feed:read' ] ) ;
686+ assert . deepEqual ( createdBody . authorization . scopes , [ 'feed:read' , 'posts:write' , 'replies:write' ] ) ;
690687 assert . equal ( createdBody . authorization . agent . id , sponsoredAgentId ) ;
691688 assert . equal ( createdBody . authorization . oauthClient . label , 'ChatGPT' ) ;
692689 assert . equal ( createdBody . authorization . status , 'active' ) ;
@@ -730,7 +727,7 @@ let firstCredentialToken = '';
730727 authorization : { id : string ; scopes : string [ ] ; status : string } ;
731728 } ;
732729 assert . equal ( redeemedBody . authorization . id , mcpGrantId ) ;
733- assert . deepEqual ( redeemedBody . authorization . scopes , [ 'feed:read' ] ) ;
730+ assert . deepEqual ( redeemedBody . authorization . scopes , [ 'feed:read' , 'posts:write' , 'replies:write' ] ) ;
734731 assert . equal ( redeemedBody . authorization . status , 'active' ) ;
735732
736733 const replay = await postJson ( '/v1/mcp/delegations/redeem' , {
@@ -786,7 +783,10 @@ let firstCredentialToken = '';
786783 } ;
787784 } ;
788785 assert . equal ( privateStateBody . authorization . id , mcpGrantId ) ;
789- assert . deepEqual ( privateStateBody . authorization . scopes , [ 'feed:read' ] ) ;
786+ assert . deepEqual (
787+ privateStateBody . authorization . scopes ,
788+ [ 'feed:read' , 'posts:write' , 'replies:write' ] ,
789+ ) ;
790790 assert . equal ( privateStateBody . authorization . lastUsedAt , NOW + 56 ) ;
791791 assert . equal ( privateStateBody . agent . id , sponsoredAgentId ) ;
792792 assert . equal ( privateStateBody . agent . handle , 'selene-test-agent' ) ;
@@ -865,24 +865,9 @@ let firstCredentialToken = '';
865865 assert . equal ( expiredTicket . status , 400 ) ;
866866 } ) ;
867867
868- test ( 'MCP delegated writes enforce independent scopes, idempotency and revocation' , async ( ) => {
869- const requestedScopes = [ 'feed:read' , 'posts:write' , 'replies:write' ] ;
870- const postOnly = await createAuthorizedMcpGrant ( {
871- requestId : 'chatgpt-post-only-request' ,
872- requestedScopes,
873- grantedScopes : [ 'feed:read' , 'posts:write' ] ,
874- now : NOW + 1_000 ,
875- } ) ;
876- const replyOnly = await createAuthorizedMcpGrant ( {
877- requestId : 'chatgpt-reply-only-request' ,
878- requestedScopes,
879- grantedScopes : [ 'feed:read' , 'replies:write' ] ,
880- now : NOW + 1_010 ,
881- } ) ;
868+ test ( 'MCP delegated writes enforce the full bundle, idempotency and revocation' , async ( ) => {
882869 const full = await createAuthorizedMcpGrant ( {
883870 requestId : 'chatgpt-write-request' ,
884- requestedScopes,
885- grantedScopes : requestedScopes ,
886871 now : NOW + 1_020 ,
887872 } ) ;
888873
@@ -916,26 +901,6 @@ let firstCredentialToken = '';
916901 const missingKeyBody = await missingKey . json ( ) as { error : { code : string } } ;
917902 assert . equal ( missingKeyBody . error . code , 'idempotency_key_required' ) ;
918903
919- const postDenied = await postJson (
920- `/v1/mcp/grants/${ encodeURIComponent ( replyOnly . grantId ) } /records` ,
921- mcpWritePostBody ,
922- mcpServiceHeaders ( 'mcp-post-scope-denied' ) ,
923- NOW + 1_032 ,
924- ) ;
925- assert . equal ( postDenied . status , 403 ) ;
926- const mcpPostDeniedBody = await postDenied . json ( ) as { error : { code : string } } ;
927- assert . equal ( mcpPostDeniedBody . error . code , 'mcp_authorization_scope_denied' ) ;
928-
929- const replyDenied = await postJson (
930- `/v1/mcp/grants/${ encodeURIComponent ( postOnly . grantId ) } /records/not-needed/replies` ,
931- { bodyMarkdown : 'Bu yanıt kapsam nedeniyle reddedilmeli.' } ,
932- mcpServiceHeaders ( 'mcp-reply-scope-denied' ) ,
933- NOW + 1_033 ,
934- ) ;
935- assert . equal ( replyDenied . status , 403 ) ;
936- const replyDeniedBody = await replyDenied . json ( ) as { error : { code : string } } ;
937- assert . equal ( replyDeniedBody . error . code , 'mcp_authorization_scope_denied' ) ;
938-
939904 const mediaDenied = await postJson (
940905 `/v1/mcp/grants/${ encodeURIComponent ( full . grantId ) } /records` ,
941906 { ...mcpWritePostBody , mediaId : '019f64d2-0109-7644-9a4e-a0d25df888e2' } ,
@@ -1211,7 +1176,8 @@ let firstCredentialToken = '';
12111176 authorizationRequestId : 'foreign-authorization-request' ,
12121177 oauthClientId : 'foreign-client' ,
12131178 oauthClientLabel : 'Foreign client' ,
1214- scopes : [ 'feed:read' ] ,
1179+ scopes : [ 'feed:read' , 'posts:write' , 'replies:write' ] ,
1180+ scopeBundleVersion : 1 ,
12151181 } , { authorization : `Bearer ${ MCP_SERVICE_SECRET } ` } , NOW + 54 ) ;
12161182 assert . equal ( foreignTicketResponse . status , 201 ) ;
12171183 const foreignTicket = ( await foreignTicketResponse . json ( ) as { ticket : string } ) . ticket ;
0 commit comments