1
- import React , { useState , useEffect } from 'react' ;
1
+ import { useState , useEffect } from 'react' ;
2
2
import { Button } from './ui/button' ;
3
3
import { Input } from './ui/input' ;
4
- import { ScrollArea } from './ui/scroll-area' ;
5
4
import { Badge } from './ui/badge' ;
6
- import { X , Plus , Settings , Shield , AlertTriangle , Moon , Sun , Server , Edit3 , Trash2 , Play , Globe , Terminal , Zap } from 'lucide-react' ;
5
+ import { X , Plus , Settings , Shield , AlertTriangle , Moon , Sun , Server , Edit3 , Trash2 , Globe , Terminal , Zap , FolderOpen } from 'lucide-react' ;
7
6
import { useTheme } from '../contexts/ThemeContext' ;
8
7
9
- function ToolsSettings ( { isOpen, onClose } ) {
8
+ function ToolsSettings ( { isOpen, onClose, projects = [ ] } ) {
10
9
const { isDarkMode, toggleDarkMode } = useTheme ( ) ;
11
10
const [ allowedTools , setAllowedTools ] = useState ( [ ] ) ;
12
11
const [ disallowedTools , setDisallowedTools ] = useState ( [ ] ) ;
@@ -17,14 +16,14 @@ function ToolsSettings({ isOpen, onClose }) {
17
16
const [ saveStatus , setSaveStatus ] = useState ( null ) ;
18
17
const [ projectSortOrder , setProjectSortOrder ] = useState ( 'name' ) ;
19
18
20
- // MCP server management state
21
19
const [ mcpServers , setMcpServers ] = useState ( [ ] ) ;
22
20
const [ showMcpForm , setShowMcpForm ] = useState ( false ) ;
23
21
const [ editingMcpServer , setEditingMcpServer ] = useState ( null ) ;
24
22
const [ mcpFormData , setMcpFormData ] = useState ( {
25
23
name : '' ,
26
24
type : 'stdio' ,
27
- scope : 'user' , // Always use user scope
25
+ scope : 'user' ,
26
+ projectPath : '' , // For local scope
28
27
config : {
29
28
command : '' ,
30
29
args : [ ] ,
@@ -42,7 +41,6 @@ function ToolsSettings({ isOpen, onClose }) {
42
41
const [ mcpToolsLoading , setMcpToolsLoading ] = useState ( { } ) ;
43
42
const [ activeTab , setActiveTab ] = useState ( 'tools' ) ;
44
43
const [ jsonValidationError , setJsonValidationError ] = useState ( '' ) ;
45
-
46
44
// Common tool patterns
47
45
const commonTools = [
48
46
'Bash(git log:*)' ,
@@ -153,6 +151,8 @@ function ToolsSettings({ isOpen, onClose }) {
153
151
body : JSON . stringify ( {
154
152
name : serverData . name ,
155
153
type : serverData . type ,
154
+ scope : serverData . scope ,
155
+ projectPath : serverData . projectPath ,
156
156
command : serverData . config ?. command ,
157
157
args : serverData . config ?. args || [ ] ,
158
158
url : serverData . config ?. url ,
@@ -285,8 +285,9 @@ function ToolsSettings({ isOpen, onClose }) {
285
285
setProjectSortOrder ( 'name' ) ;
286
286
}
287
287
288
- // Load MCP servers from API
288
+ // Load MCP servers and projects from API
289
289
await fetchMcpServers ( ) ;
290
+ await fetchAvailableProjects ( ) ;
290
291
} catch ( error ) {
291
292
console . error ( 'Error loading tool settings:' , error ) ;
292
293
// Set defaults on error
@@ -354,7 +355,8 @@ function ToolsSettings({ isOpen, onClose }) {
354
355
setMcpFormData ( {
355
356
name : '' ,
356
357
type : 'stdio' ,
357
- scope : 'user' , // Always use user scope for global availability
358
+ scope : 'user' , // Default to user scope
359
+ projectPath : '' ,
358
360
config : {
359
361
command : '' ,
360
362
args : [ ] ,
@@ -378,8 +380,11 @@ function ToolsSettings({ isOpen, onClose }) {
378
380
name : server . name ,
379
381
type : server . type ,
380
382
scope : server . scope ,
383
+ projectPath : server . projectPath || '' ,
381
384
config : { ...server . config } ,
382
- raw : server . raw // Store raw config for display
385
+ raw : server . raw , // Store raw config for display
386
+ importMode : 'form' , // Always use form mode when editing
387
+ jsonInput : ''
383
388
} ) ;
384
389
} else {
385
390
resetMcpForm ( ) ;
@@ -404,7 +409,9 @@ function ToolsSettings({ isOpen, onClose }) {
404
409
} ,
405
410
body : JSON . stringify ( {
406
411
name : mcpFormData . name ,
407
- jsonConfig : mcpFormData . jsonInput
412
+ jsonConfig : mcpFormData . jsonInput ,
413
+ scope : mcpFormData . scope ,
414
+ projectPath : mcpFormData . projectPath
408
415
} )
409
416
} ) ;
410
417
@@ -972,39 +979,12 @@ function ToolsSettings({ isOpen, onClose }) {
972
979
</ div >
973
980
974
981
< div className = "flex items-center gap-2 ml-4" >
975
- < Button
976
- onClick = { ( ) => handleMcpTest ( server . id , server . scope ) }
977
- variant = "ghost"
978
- size = "sm"
979
- disabled = { mcpTestResults [ server . id ] ?. loading }
980
- className = "text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
981
- title = "Test connection"
982
- >
983
- { mcpTestResults [ server . id ] ?. loading ? (
984
- < div className = "w-4 h-4 animate-spin rounded-full border-2 border-blue-600 border-t-transparent" />
985
- ) : (
986
- < Play className = "w-4 h-4" />
987
- ) }
988
- </ Button >
989
- < Button
990
- onClick = { ( ) => handleMcpToolsDiscovery ( server . id , server . scope ) }
991
- variant = "ghost"
992
- size = "sm"
993
- disabled = { mcpToolsLoading [ server . id ] }
994
- className = "text-purple-600 hover:text-purple-700 dark:text-purple-400 dark:hover:text-purple-300"
995
- title = "Discover tools"
996
- >
997
- { mcpToolsLoading [ server . id ] ? (
998
- < div className = "w-4 h-4 animate-spin rounded-full border-2 border-purple-600 border-t-transparent" />
999
- ) : (
1000
- < Settings className = "w-4 h-4" />
1001
- ) }
1002
- </ Button >
1003
982
< Button
1004
983
onClick = { ( ) => openMcpForm ( server ) }
1005
984
variant = "ghost"
1006
985
size = "sm"
1007
986
className = "text-gray-600 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
987
+ title = "Edit server"
1008
988
>
1009
989
< Edit3 className = "w-4 h-4" />
1010
990
</ Button >
@@ -1013,6 +993,7 @@ function ToolsSettings({ isOpen, onClose }) {
1013
993
variant = "ghost"
1014
994
size = "sm"
1015
995
className = "text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"
996
+ title = "Delete server"
1016
997
>
1017
998
< Trash2 className = "w-4 h-4" />
1018
999
</ Button >
@@ -1042,7 +1023,8 @@ function ToolsSettings({ isOpen, onClose }) {
1042
1023
</ div >
1043
1024
1044
1025
< form onSubmit = { handleMcpSubmit } className = "p-4 space-y-4" >
1045
- { /* Import Mode Toggle */ }
1026
+
1027
+ { ! editingMcpServer && (
1046
1028
< div className = "flex gap-2 mb-4" >
1047
1029
< button
1048
1030
type = "button"
@@ -1067,6 +1049,104 @@ function ToolsSettings({ isOpen, onClose }) {
1067
1049
JSON Import
1068
1050
</ button >
1069
1051
</ div >
1052
+ ) }
1053
+
1054
+ { /* Show current scope when editing */ }
1055
+ { mcpFormData . importMode === 'form' && editingMcpServer && (
1056
+ < div className = "bg-gray-50 dark:bg-gray-900/50 border border-gray-200 dark:border-gray-700 rounded-lg p-3" >
1057
+ < label className = "block text-sm font-medium text-foreground mb-2" >
1058
+ Scope
1059
+ </ label >
1060
+ < div className = "flex items-center gap-2" >
1061
+ { mcpFormData . scope === 'user' ? < Globe className = "w-4 h-4" /> : < FolderOpen className = "w-4 h-4" /> }
1062
+ < span className = "text-sm" >
1063
+ { mcpFormData . scope === 'user' ? 'User (Global)' : 'Project (Local)' }
1064
+ </ span >
1065
+ { mcpFormData . scope === 'local' && mcpFormData . projectPath && (
1066
+ < span className = "text-xs text-muted-foreground" >
1067
+ - { mcpFormData . projectPath }
1068
+ </ span >
1069
+ ) }
1070
+ </ div >
1071
+ < p className = "text-xs text-muted-foreground mt-2" >
1072
+ Scope cannot be changed when editing an existing server
1073
+ </ p >
1074
+ </ div >
1075
+ ) }
1076
+
1077
+ { /* Scope Selection - Moved to top, disabled when editing */ }
1078
+ { mcpFormData . importMode === 'form' && ! editingMcpServer && (
1079
+ < div className = "space-y-4" >
1080
+ < div >
1081
+ < label className = "block text-sm font-medium text-foreground mb-2" >
1082
+ Scope *
1083
+ </ label >
1084
+ < div className = "flex gap-2" >
1085
+ < button
1086
+ type = "button"
1087
+ onClick = { ( ) => setMcpFormData ( prev => ( { ...prev , scope : 'user' , projectPath : '' } ) ) }
1088
+ className = { `flex-1 px-4 py-2 rounded-lg font-medium transition-colors ${
1089
+ mcpFormData . scope === 'user'
1090
+ ? 'bg-blue-600 text-white'
1091
+ : 'bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700'
1092
+ } `}
1093
+ >
1094
+ < div className = "flex items-center justify-center gap-2" >
1095
+ < Globe className = "w-4 h-4" />
1096
+ < span > User (Global)</ span >
1097
+ </ div >
1098
+ </ button >
1099
+ < button
1100
+ type = "button"
1101
+ onClick = { ( ) => setMcpFormData ( prev => ( { ...prev , scope : 'local' } ) ) }
1102
+ className = { `flex-1 px-4 py-2 rounded-lg font-medium transition-colors ${
1103
+ mcpFormData . scope === 'local'
1104
+ ? 'bg-blue-600 text-white'
1105
+ : 'bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700'
1106
+ } `}
1107
+ >
1108
+ < div className = "flex items-center justify-center gap-2" >
1109
+ < FolderOpen className = "w-4 h-4" />
1110
+ < span > Project (Local)</ span >
1111
+ </ div >
1112
+ </ button >
1113
+ </ div >
1114
+ < p className = "text-xs text-muted-foreground mt-2" >
1115
+ { mcpFormData . scope === 'user'
1116
+ ? 'User scope: Available across all projects on your machine'
1117
+ : 'Local scope: Only available in the selected project'
1118
+ }
1119
+ </ p >
1120
+ </ div >
1121
+
1122
+ { /* Project Selection for Local Scope */ }
1123
+ { mcpFormData . scope === 'local' && ! editingMcpServer && (
1124
+ < div >
1125
+ < label className = "block text-sm font-medium text-foreground mb-2" >
1126
+ Project *
1127
+ </ label >
1128
+ < select
1129
+ value = { mcpFormData . projectPath }
1130
+ onChange = { ( e ) => setMcpFormData ( prev => ( { ...prev , projectPath : e . target . value } ) ) }
1131
+ className = "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 rounded-lg focus:ring-blue-500 focus:border-blue-500"
1132
+ required = { mcpFormData . scope === 'local' }
1133
+ >
1134
+ < option value = "" > Select a project...</ option >
1135
+ { projects . map ( project => (
1136
+ < option key = { project . name } value = { project . path || project . fullPath } >
1137
+ { project . displayName || project . name }
1138
+ </ option >
1139
+ ) ) }
1140
+ </ select >
1141
+ { mcpFormData . projectPath && (
1142
+ < p className = "text-xs text-muted-foreground mt-1" >
1143
+ Path: { mcpFormData . projectPath }
1144
+ </ p >
1145
+ ) }
1146
+ </ div >
1147
+ ) }
1148
+ </ div >
1149
+ ) }
1070
1150
1071
1151
{ /* Basic Info */ }
1072
1152
< div className = "grid grid-cols-1 md:grid-cols-2 gap-4" >
@@ -1104,7 +1184,6 @@ function ToolsSettings({ isOpen, onClose }) {
1104
1184
) }
1105
1185
</ div >
1106
1186
1107
- { /* Scope is fixed to user - no selection needed */ }
1108
1187
1109
1188
{ /* Show raw configuration details when editing */ }
1110
1189
{ editingMcpServer && mcpFormData . raw && mcpFormData . importMode === 'form' && (
0 commit comments