@@ -10,6 +10,9 @@ import {
1010 Plus ,
1111 ChevronDown ,
1212 Server ,
13+ Pencil ,
14+ Check ,
15+ X ,
1316} from "lucide-react" ;
1417
1518type TestResult = { success : true ; tools : string [ ] } | { success : false ; error : string } ;
@@ -33,6 +36,11 @@ export const ProjectSettingsSection: React.FC = () => {
3336 const [ testingNewCommand , setTestingNewCommand ] = useState ( false ) ;
3437 const [ newCommandTestResult , setNewCommandTestResult ] = useState < TestResult | null > ( null ) ;
3538
39+ // Edit server state
40+ const [ editingServer , setEditingServer ] = useState < string | null > ( null ) ;
41+ const [ editCommand , setEditCommand ] = useState ( "" ) ;
42+ const [ savingEdit , setSavingEdit ] = useState ( false ) ;
43+
3644 // Set default project when projects load
3745 useEffect ( ( ) => {
3846 if ( projectList . length > 0 && ! selectedProject ) {
@@ -155,6 +163,46 @@ export const ProjectSettingsSection: React.FC = () => {
155163 }
156164 } , [ api , selectedProject , newServerName , newServerCommand , refresh ] ) ;
157165
166+ const handleStartEdit = useCallback ( ( name : string , command : string ) => {
167+ setEditingServer ( name ) ;
168+ setEditCommand ( command ) ;
169+ } , [ ] ) ;
170+
171+ const handleCancelEdit = useCallback ( ( ) => {
172+ setEditingServer ( null ) ;
173+ setEditCommand ( "" ) ;
174+ } , [ ] ) ;
175+
176+ const handleSaveEdit = useCallback ( async ( ) => {
177+ if ( ! api || ! selectedProject || ! editingServer || ! editCommand . trim ( ) ) return ;
178+ setSavingEdit ( true ) ;
179+ setError ( null ) ;
180+ try {
181+ const result = await api . projects . mcp . add ( {
182+ projectPath : selectedProject ,
183+ name : editingServer ,
184+ command : editCommand . trim ( ) ,
185+ } ) ;
186+ if ( ! result . success ) {
187+ setError ( result . error ?? "Failed to update MCP server" ) ;
188+ } else {
189+ setEditingServer ( null ) ;
190+ setEditCommand ( "" ) ;
191+ // Clear test result for this server since command changed
192+ setTestResults ( ( prev ) => {
193+ const next = new Map ( prev ) ;
194+ next . delete ( editingServer ) ;
195+ return next ;
196+ } ) ;
197+ await refresh ( ) ;
198+ }
199+ } catch ( err ) {
200+ setError ( err instanceof Error ? err . message : "Failed to update MCP server" ) ;
201+ } finally {
202+ setSavingEdit ( false ) ;
203+ }
204+ } , [ api , selectedProject , editingServer , editCommand , refresh ] ) ;
205+
158206 if ( projectList . length === 0 ) {
159207 return (
160208 < div className = "flex flex-col items-center justify-center py-12 text-center" >
@@ -227,52 +275,107 @@ export const ProjectSettingsSection: React.FC = () => {
227275 { Object . entries ( servers ) . map ( ( [ name , command ] ) => {
228276 const isTesting = testingServer === name ;
229277 const testResult = testResults . get ( name ) ;
278+ const isEditing = editingServer === name ;
230279 return (
231280 < li key = { name } className = "border-border-medium bg-secondary/20 rounded-lg border p-3" >
232281 < div className = "flex items-start justify-between gap-3" >
233282 < div className = "min-w-0 flex-1" >
234283 < div className = "flex items-center gap-2" >
235284 < span className = "font-medium" > { name } </ span >
236- { testResult ?. success && (
285+ { testResult ?. success && ! isEditing && (
237286 < span className = "rounded bg-green-500/10 px-1.5 py-0.5 text-xs text-green-500" >
238287 { testResult . tools . length } tools
239288 </ span >
240289 ) }
241290 </ div >
242- < p className = "text-muted-foreground mt-0.5 text-xs break-all" > { command } </ p >
291+ { isEditing ? (
292+ < input
293+ type = "text"
294+ value = { editCommand }
295+ onChange = { ( e ) => setEditCommand ( e . target . value ) }
296+ className = "border-border-medium bg-secondary/30 text-foreground placeholder:text-muted-foreground focus:ring-accent mt-1 w-full rounded-md border px-2 py-1 text-xs focus:ring-1 focus:outline-none"
297+ autoFocus
298+ onKeyDown = { ( e ) => {
299+ if ( e . key === "Enter" ) {
300+ void handleSaveEdit ( ) ;
301+ } else if ( e . key === "Escape" ) {
302+ handleCancelEdit ( ) ;
303+ }
304+ } }
305+ />
306+ ) : (
307+ < p className = "text-muted-foreground mt-0.5 text-xs break-all" > { command } </ p >
308+ ) }
243309 </ div >
244310 < div className = "flex shrink-0 gap-1" >
245- < button
246- type = "button"
247- onClick = { ( ) => void handleTest ( name ) }
248- disabled = { isTesting }
249- className = "text-muted-foreground hover:bg-secondary hover:text-accent rounded p-1.5 transition-colors disabled:opacity-50"
250- title = "Test connection"
251- >
252- { isTesting ? (
253- < Loader2 className = "h-4 w-4 animate-spin" />
254- ) : (
255- < Play className = "h-4 w-4" />
256- ) }
257- </ button >
258- < button
259- type = "button"
260- onClick = { ( ) => void handleRemove ( name ) }
261- disabled = { loading }
262- className = "text-muted-foreground hover:bg-destructive/10 hover:text-destructive rounded p-1.5 transition-colors"
263- title = "Remove server"
264- >
265- < Trash2 className = "h-4 w-4" />
266- </ button >
311+ { isEditing ? (
312+ < >
313+ < button
314+ type = "button"
315+ onClick = { ( ) => void handleSaveEdit ( ) }
316+ disabled = { savingEdit || ! editCommand . trim ( ) }
317+ className = "text-muted-foreground rounded p-1.5 transition-colors hover:bg-green-500/10 hover:text-green-500 disabled:opacity-50"
318+ title = "Save (Enter)"
319+ >
320+ { savingEdit ? (
321+ < Loader2 className = "h-4 w-4 animate-spin" />
322+ ) : (
323+ < Check className = "h-4 w-4" />
324+ ) }
325+ </ button >
326+ < button
327+ type = "button"
328+ onClick = { handleCancelEdit }
329+ disabled = { savingEdit }
330+ className = "text-muted-foreground hover:bg-destructive/10 hover:text-destructive rounded p-1.5 transition-colors"
331+ title = "Cancel (Esc)"
332+ >
333+ < X className = "h-4 w-4" />
334+ </ button >
335+ </ >
336+ ) : (
337+ < >
338+ < button
339+ type = "button"
340+ onClick = { ( ) => void handleTest ( name ) }
341+ disabled = { isTesting }
342+ className = "text-muted-foreground hover:bg-secondary hover:text-accent rounded p-1.5 transition-colors disabled:opacity-50"
343+ title = "Test connection"
344+ >
345+ { isTesting ? (
346+ < Loader2 className = "h-4 w-4 animate-spin" />
347+ ) : (
348+ < Play className = "h-4 w-4" />
349+ ) }
350+ </ button >
351+ < button
352+ type = "button"
353+ onClick = { ( ) => handleStartEdit ( name , command ) }
354+ className = "text-muted-foreground hover:bg-secondary hover:text-accent rounded p-1.5 transition-colors"
355+ title = "Edit command"
356+ >
357+ < Pencil className = "h-4 w-4" />
358+ </ button >
359+ < button
360+ type = "button"
361+ onClick = { ( ) => void handleRemove ( name ) }
362+ disabled = { loading }
363+ className = "text-muted-foreground hover:bg-destructive/10 hover:text-destructive rounded p-1.5 transition-colors"
364+ title = "Remove server"
365+ >
366+ < Trash2 className = "h-4 w-4" />
367+ </ button >
368+ </ >
369+ ) }
267370 </ div >
268371 </ div >
269- { testResult && ! testResult . success && (
372+ { testResult && ! testResult . success && ! isEditing && (
270373 < div className = "text-destructive mt-2 flex items-start gap-1.5 text-xs" >
271374 < XCircle className = "mt-0.5 h-3 w-3 shrink-0" />
272375 < span > { testResult . error } </ span >
273376 </ div >
274377 ) }
275- { testResult ?. success && testResult . tools . length > 0 && (
378+ { testResult ?. success && testResult . tools . length > 0 && ! isEditing && (
276379 < p className = "text-muted-foreground mt-2 text-xs" >
277380 Tools: { testResult . tools . join ( ", " ) }
278381 </ p >
0 commit comments