@@ -13,9 +13,11 @@ import {
1313 Skeleton ,
1414 Textarea ,
1515} from '@/components/emcn'
16+ import { ApiClientError } from '@/lib/api/client/errors'
1617import { cn } from '@/lib/core/utils/cn'
1718import {
1819 extractDescriptionOverrides ,
20+ extractInputFormatFromBlocks ,
1921 generateToolInputSchema ,
2022 getMeaningfulWorkflowDescription ,
2123 sanitizeToolName ,
@@ -35,6 +37,7 @@ import {
3537} from '@/hooks/queries/workflow-mcp-servers'
3638import { EMPTY_SUBBLOCK_VALUES , useSubBlockStore } from '@/stores/workflows/subblock/store'
3739import { useWorkflowStore } from '@/stores/workflows/workflow/store'
40+ import type { WorkflowState } from '@/stores/workflows/workflow/types'
3841
3942const logger = createLogger ( 'McpToolDeploy' )
4043
@@ -54,6 +57,7 @@ interface McpDeployProps {
5457 workflowName : string
5558 workflowDescription ?: string | null
5659 isDeployed : boolean
60+ deployedState ?: WorkflowState | null
5761 onAddedToServer ?: ( ) => void
5862 onSubmittingChange ?: ( submitting : boolean ) => void
5963 onCanSaveChange ?: ( canSave : boolean ) => void
@@ -122,6 +126,7 @@ export function McpDeploy({
122126 workflowName,
123127 workflowDescription,
124128 isDeployed,
129+ deployedState,
125130 onAddedToServer,
126131 onSubmittingChange,
127132 onCanSaveChange,
@@ -153,7 +158,7 @@ export function McpDeploy({
153158 ( state ) => ( workflowId ? state . workflowValues [ workflowId ] : undefined ) ?? EMPTY_SUBBLOCK_VALUES
154159 )
155160
156- const inputFormat = useMemo ( ( ) : NormalizedField [ ] => {
161+ const liveInputFormat = useMemo ( ( ) : NormalizedField [ ] => {
157162 if ( ! starterBlockId ) return [ ]
158163
159164 const storeValue = subBlockValues [ starterBlockId ] ?. inputFormat
@@ -165,6 +170,19 @@ export function McpDeploy({
165170 return normalizeInputFormatValue ( blockValue ) as NormalizedField [ ]
166171 } , [ starterBlockId , subBlockValues , blocks ] )
167172
173+ // The served tool is built from the DEPLOYED Start block and the server materializes overrides
174+ // against it, so base the form on the deployed inputs (falling back to the live editor only while
175+ // the deployed state loads) to keep the modal's defaults and override classification matching what
176+ // is actually served.
177+ const deployedInputFormat = useMemo ( ( ) : NormalizedField [ ] => {
178+ const deployedBlocks = deployedState ?. blocks
179+ if ( ! deployedBlocks ) return [ ]
180+ return ( extractInputFormatFromBlocks ( deployedBlocks as Record < string , unknown > ) ??
181+ [ ] ) as NormalizedField [ ]
182+ } , [ deployedState ] )
183+
184+ const inputFormat = deployedState ? deployedInputFormat : liveInputFormat
185+
168186 const [ toolName , setToolName ] = useState ( ( ) => sanitizeToolName ( workflowName ) )
169187 const [ toolDescription , setToolDescription ] = useState ( '' )
170188 const workflowDescriptionFallback = getMeaningfulWorkflowDescription (
@@ -238,7 +256,9 @@ export function McpDeploy({
238256 } | null > ( null )
239257
240258 useEffect ( ( ) => {
241- if ( savedValues ) return
259+ // Wait for the deployed state so the legacy migration diffs against the deployed base (what the
260+ // server uses), not the live editor — otherwise unpublished Start-block edits mis-classify.
261+ if ( savedValues || ! deployedState ) return
242262
243263 for ( const server of servers ) {
244264 const toolInfo = serverToolsMap [ server . id ]
@@ -268,7 +288,7 @@ export function McpDeploy({
268288 break
269289 }
270290 }
271- } , [ servers , serverToolsMap , startBlockDescriptions , inputFormat , savedValues ] )
291+ } , [ servers , serverToolsMap , startBlockDescriptions , inputFormat , deployedState , savedValues ] )
272292
273293 const selectedServerIdsForForm = draftSelectedServerIds ?? selectedServerIds
274294
@@ -400,8 +420,27 @@ export function McpDeploy({
400420 } )
401421 } catch ( error ) {
402422 const serverName = servers . find ( ( s ) => s . id === serverId ) ?. name || serverId
403- errors . push ( `Failed to update on ${ serverName } ` )
404- logger . error ( `Failed to update tool on server ${ serverId } :` , error )
423+ // The tool can be removed out-of-band (undeploying a workflow deletes its MCP tools), so
424+ // a stale-cache update may hit a missing tool — re-create it instead of failing the save.
425+ if ( error instanceof ApiClientError && error . status === 404 ) {
426+ try {
427+ const recreated = await addToolMutation . mutateAsync ( {
428+ workspaceId,
429+ serverId,
430+ workflowId,
431+ toolName : toolName . trim ( ) ,
432+ toolDescription : toolDescriptionForSave ,
433+ parameterDescriptionOverrides,
434+ } )
435+ addedEntries [ serverId ] = { tool : recreated , isLoading : false }
436+ } catch ( recreateError ) {
437+ errors . push ( `Failed to update on ${ serverName } ` )
438+ logger . error ( `Failed to re-add tool on server ${ serverId } :` , recreateError )
439+ }
440+ } else {
441+ errors . push ( `Failed to update on ${ serverName } ` )
442+ logger . error ( `Failed to update tool on server ${ serverId } :` , error )
443+ }
405444 }
406445 }
407446 }
0 commit comments