@@ -173,7 +173,7 @@ export function McpDeploy({
173173 [ ]
174174 )
175175
176- const selectedServerIds = useMemo ( ( ) => {
176+ const actualServerIds = useMemo ( ( ) => {
177177 const ids : string [ ] = [ ]
178178 for ( const server of servers ) {
179179 const toolInfo = serverToolsMap [ server . id ]
@@ -184,6 +184,20 @@ export function McpDeploy({
184184 return ids
185185 } , [ servers , serverToolsMap ] )
186186
187+ const [ pendingSelectedServerIds , setPendingSelectedServerIds ] = useState < string [ ] | null > ( null )
188+
189+ const selectedServerIds = pendingSelectedServerIds ?? actualServerIds
190+
191+ useEffect ( ( ) => {
192+ if ( pendingSelectedServerIds !== null ) {
193+ const pendingSet = new Set ( pendingSelectedServerIds )
194+ const actualSet = new Set ( actualServerIds )
195+ if ( pendingSet . size === actualSet . size && [ ...pendingSet ] . every ( ( id ) => actualSet . has ( id ) ) ) {
196+ setPendingSelectedServerIds ( null )
197+ }
198+ }
199+ } , [ actualServerIds , pendingSelectedServerIds ] )
200+
187201 const hasLoadedInitialData = useRef ( false )
188202
189203 useEffect ( ( ) => {
@@ -241,7 +255,17 @@ export function McpDeploy({
241255 } , [ toolName , toolDescription , parameterDescriptions , savedValues ] )
242256
243257 const hasDeployedTools = selectedServerIds . length > 0
258+
259+ const hasServerSelectionChanges = useMemo ( ( ) => {
260+ if ( pendingSelectedServerIds === null ) return false
261+ const pendingSet = new Set ( pendingSelectedServerIds )
262+ const actualSet = new Set ( actualServerIds )
263+ if ( pendingSet . size !== actualSet . size ) return true
264+ return ! [ ...pendingSet ] . every ( ( id ) => actualSet . has ( id ) )
265+ } , [ pendingSelectedServerIds , actualServerIds ] )
266+
244267 const hasChanges = useMemo ( ( ) => {
268+ if ( hasServerSelectionChanges && selectedServerIds . length > 0 ) return true
245269 if ( ! savedValues || ! hasDeployedTools ) return false
246270 if ( toolName !== savedValues . toolName ) return true
247271 if ( toolDescription !== savedValues . toolDescription ) return true
@@ -251,7 +275,15 @@ export function McpDeploy({
251275 return true
252276 }
253277 return false
254- } , [ toolName , toolDescription , parameterDescriptions , hasDeployedTools , savedValues ] )
278+ } , [
279+ toolName ,
280+ toolDescription ,
281+ parameterDescriptions ,
282+ hasDeployedTools ,
283+ savedValues ,
284+ hasServerSelectionChanges ,
285+ selectedServerIds . length ,
286+ ] )
255287
256288 useEffect ( ( ) => {
257289 onCanSaveChange ?.( hasChanges && hasDeployedTools && ! ! toolName . trim ( ) )
@@ -262,74 +294,19 @@ export function McpDeploy({
262294 } , [ servers . length , onHasServersChange ] )
263295
264296 /**
265- * Save tool configuration to all deployed servers
297+ * Save tool configuration to all selected servers.
298+ * This handles both adding to new servers and updating existing tools.
266299 */
267300 const handleSave = useCallback ( async ( ) => {
268301 if ( ! toolName . trim ( ) ) return
269-
270- const toolsToUpdate : Array < { serverId : string ; toolId : string } > = [ ]
271- for ( const server of servers ) {
272- const toolInfo = serverToolsMap [ server . id ]
273- if ( toolInfo ?. tool ) {
274- toolsToUpdate . push ( { serverId : server . id , toolId : toolInfo . tool . id } )
275- }
276- }
277-
278- if ( toolsToUpdate . length === 0 ) return
302+ if ( selectedServerIds . length === 0 ) return
279303
280304 onSubmittingChange ?.( true )
281305 try {
282- for ( const { serverId, toolId } of toolsToUpdate ) {
283- await updateToolMutation . mutateAsync ( {
284- workspaceId,
285- serverId,
286- toolId,
287- toolName : toolName . trim ( ) ,
288- toolDescription : toolDescription . trim ( ) || undefined ,
289- parameterSchema,
290- } )
291- }
292- // Update saved values after successful save (triggers re-render → hasChanges becomes false)
293- setSavedValues ( {
294- toolName,
295- toolDescription,
296- parameterDescriptions : { ...parameterDescriptions } ,
297- } )
298- onCanSaveChange ?.( false )
299- onSubmittingChange ?.( false )
300- } catch ( error ) {
301- logger . error ( 'Failed to save tool configuration:' , error )
302- onSubmittingChange ?.( false )
303- }
304- } , [
305- toolName ,
306- toolDescription ,
307- parameterDescriptions ,
308- parameterSchema ,
309- servers ,
310- serverToolsMap ,
311- workspaceId ,
312- updateToolMutation ,
313- onSubmittingChange ,
314- onCanSaveChange ,
315- ] )
316-
317- const serverOptions : ComboboxOption [ ] = useMemo ( ( ) => {
318- return servers . map ( ( server ) => ( {
319- label : server . name ,
320- value : server . id ,
321- } ) )
322- } , [ servers ] )
323-
324- const handleServerSelectionChange = useCallback (
325- async ( newSelectedIds : string [ ] ) => {
326- if ( ! toolName . trim ( ) ) return
327-
328- const currentIds = new Set ( selectedServerIds )
329- const newIds = new Set ( newSelectedIds )
330-
331- const toAdd = newSelectedIds . filter ( ( id ) => ! currentIds . has ( id ) )
332- const toRemove = selectedServerIds . filter ( ( id ) => ! newIds . has ( id ) )
306+ const actualSet = new Set ( actualServerIds )
307+ const toAdd = selectedServerIds . filter ( ( id ) => ! actualSet . has ( id ) )
308+ const toRemove = actualServerIds . filter ( ( id ) => ! selectedServerIds . includes ( id ) )
309+ const toUpdate = selectedServerIds . filter ( ( id ) => actualSet . has ( id ) )
333310
334311 for ( const serverId of toAdd ) {
335312 setPendingServerChanges ( ( prev ) => new Set ( prev ) . add ( serverId ) )
@@ -342,11 +319,8 @@ export function McpDeploy({
342319 toolDescription : toolDescription . trim ( ) || undefined ,
343320 parameterSchema,
344321 } )
345- refetchServers ( )
346322 onAddedToServer ?.( )
347323 logger . info ( `Added workflow ${ workflowId } as tool to server ${ serverId } ` )
348- } catch ( error ) {
349- logger . error ( 'Failed to add tool:' , error )
350324 } finally {
351325 setPendingServerChanges ( ( prev ) => {
352326 const next = new Set ( prev )
@@ -371,9 +345,6 @@ export function McpDeploy({
371345 delete next [ serverId ]
372346 return next
373347 } )
374- refetchServers ( )
375- } catch ( error ) {
376- logger . error ( 'Failed to remove tool:' , error )
377348 } finally {
378349 setPendingServerChanges ( ( prev ) => {
379350 const next = new Set ( prev )
@@ -383,21 +354,69 @@ export function McpDeploy({
383354 }
384355 }
385356 }
386- } ,
387- [
388- selectedServerIds ,
389- serverToolsMap ,
390- toolName ,
391- toolDescription ,
392- workspaceId ,
393- workflowId ,
394- parameterSchema ,
395- addToolMutation ,
396- deleteToolMutation ,
397- refetchServers ,
398- onAddedToServer ,
399- ]
400- )
357+
358+ for ( const serverId of toUpdate ) {
359+ const toolInfo = serverToolsMap [ serverId ]
360+ if ( toolInfo ?. tool ) {
361+ await updateToolMutation . mutateAsync ( {
362+ workspaceId,
363+ serverId,
364+ toolId : toolInfo . tool . id ,
365+ toolName : toolName . trim ( ) ,
366+ toolDescription : toolDescription . trim ( ) || undefined ,
367+ parameterSchema,
368+ } )
369+ }
370+ }
371+
372+ refetchServers ( )
373+
374+ setPendingSelectedServerIds ( null )
375+ setSavedValues ( {
376+ toolName,
377+ toolDescription,
378+ parameterDescriptions : { ...parameterDescriptions } ,
379+ } )
380+ onCanSaveChange ?.( false )
381+ onSubmittingChange ?.( false )
382+ } catch ( error ) {
383+ logger . error ( 'Failed to save tool configuration:' , error )
384+ onSubmittingChange ?.( false )
385+ }
386+ } , [
387+ toolName ,
388+ toolDescription ,
389+ parameterDescriptions ,
390+ parameterSchema ,
391+ servers ,
392+ serverToolsMap ,
393+ workspaceId ,
394+ workflowId ,
395+ selectedServerIds ,
396+ actualServerIds ,
397+ addToolMutation ,
398+ deleteToolMutation ,
399+ updateToolMutation ,
400+ refetchServers ,
401+ onSubmittingChange ,
402+ onCanSaveChange ,
403+ onAddedToServer ,
404+ ] )
405+
406+ const serverOptions : ComboboxOption [ ] = useMemo ( ( ) => {
407+ return servers . map ( ( server ) => ( {
408+ label : server . name ,
409+ value : server . id ,
410+ } ) )
411+ } , [ servers ] )
412+
413+ /**
414+ * Handle server selection change - only updates local state.
415+ * Actual add/remove operations happen when user clicks Save.
416+ */
417+ const handleServerSelectionChange = useCallback ( ( newSelectedIds : string [ ] ) => {
418+ setPendingSelectedServerIds ( newSelectedIds )
419+ } , [ ] )
401420
402421 const selectedServersLabel = useMemo ( ( ) => {
403422 const count = selectedServerIds . length
0 commit comments