@@ -63,6 +63,7 @@ public sealed partial class CopilotSession : IAsyncDisposable
6363 private readonly CopilotClient _parentClient ;
6464
6565 private volatile Func < PermissionRequest , PermissionInvocation , Task < PermissionDecision > > ? _permissionHandler ;
66+ private volatile Func < McpAuthContext , Task < McpAuthResult ? > > ? _mcpAuthHandler ;
6667 private volatile Func < UserInputRequest , UserInputInvocation , Task < UserInputResponse > > ? _userInputHandler ;
6768 private volatile Func < ElicitationContext , Task < ElicitationResult > > ? _elicitationHandler ;
6869 private volatile Func < ExitPlanModeRequest , ExitPlanModeInvocation , Task < ExitPlanModeResult > > ? _exitPlanModeHandler ;
@@ -558,6 +559,11 @@ internal void RegisterPermissionHandler(Func<PermissionRequest, PermissionInvoca
558559 _permissionHandler = handler ;
559560 }
560561
562+ internal void RegisterMcpAuthHandler ( Func < McpAuthContext , Task < McpAuthResult ? > > ? handler )
563+ {
564+ _mcpAuthHandler = handler ;
565+ }
566+
561567 /// <summary>
562568 /// Handles a permission request from the Copilot CLI.
563569 /// </summary>
@@ -633,6 +639,39 @@ private async Task HandleBroadcastEventAsync(SessionEvent sessionEvent)
633639 break ;
634640 }
635641
642+ case McpOauthRequiredEvent authEvent :
643+ {
644+ var data = authEvent . Data ;
645+ if ( string . IsNullOrEmpty ( data . RequestId ) )
646+ return ;
647+
648+ var handler = _mcpAuthHandler ;
649+ if ( handler is null )
650+ {
651+ if ( _logger . IsEnabled ( LogLevel . Warning ) )
652+ {
653+ _logger . LogWarning (
654+ "Received MCP OAuth request without a registered MCP auth handler. SessionId={SessionId}, RequestId={RequestId}" ,
655+ SessionId ,
656+ data . RequestId ) ;
657+ }
658+ return ;
659+ }
660+
661+ await ExecuteMcpAuthAndRespondAsync ( data . RequestId , new McpAuthContext
662+ {
663+ SessionId = SessionId ,
664+ RequestId = data . RequestId ,
665+ ServerName = data . ServerName ,
666+ ServerUrl = data . ServerUrl ,
667+ Reason = data . Reason ,
668+ WwwAuthenticateParams = data . WwwAuthenticateParams ,
669+ ResourceMetadata = data . ResourceMetadata ,
670+ StaticClientConfig = data . StaticClientConfig
671+ } , handler ) ;
672+ break ;
673+ }
674+
636675 case CommandExecuteEvent cmdEvent :
637676 {
638677 var data = cmdEvent . Data ;
@@ -702,6 +741,91 @@ await HandleElicitationRequestAsync(
702741 }
703742 }
704743
744+ private async Task ExecuteMcpAuthAndRespondAsync (
745+ string requestId ,
746+ McpAuthContext context ,
747+ Func < McpAuthContext , Task < McpAuthResult ? > > handler )
748+ {
749+ try
750+ {
751+ var result = await handler ( context ) ;
752+ McpOauthPendingRequestResponse response =
753+ result is { Cancelled : false , Token : { } token }
754+ ? new McpOauthPendingRequestResponseToken
755+ {
756+ AccessToken = token . AccessToken ,
757+ TokenType = token . TokenType ,
758+ ExpiresIn = token . ExpiresIn
759+ }
760+ : new McpOauthPendingRequestResponseCancelled ( ) ;
761+
762+ await Rpc . Mcp . Oauth . HandlePendingRequestAsync ( requestId , response ) ;
763+ }
764+ catch ( OperationCanceledException )
765+ {
766+ await TryCancelMcpAuthRequestAsync ( requestId ) ;
767+ }
768+ catch ( ObjectDisposedException )
769+ {
770+ await TryCancelMcpAuthRequestAsync ( requestId ) ;
771+ }
772+ catch ( InvalidOperationException )
773+ {
774+ await TryCancelMcpAuthRequestAsync ( requestId ) ;
775+ }
776+ catch ( ArgumentException )
777+ {
778+ await TryCancelMcpAuthRequestAsync ( requestId ) ;
779+ }
780+ catch ( NotSupportedException )
781+ {
782+ await TryCancelMcpAuthRequestAsync ( requestId ) ;
783+ }
784+ catch ( JsonException )
785+ {
786+ await TryCancelMcpAuthRequestAsync ( requestId ) ;
787+ }
788+ catch ( RemoteRpcException )
789+ {
790+ await TryCancelMcpAuthRequestAsync ( requestId ) ;
791+ }
792+ catch ( IOException )
793+ {
794+ await TryCancelMcpAuthRequestAsync ( requestId ) ;
795+ }
796+ catch ( Exception ex ) when ( IsRecoverableMcpAuthFailure ( ex ) )
797+ {
798+ await TryCancelMcpAuthRequestAsync ( requestId ) ;
799+ }
800+ }
801+
802+ private static bool IsRecoverableMcpAuthFailure ( Exception exception )
803+ => exception is not OperationCanceledException
804+ and not OutOfMemoryException
805+ and not StackOverflowException
806+ and not AccessViolationException
807+ and not AppDomainUnloadedException ;
808+
809+ private async Task TryCancelMcpAuthRequestAsync ( string requestId )
810+ {
811+ try
812+ {
813+ await Rpc . Mcp . Oauth . HandlePendingRequestAsync ( requestId , new McpOauthPendingRequestResponseCancelled ( ) ) ;
814+ }
815+ catch ( IOException )
816+ {
817+ // Connection lost — nothing we can do.
818+ }
819+ catch ( ObjectDisposedException )
820+ {
821+ // Connection already disposed — nothing we can do.
822+ }
823+ catch ( RemoteRpcException )
824+ {
825+ // The pending request may already be gone — nothing we can do.
826+ }
827+ }
828+
705829 /// <summary>
706830 /// Executes a tool handler and sends the result back via the HandlePendingToolCall RPC.
707831 /// </summary>
0 commit comments