@@ -23,6 +23,7 @@ import {
2323 ChevronRight ,
2424 Globe ,
2525 Folder ,
26+ AlertTriangle ,
2627} from 'lucide-react' ;
2728import { Card } from '@/components/ui/Card' ;
2829import { Button } from '@/components/ui/Button' ;
@@ -84,6 +85,12 @@ export interface CcwToolsMcpCardProps {
8485 onInstall : ( ) => void ;
8586 /** Installation target: Claude or Codex */
8687 target ?: 'claude' | 'codex' ;
88+ /** Scopes where CCW MCP is currently installed */
89+ installedScopes ?: ( 'global' | 'project' ) [ ] ;
90+ /** Callback to uninstall from a specific scope */
91+ onUninstallScope ?: ( scope : 'global' | 'project' ) => void ;
92+ /** Callback to install to an additional scope */
93+ onInstallToScope ?: ( scope : 'global' | 'project' ) => void ;
8794}
8895
8996// ========== Constants ==========
@@ -115,6 +122,9 @@ export function CcwToolsMcpCard({
115122 onUpdateConfig,
116123 onInstall,
117124 target = 'claude' ,
125+ installedScopes = [ ] ,
126+ onUninstallScope,
127+ onInstallToScope,
118128} : CcwToolsMcpCardProps ) {
119129 const { formatMessage } = useIntl ( ) ;
120130 const queryClient = useQueryClient ( ) ;
@@ -242,9 +252,26 @@ export function CcwToolsMcpCard({
242252 < span className = "text-sm font-medium text-foreground" >
243253 { formatMessage ( { id : 'mcp.ccw.title' } ) }
244254 </ span >
245- < Badge variant = { isInstalled ? 'default' : 'secondary' } className = "text-xs" >
246- { isInstalled ? formatMessage ( { id : 'mcp.ccw.status.installed' } ) : formatMessage ( { id : 'mcp.ccw.status.notInstalled' } ) }
247- </ Badge >
255+ { isInstalled && installedScopes . length > 0 ? (
256+ < >
257+ { installedScopes . map ( ( s ) => (
258+ < Badge key = { s } variant = "default" className = "text-xs" >
259+ { s === 'global' ? < Globe className = "w-3 h-3 mr-1" /> : < Folder className = "w-3 h-3 mr-1" /> }
260+ { formatMessage ( { id : `mcp.ccw.scope.${ s } ` } ) }
261+ </ Badge >
262+ ) ) }
263+ { installedScopes . length >= 2 && (
264+ < Badge variant = "outline" className = "text-xs text-orange-500 border-orange-300" >
265+ < AlertTriangle className = "w-3 h-3 mr-1" />
266+ { formatMessage ( { id : 'mcp.conflict.badge' } ) }
267+ </ Badge >
268+ ) }
269+ </ >
270+ ) : (
271+ < Badge variant = { isInstalled ? 'default' : 'secondary' } className = "text-xs" >
272+ { isInstalled ? formatMessage ( { id : 'mcp.ccw.status.installed' } ) : formatMessage ( { id : 'mcp.ccw.status.notInstalled' } ) }
273+ </ Badge >
274+ ) }
248275 { isCodex && (
249276 < Badge variant = "outline" className = "text-xs text-blue-500" >
250277 Codex
@@ -425,7 +452,7 @@ export function CcwToolsMcpCard({
425452
426453 { /* Install/Uninstall Button */ }
427454 < div className = "pt-3 border-t border-border space-y-3" >
428- { /* Scope Selection - Claude only (Codex is always global) */ }
455+ { /* Scope Selection - Claude only, only when not installed */ }
429456 { ! isInstalled && ! isCodex && (
430457 < div className = "space-y-2" >
431458 < p className = "text-xs font-medium text-muted-foreground uppercase" >
@@ -465,6 +492,20 @@ export function CcwToolsMcpCard({
465492 { formatMessage ( { id : 'mcp.ccw.codexNote' } ) }
466493 </ p >
467494 ) }
495+
496+ { /* Dual-scope conflict warning */ }
497+ { isInstalled && ! isCodex && installedScopes . length >= 2 && (
498+ < div className = "p-3 bg-orange-50 dark:bg-orange-950/30 border border-orange-200 dark:border-orange-800 rounded-lg space-y-1" >
499+ < div className = "flex items-center gap-2 text-orange-700 dark:text-orange-400" >
500+ < AlertTriangle className = "w-4 h-4" />
501+ < span className = "text-sm font-medium" > { formatMessage ( { id : 'mcp.conflict.title' } ) } </ span >
502+ </ div >
503+ < p className = "text-xs text-orange-600 dark:text-orange-400/80" >
504+ { formatMessage ( { id : 'mcp.conflict.description' } , { scope : formatMessage ( { id : 'mcp.scope.global' } ) } ) }
505+ </ p >
506+ </ div >
507+ ) }
508+
468509 { ! isInstalled ? (
469510 < Button
470511 onClick = { handleInstallClick }
@@ -476,7 +517,8 @@ export function CcwToolsMcpCard({
476517 : formatMessage ( { id : isCodex ? 'mcp.ccw.actions.installCodex' : 'mcp.ccw.actions.install' } )
477518 }
478519 </ Button >
479- ) : (
520+ ) : isCodex ? (
521+ /* Codex: single uninstall button */
480522 < Button
481523 variant = "destructive"
482524 onClick = { handleUninstallClick }
@@ -488,6 +530,63 @@ export function CcwToolsMcpCard({
488530 : formatMessage ( { id : 'mcp.ccw.actions.uninstall' } )
489531 }
490532 </ Button >
533+ ) : (
534+ /* Claude: per-scope install/uninstall */
535+ < div className = "space-y-2" >
536+ { /* Install to missing scope */ }
537+ { installedScopes . length === 1 && onInstallToScope && (
538+ < Button
539+ variant = "outline"
540+ onClick = { ( ) => {
541+ const missingScope = installedScopes . includes ( 'global' ) ? 'project' : 'global' ;
542+ onInstallToScope ( missingScope ) ;
543+ } }
544+ disabled = { isPending }
545+ className = "w-full"
546+ >
547+ { installedScopes . includes ( 'global' )
548+ ? formatMessage ( { id : 'mcp.ccw.scope.installToProject' } )
549+ : formatMessage ( { id : 'mcp.ccw.scope.installToGlobal' } )
550+ }
551+ </ Button >
552+ ) }
553+
554+ { /* Per-scope uninstall buttons */ }
555+ { onUninstallScope && installedScopes . map ( ( s ) => (
556+ < Button
557+ key = { s }
558+ variant = "destructive"
559+ size = "sm"
560+ onClick = { ( ) => {
561+ if ( confirm ( formatMessage ( { id : 'mcp.ccw.actions.uninstallScopeConfirm' } , { scope : formatMessage ( { id : `mcp.ccw.scope.${ s } ` } ) } ) ) ) {
562+ onUninstallScope ( s ) ;
563+ }
564+ } }
565+ disabled = { isPending }
566+ className = "w-full"
567+ >
568+ { s === 'global'
569+ ? formatMessage ( { id : 'mcp.ccw.scope.uninstallGlobal' } )
570+ : formatMessage ( { id : 'mcp.ccw.scope.uninstallProject' } )
571+ }
572+ </ Button >
573+ ) ) }
574+
575+ { /* Fallback: full uninstall if no scope info */ }
576+ { ( ! onUninstallScope || installedScopes . length === 0 ) && (
577+ < Button
578+ variant = "destructive"
579+ onClick = { handleUninstallClick }
580+ disabled = { isPending }
581+ className = "w-full"
582+ >
583+ { isPending
584+ ? formatMessage ( { id : 'mcp.ccw.actions.uninstalling' } )
585+ : formatMessage ( { id : 'mcp.ccw.actions.uninstall' } )
586+ }
587+ </ Button >
588+ ) }
589+ </ div >
491590 ) }
492591 </ div >
493592 </ div >
0 commit comments