@@ -10,9 +10,18 @@ import {CodexApprovalHandler} from "./CodexApprovalHandler";
1010import { CodexAuthMethods , type CodexAuthRequest } from "./CodexAuthMethod" ;
1111import { CodexAcpClient , type SessionMetadata , type SessionMetadataWithThread } from "./CodexAcpClient" ;
1212import { ACPSessionConnection , type UpdateSessionEvent } from "./ACPSessionConnection" ;
13- import type { Account , CollabAgentToolCallStatus , Model , Thread , ThreadItem , UserInput , ReasoningEffortOption } from "./app-server/v2" ;
13+ import type { McpStartupCompleteEvent , InputModality , ReasoningEffort } from "./app-server" ;
14+ import type {
15+ Account ,
16+ CollabAgentToolCallStatus ,
17+ McpServerStatusUpdatedNotification ,
18+ Model ,
19+ Thread ,
20+ ThreadItem ,
21+ UserInput ,
22+ ReasoningEffortOption
23+ } from "./app-server/v2" ;
1424import type { RateLimitsMap } from "./RateLimitsMap" ;
15- import type { InputModality , ReasoningEffort } from "./app-server" ;
1625import { ModelId } from "./ModelId" ;
1726import { AgentMode } from "./AgentMode" ;
1827import type { TokenCount } from "./TokenCount" ;
@@ -43,6 +52,12 @@ export interface SessionState {
4352 sessionMcpServers ?: Array < string > ;
4453}
4554
55+ interface PendingMcpStartupSession {
56+ requestedServers : Set < string > ;
57+ terminalServers : Set < string > ;
58+ publishedFailures : Set < string > ;
59+ }
60+
4661export class CodexAcpServer implements acp . Agent {
4762 private readonly codexAcpClient : CodexAcpClient ;
4863 private readonly connection : acp . AgentSideConnection ;
@@ -51,6 +66,7 @@ export class CodexAcpServer implements acp.Agent {
5166 private readonly availableCommands : CodexCommands ;
5267
5368 private readonly sessions : Map < string , SessionState > ;
69+ private readonly pendingMcpStartupSessions : Map < string , PendingMcpStartupSession > ;
5470
5571 constructor (
5672 connection : acp . AgentSideConnection ,
@@ -59,6 +75,7 @@ export class CodexAcpServer implements acp.Agent {
5975 getExitCode ?: ( ) => number | null ,
6076 ) {
6177 this . sessions = new Map ( ) ;
78+ this . pendingMcpStartupSessions = new Map ( ) ;
6279 this . connection = connection ;
6380 this . codexAcpClient = codexAcpClient ;
6481 this . defaultAuthRequest = defaultAuthRequest ?? null ;
@@ -68,6 +85,9 @@ export class CodexAcpServer implements acp.Agent {
6885 codexAcpClient ,
6986 ( operation ) => this . runWithProcessCheck ( operation )
7087 ) ;
88+ this . codexAcpClient . onMcpServerStatusUpdated ( ( event ) => {
89+ void this . handleMcpServerStatusUpdated ( event ) ;
90+ } ) ;
7191 }
7292
7393 async initialize (
@@ -128,6 +148,7 @@ export class CodexAcpServer implements acp.Agent {
128148 async getOrCreateSession ( request : acp . NewSessionRequest | acp . ResumeSessionRequest ) : Promise < [ SessionId , SessionModelState , SessionModeState ] > {
129149 await this . checkAuthorization ( ) ;
130150 const mcpStartupVersion = this . codexAcpClient . getMcpStartupCompleteVersion ( ) ;
151+ const mcpStatusVersion = this . codexAcpClient . getMcpServerStatusVersion ( ) ;
131152
132153 let sessionMetadata : SessionMetadata ;
133154 if ( "sessionId" in request ) {
@@ -159,6 +180,17 @@ export class CodexAcpServer implements acp.Agent {
159180 }
160181 this . sessions . set ( sessionId , sessionState ) ;
161182
183+ const requestedMcpServers = request . mcpServers ?? [ ] ;
184+ if ( requestedMcpServers . length > 0 ) {
185+ this . pendingMcpStartupSessions . set ( sessionId , {
186+ requestedServers : new Set ( requestedMcpServers . map ( server => server . name ) ) ,
187+ terminalServers : new Set ( ) ,
188+ publishedFailures : new Set ( ) ,
189+ } ) ;
190+ await this . replayBufferedMcpStartupStatus ( sessionId , mcpStatusVersion ) ;
191+ this . publishMcpStartupStatusAsync ( sessionId , mcpStartupVersion ) ;
192+ }
193+
162194 this . publishAvailableCommandsAsync ( sessionId ) ;
163195 const sessionModelState : SessionModelState = this . createModelState ( models , currentModelId ) ;
164196 const sessionModeState : SessionModeState = sessionState . agentMode . toSessionModeState ( ) ;
@@ -621,6 +653,80 @@ export class CodexAcpServer implements acp.Agent {
621653 return await this . runWithProcessCheck ( ( ) => this . codexAcpClient . awaitMcpStartup ( mcpStartupVersion ) ) ;
622654 }
623655
656+ private publishMcpStartupStatusAsync ( sessionId : string , mcpStartupVersion : number ) : void {
657+ void ( async ( ) => {
658+ try {
659+ const mcpStartup = await this . runWithProcessCheck ( ( ) => this . codexAcpClient . awaitMcpStartupResult ( mcpStartupVersion ) ) ;
660+ const sessionState = this . sessions . get ( sessionId ) ;
661+ if ( sessionState ) {
662+ sessionState . sessionMcpServers = mcpStartup . ready ;
663+ }
664+ await this . publishMcpStartupStatus ( sessionId , mcpStartup ) ;
665+ this . pendingMcpStartupSessions . delete ( sessionId ) ;
666+ } catch ( err ) {
667+ logger . error ( `Failed to publish MCP startup status for session ${ sessionId } ` , err ) ;
668+ }
669+ } ) ( ) ;
670+ }
671+
672+ private async publishMcpStartupStatus ( sessionId : string , mcpStartup : McpStartupCompleteEvent ) : Promise < void > {
673+ for ( const update of CodexEventHandler . createMcpStartupUpdates ( mcpStartup ) ) {
674+ await this . connection . sessionUpdate ( {
675+ sessionId,
676+ update,
677+ } ) ;
678+ }
679+ }
680+
681+ private async handleMcpServerStatusUpdated ( event : McpServerStatusUpdatedNotification ) : Promise < void > {
682+ for ( const [ sessionId , pending ] of this . pendingMcpStartupSessions ) {
683+ if ( ! pending . requestedServers . has ( event . name ) ) {
684+ continue ;
685+ }
686+
687+ if ( event . status === "ready" ) {
688+ pending . terminalServers . add ( event . name ) ;
689+ const sessionState = this . sessions . get ( sessionId ) ;
690+ if ( sessionState && ! ( sessionState . sessionMcpServers ?? [ ] ) . includes ( event . name ) ) {
691+ sessionState . sessionMcpServers = [ ...( sessionState . sessionMcpServers ?? [ ] ) , event . name ] ;
692+ }
693+ } else if ( event . status === "failed" || event . status === "cancelled" ) {
694+ pending . terminalServers . add ( event . name ) ;
695+ if ( ! pending . publishedFailures . has ( event . name ) ) {
696+ pending . publishedFailures . add ( event . name ) ;
697+ const updates = CodexEventHandler . createMcpStartupUpdates ( {
698+ ready : [ ] ,
699+ failed : event . status === "failed" ? [ {
700+ server : event . name ,
701+ error : event . error ?? "Unknown MCP startup error" ,
702+ } ] : [ ] ,
703+ cancelled : event . status === "cancelled" ? [ event . name ] : [ ] ,
704+ } ) ;
705+ for ( const update of updates ) {
706+ await this . connection . sessionUpdate ( {
707+ sessionId,
708+ update,
709+ } ) ;
710+ }
711+ }
712+ }
713+
714+ if ( pending . terminalServers . size >= pending . requestedServers . size ) {
715+ this . pendingMcpStartupSessions . delete ( sessionId ) ;
716+ }
717+ }
718+ }
719+
720+ private async replayBufferedMcpStartupStatus ( sessionId : string , afterVersion : number ) : Promise < void > {
721+ const updates = this . codexAcpClient . getMcpServerStatusUpdates ( afterVersion ) ;
722+ for ( const update of updates ) {
723+ await this . handleMcpServerStatusUpdated ( update ) ;
724+ if ( ! this . pendingMcpStartupSessions . has ( sessionId ) ) {
725+ return ;
726+ }
727+ }
728+ }
729+
624730 async prompt ( params : acp . PromptRequest ) : Promise < acp . PromptResponse > {
625731 logger . log ( "Prompt received" , {
626732 sessionId : params . sessionId ,
0 commit comments