@@ -321,20 +321,22 @@ private void ApplyPlan(ConnectionPagePlan plan)
321321 bool isRecovery = plan . Mode == ConnectionPageMode . Recovery ;
322322 bool isAdding = plan . Mode == ConnectionPageMode . AddGateway ;
323323
324- // Operator + Node cards only when we actually have an active operator
325- // connection AND we're not in a focused sub-view (Welcome / Recovery /
326- // AddGateway). Recovery's help block carries the action; the role
327- // cards would just compete with it.
324+ // Operator + Node cards are normally tied to an active operator session.
325+ // Local MCP-only mode has no operator session, but still needs the Node
326+ // card so users can see that MCP is serving local tools.
328327 bool hasOperatorSession = _lastSnapshot . OverallState is
329328 OverallConnectionState . Connected
330329 or OverallConnectionState . Ready
331330 or OverallConnectionState . Degraded
332331 or OverallConnectionState . Connecting
333332 or OverallConnectionState . PairingRequired
334333 or OverallConnectionState . Disconnecting ;
335- bool showRoles = hasOperatorSession && ! isWelcome && ! isAdding && ! isRecovery ;
334+ var hasStandaloneNodeCard = plan . NodeCard != NodeCardState . Hidden && ! hasOperatorSession ;
335+ bool showRoles = ( hasOperatorSession || hasStandaloneNodeCard ) && ! isAdding && ! isRecovery ;
336336 CockpitPanel . Visibility = showRoles ? Visibility . Visible : Visibility . Collapsed ;
337- OperatorSection . Visibility = showRoles ? Visibility . Visible : Visibility . Collapsed ;
337+ OperatorSection . Visibility = showRoles && plan . OperatorCard != OperatorCardState . Hidden
338+ ? Visibility . Visible
339+ : Visibility . Collapsed ;
338340
339341 // Bottom section: exactly one of these is visible
340342 // • SavedGatewaysCard — Cockpit / Recovery (always present when registry has items)
@@ -814,6 +816,14 @@ or NodeCardState.OnNodeRateLimited
814816 Helpers . FluentIconCatalog . StatusOk ,
815817 "SystemFillColorSuccessBrush" ,
816818 capCount == 1 ? LocalizationHelper . GetString ( "ConnectionPage_NodeActiveOneCapability" ) : string . Format ( LocalizationHelper . GetString ( "ConnectionPage_NodeActiveCapabilities" ) , capCount ) ) ,
819+ NodeCardState . OnNodeConnecting => (
820+ Helpers . FluentIconCatalog . Sync ,
821+ "SystemFillColorCautionBrush" ,
822+ LocalizationHelper . GetString ( "ConnectionPage_NodeStarting" ) ) ,
823+ NodeCardState . OffMcpOnly => (
824+ Helpers . FluentIconCatalog . Terminal ,
825+ "SystemFillColorAttentionBrush" ,
826+ LocalizationHelper . GetString ( "ConnectionPage_NodeMcpOnly" ) ) ,
817827 NodeCardState . OnPermissionsIncomplete => (
818828 Helpers . FluentIconCatalog . StatusWarn ,
819829 "SystemFillColorCautionBrush" ,
@@ -858,47 +868,72 @@ or NodeCardState.OnNodeRateLimited
858868 ? ResolveBrush ( "SystemFillColorCriticalBrush" )
859869 : ResolveBrush ( "TextFillColorPrimaryBrush" ) ;
860870
861- // The gateway's node-list contract owns this boundary. Pending
862- // declarations are visible for approval context but never counted or
863- // labeled as approved/effective.
864- bool showSurfaces = settings != null && plan . NodeCard != NodeCardState . Off
865- && plan . NodeCard != NodeCardState . Hidden ;
866- NodeCapabilityText . Visibility = showSurfaces ? Visibility . Visible : Visibility . Collapsed ;
867- NodeCommandText . Visibility = showSurfaces ? Visibility . Visible : Visibility . Collapsed ;
868- NodePermissionText . Visibility = showSurfaces ? Visibility . Visible : Visibility . Collapsed ;
869- if ( showSurfaces )
870- {
871- NodeCapabilityText . Text = BuildNodeSurfaceListString (
872- "ConnectionPage_NodeEffectiveCapabilities" ,
873- plan . NodeEffectiveCapabilities ) ;
874- NodeCommandText . Text = BuildNodeSurfaceListString (
875- "ConnectionPage_NodeEffectiveCommands" ,
876- plan . NodeEffectiveCommands ) ;
877- NodePermissionText . Text = BuildNodePermissionListString (
878- "ConnectionPage_NodeEffectivePermissions" ,
879- plan . NodeEffectivePermissions ) ;
880- }
881-
882- var showPendingDeclarations = showSurfaces &&
883- ( plan . NodeApprovalState is GatewayNodeApprovalState . PendingApproval or
884- GatewayNodeApprovalState . PendingReapproval ||
885- plan . NodePendingDeclaredCapabilities . Count > 0 ||
886- plan . NodePendingDeclaredCommands . Count > 0 ||
887- plan . NodePendingDeclaredPermissions . Count > 0 ) ;
888- NodePendingDeclarationsPanel . Visibility = showPendingDeclarations
889- ? Visibility . Visible
890- : Visibility . Collapsed ;
891- if ( showPendingDeclarations )
871+ if ( plan . NodeCard == NodeCardState . OffMcpOnly )
872+ {
873+ NodeCapabilityText . Visibility = Visibility . Visible ;
874+ NodeCapabilityText . Text = LocalizationHelper . Format (
875+ "ConnectionPage_NodeMcpOnlyReachable" , NodeService . McpServerUrl ) ;
876+ NodeCommandText . Visibility = Visibility . Collapsed ;
877+ NodePermissionText . Visibility = Visibility . Collapsed ;
878+ NodePendingDeclarationsPanel . Visibility = Visibility . Collapsed ;
879+
880+ var mcpError = CurrentApp . ActiveNodeService ? . McpStartupError ;
881+ if ( ! string . IsNullOrEmpty ( mcpError ) )
882+ {
883+ NodeStatusIcon . Glyph = Helpers . FluentIconCatalog . StatusErr ;
884+ NodeStatusIcon . Foreground = ResolveBrush ( "SystemFillColorCriticalBrush" ) ;
885+ NodeStatusText . Text = LocalizationHelper . GetString ( "ConnectionPage_NodeMcpError" ) ;
886+ NodeStatusText . Foreground = ResolveBrush ( "SystemFillColorCriticalBrush" ) ;
887+ NodeCapabilityText . Visibility = Visibility . Collapsed ;
888+ NodeBodyText . Text = mcpError ;
889+ NodeBodyText . Foreground = ResolveBrush ( "SystemFillColorCriticalBrush" ) ;
890+ NodeBodyText . Visibility = Visibility . Visible ;
891+ }
892+ }
893+ else
892894 {
893- NodePendingCapabilityText . Text = BuildNodeSurfaceListString (
894- "ConnectionPage_NodePendingDeclaredCapabilities" ,
895- plan . NodePendingDeclaredCapabilities ) ;
896- NodePendingCommandText . Text = BuildNodeSurfaceListString (
897- "ConnectionPage_NodePendingDeclaredCommands" ,
898- plan . NodePendingDeclaredCommands ) ;
899- NodePendingPermissionText . Text = BuildNodePermissionListString (
900- "ConnectionPage_NodePendingDeclaredPermissions" ,
901- plan . NodePendingDeclaredPermissions ) ;
895+ // Pending declarations are visible for approval context but never
896+ // counted as the active node contract.
897+ bool showSurfaces = settings != null && plan . NodeCard != NodeCardState . Off
898+ && plan . NodeCard != NodeCardState . Hidden
899+ && plan . NodeCard != NodeCardState . OnNodeConnecting ;
900+ NodeCapabilityText . Visibility = showSurfaces ? Visibility . Visible : Visibility . Collapsed ;
901+ NodeCommandText . Visibility = showSurfaces ? Visibility . Visible : Visibility . Collapsed ;
902+ NodePermissionText . Visibility = showSurfaces ? Visibility . Visible : Visibility . Collapsed ;
903+ if ( showSurfaces )
904+ {
905+ NodeCapabilityText . Text = BuildNodeSurfaceListString (
906+ "ConnectionPage_NodeEffectiveCapabilities" ,
907+ plan . NodeEffectiveCapabilities ) ;
908+ NodeCommandText . Text = BuildNodeSurfaceListString (
909+ "ConnectionPage_NodeEffectiveCommands" ,
910+ plan . NodeEffectiveCommands ) ;
911+ NodePermissionText . Text = BuildNodePermissionListString (
912+ "ConnectionPage_NodeEffectivePermissions" ,
913+ plan . NodeEffectivePermissions ) ;
914+ }
915+
916+ var showPendingDeclarations = showSurfaces &&
917+ ( plan . NodeApprovalState is GatewayNodeApprovalState . PendingApproval or
918+ GatewayNodeApprovalState . PendingReapproval ||
919+ plan . NodePendingDeclaredCapabilities . Count > 0 ||
920+ plan . NodePendingDeclaredCommands . Count > 0 ||
921+ plan . NodePendingDeclaredPermissions . Count > 0 ) ;
922+ NodePendingDeclarationsPanel . Visibility = showPendingDeclarations
923+ ? Visibility . Visible
924+ : Visibility . Collapsed ;
925+ if ( showPendingDeclarations )
926+ {
927+ NodePendingCapabilityText . Text = BuildNodeSurfaceListString (
928+ "ConnectionPage_NodePendingDeclaredCapabilities" ,
929+ plan . NodePendingDeclaredCapabilities ) ;
930+ NodePendingCommandText . Text = BuildNodeSurfaceListString (
931+ "ConnectionPage_NodePendingDeclaredCommands" ,
932+ plan . NodePendingDeclaredCommands ) ;
933+ NodePendingPermissionText . Text = BuildNodePermissionListString (
934+ "ConnectionPage_NodePendingDeclaredPermissions" ,
935+ plan . NodePendingDeclaredPermissions ) ;
936+ }
902937 }
903938
904939 // Sync toggle from current settings (suppress event)
@@ -1102,7 +1137,9 @@ private List<Border> BuildCapabilityChips(IReadOnlyList<string>? capabilities, N
11021137 {
11031138 var chips = new List < Border > ( ) ;
11041139 if ( capabilities == null || capabilities . Count == 0 ) return chips ;
1105- if ( state == NodeCardState . Off || state == NodeCardState . Hidden ) return chips ;
1140+ if ( state == NodeCardState . Off || state == NodeCardState . Hidden
1141+ || state == NodeCardState . OffMcpOnly || state == NodeCardState . OnNodeConnecting )
1142+ return chips ;
11061143
11071144 void Add ( string label , bool enabled , bool warn = false , bool error = false )
11081145 {
0 commit comments