@@ -8,17 +8,18 @@ import {
88 Button ,
99 ChipCombobox ,
1010 ChipInput ,
11+ ChipTextarea ,
1112 type ComboboxOption ,
1213 Label ,
1314 Skeleton ,
14- Textarea ,
1515} from '@/components/emcn'
1616import { cn } from '@/lib/core/utils/cn'
17- import { generateParameterSchema , sanitizeToolName } from '@/lib/mcp/workflow-tool-schema'
17+ import { sanitizeToolName } from '@/lib/mcp/workflow-tool-schema'
1818import { normalizeInputFormatValue } from '@/lib/workflows/input-format'
1919import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
2020import type { InputFormatField } from '@/lib/workflows/types'
2121import { CreateWorkflowMcpServerModal } from '@/app/workspace/[workspaceId]/settings/components/workflow-mcp-servers/components/create-workflow-mcp-server-modal'
22+ import { isDefaultDescription } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/lib/default-description'
2223import {
2324 useAddWorkflowMcpTool ,
2425 useDeleteWorkflowMcpTool ,
@@ -28,6 +29,7 @@ import {
2829 type WorkflowMcpServer ,
2930 type WorkflowMcpTool ,
3031} from '@/hooks/queries/workflow-mcp-servers'
32+ import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
3133import { EMPTY_SUBBLOCK_VALUES , useSubBlockStore } from '@/stores/workflows/subblock/store'
3234import { useWorkflowStore } from '@/stores/workflows/workflow/store'
3335
@@ -52,6 +54,8 @@ interface McpDeployProps {
5254 onAddedToServer ?: ( ) => void
5355 onSubmittingChange ?: ( submitting : boolean ) => void
5456 onCanSaveChange ?: ( canSave : boolean ) => void
57+ /** Reports whether this workflow is currently exposed as a tool on any server. */
58+ onExposedChange ?: ( exposed : boolean ) => void
5559}
5660
5761function haveSameServerSelection ( a : string [ ] , b : string [ ] ) : boolean {
@@ -60,16 +64,6 @@ function haveSameServerSelection(a: string[], b: string[]): boolean {
6064 return a . every ( ( id ) => bSet . has ( id ) )
6165}
6266
63- function haveSameParameterDescriptions (
64- a : Record < string , string > ,
65- b : Record < string , string >
66- ) : boolean {
67- const aKeys = Object . keys ( a )
68- const bKeys = Object . keys ( b )
69- if ( aKeys . length !== bKeys . length ) return false
70- return aKeys . every ( ( key ) => a [ key ] === b [ key ] )
71- }
72-
7367/**
7468 * Component to query tools for a single server and report back via callback.
7569 */
@@ -102,6 +96,7 @@ export function McpDeploy({
10296 onAddedToServer,
10397 onSubmittingChange,
10498 onCanSaveChange,
99+ onExposedChange,
105100} : McpDeployProps ) {
106101 const params = useParams ( )
107102 const workspaceId = params . workspaceId as string
@@ -111,6 +106,7 @@ export function McpDeploy({
111106 const addToolMutation = useAddWorkflowMcpTool ( )
112107 const deleteToolMutation = useDeleteWorkflowMcpTool ( )
113108 const updateToolMutation = useUpdateWorkflowMcpTool ( )
109+ const { collaborativeSetSubblockValue } = useCollaborativeWorkflow ( )
114110
115111 const blocks = useWorkflowStore ( ( state ) => state . blocks )
116112
@@ -142,23 +138,31 @@ export function McpDeploy({
142138 } , [ starterBlockId , subBlockValues , blocks ] )
143139
144140 const [ toolName , setToolName ] = useState ( ( ) => sanitizeToolName ( workflowName ) )
145- const [ toolDescription , setToolDescription ] = useState ( ( ) => {
146- const normalizedDesc = workflowDescription ?. toLowerCase ( ) . trim ( )
147- const isDefaultDescription =
148- ! workflowDescription ||
149- workflowDescription === workflowName ||
150- normalizedDesc === 'new workflow' ||
151- normalizedDesc === 'your first workflow - start building here!'
152-
153- return isDefaultDescription ? '' : workflowDescription
154- } )
155- const [ parameterDescriptions , setParameterDescriptions ] = useState < Record < string , string > > ( { } )
141+ const [ toolDescription , setToolDescription ] = useState ( ( ) =>
142+ isDefaultDescription ( workflowDescription , workflowName ) ? '' : ( workflowDescription ?? '' )
143+ )
156144 const [ pendingServerChanges , setPendingServerChanges ] = useState < Set < string > > ( ( ) => new Set ( ) )
157145 const [ saveErrors , setSaveErrors ] = useState < string [ ] > ( [ ] )
158146
159- const parameterSchema = useMemo (
160- ( ) => generateParameterSchema ( inputFormat , parameterDescriptions ) ,
161- [ inputFormat , parameterDescriptions ]
147+ /**
148+ * Per-parameter descriptions live on the start block's input format — the single
149+ * source of truth shared by every deployment surface. The tool's parameter schema
150+ * is derived server-side from the deployed workflow on each deploy, so the tool can
151+ * never drift from what actually runs and descriptions are never wiped by a redeploy.
152+ * Editing a description here mutates the workflow and surfaces the redeploy prompt.
153+ */
154+ const updateFieldDescription = useCallback (
155+ ( fieldName : string , description : string ) => {
156+ if ( ! starterBlockId ) return
157+ const currentFields = normalizeInputFormatValue (
158+ useSubBlockStore . getState ( ) . getValue ( starterBlockId , 'inputFormat' )
159+ ) as NormalizedField [ ]
160+ const nextFields = currentFields . map ( ( field ) =>
161+ field . name === fieldName ? { ...field , description } : field
162+ )
163+ collaborativeSetSubblockValue ( starterBlockId , 'inputFormat' , nextFields )
164+ } ,
165+ [ starterBlockId , collaborativeSetSubblockValue ]
162166 )
163167
164168 const toolNameError = useMemo ( ( ) => {
@@ -207,7 +211,6 @@ export function McpDeploy({
207211 const [ savedValues , setSavedValues ] = useState < {
208212 toolName : string
209213 toolDescription : string
210- parameterDescriptions : Record < string , string >
211214 } | null > ( null )
212215
213216 useEffect ( ( ) => {
@@ -219,38 +222,15 @@ export function McpDeploy({
219222 const initialToolName = toolInfo . tool . toolName
220223
221224 const loadedDescription = toolInfo . tool . toolDescription || ''
222- const normalizedLoadedDesc = loadedDescription . toLowerCase ( ) . trim ( )
223- const isDefaultDescription =
224- ! loadedDescription ||
225- loadedDescription === workflowName ||
226- normalizedLoadedDesc === 'new workflow' ||
227- normalizedLoadedDesc === 'your first workflow - start building here!'
228- const initialToolDescription = isDefaultDescription ? '' : loadedDescription
229-
230- const schema = toolInfo . tool . parameterSchema as Record < string , unknown > | undefined
231- const properties = schema ?. properties as
232- | Record < string , { description ?: string } >
233- | undefined
234- const initialParameterDescriptions : Record < string , string > = { }
235- if ( properties ) {
236- for ( const [ name , prop ] of Object . entries ( properties ) ) {
237- if (
238- prop . description &&
239- prop . description !== name &&
240- prop . description !== 'Array of file objects'
241- ) {
242- initialParameterDescriptions [ name ] = prop . description
243- }
244- }
245- }
225+ const initialToolDescription = isDefaultDescription ( loadedDescription , workflowName )
226+ ? ''
227+ : loadedDescription
246228
247229 setToolName ( initialToolName )
248230 setToolDescription ( initialToolDescription )
249- setParameterDescriptions ( initialParameterDescriptions )
250231 setSavedValues ( {
251232 toolName : initialToolName ,
252233 toolDescription : initialToolDescription ,
253- parameterDescriptions : initialParameterDescriptions ,
254234 } )
255235 break
256236 }
@@ -263,11 +243,8 @@ export function McpDeploy({
263243 if ( ! savedValues ) return false
264244 if ( toolName !== savedValues . toolName ) return true
265245 if ( toolDescription !== savedValues . toolDescription ) return true
266- if ( ! haveSameParameterDescriptions ( parameterDescriptions , savedValues . parameterDescriptions ) ) {
267- return true
268- }
269246 return false
270- } , [ toolName , toolDescription , parameterDescriptions , savedValues ] )
247+ } , [ toolName , toolDescription , savedValues ] )
271248 const hasServerSelectionChanges = useMemo (
272249 ( ) => ! haveSameServerSelection ( selectedServerIdsForForm , selectedServerIds ) ,
273250 [ selectedServerIdsForForm , selectedServerIds ]
@@ -280,6 +257,10 @@ export function McpDeploy({
280257 onCanSaveChange ?.( hasChanges && ! ! toolName . trim ( ) && ! toolNameError )
281258 } , [ hasChanges , toolName , toolNameError , onCanSaveChange ] )
282259
260+ useEffect ( ( ) => {
261+ onExposedChange ?.( selectedServerIds . length > 0 )
262+ } , [ selectedServerIds , onExposedChange ] )
263+
283264 const handleSave = async ( ) => {
284265 if ( ! toolName . trim ( ) || toolNameError ) return
285266
@@ -307,7 +288,6 @@ export function McpDeploy({
307288 workflowId,
308289 toolName : toolName . trim ( ) ,
309290 toolDescription : toolDescription . trim ( ) || undefined ,
310- parameterSchema,
311291 } )
312292 addedEntries [ serverId ] = { tool : addedTool , isLoading : false }
313293 onAddedToServer ?.( )
@@ -363,7 +343,6 @@ export function McpDeploy({
363343 toolId : toolInfo . tool . id ,
364344 toolName : toolName . trim ( ) ,
365345 toolDescription : toolDescription . trim ( ) || undefined ,
366- parameterSchema,
367346 } )
368347 } catch ( error ) {
369348 const serverName = servers . find ( ( s ) => s . id === serverId ) ?. name || serverId
@@ -387,7 +366,6 @@ export function McpDeploy({
387366 setSavedValues ( {
388367 toolName,
389368 toolDescription,
390- parameterDescriptions : { ...parameterDescriptions } ,
391369 } )
392370 onCanSaveChange ?.( false )
393371 }
@@ -516,7 +494,7 @@ export function McpDeploy({
516494 < Label className = 'mb-[6.5px] block pl-0.5 font-medium text-[var(--text-primary)] text-small' >
517495 Description
518496 </ Label >
519- < Textarea
497+ < ChipTextarea
520498 placeholder = 'Describe what this tool does...'
521499 className = 'min-h-[100px] resize-none'
522500 value = { toolDescription }
@@ -529,6 +507,9 @@ export function McpDeploy({
529507 < Label className = 'mb-[6.5px] block pl-0.5 font-medium text-[var(--text-primary)] text-small' >
530508 Parameters ({ inputFormat . length } )
531509 </ Label >
510+ < p className = 'mb-[6.5px] pl-0.5 text-[var(--text-secondary)] text-xs' >
511+ Descriptions are part of the workflow's inputs. Redeploy to apply changes to the tool.
512+ </ p >
532513 < div className = 'flex flex-col gap-2' >
533514 { inputFormat . map ( ( field ) => (
534515 < div
@@ -549,13 +530,8 @@ export function McpDeploy({
549530 < div className = 'flex flex-col gap-1.5' >
550531 < Label className = 'text-small' > Description</ Label >
551532 < ChipInput
552- value = { parameterDescriptions [ field . name ] || '' }
553- onChange = { ( e ) =>
554- setParameterDescriptions ( ( prev ) => ( {
555- ...prev ,
556- [ field . name ] : e . target . value ,
557- } ) )
558- }
533+ value = { field . description ?? '' }
534+ onChange = { ( e ) => updateFieldDescription ( field . name , e . target . value ) }
559535 placeholder = { `Enter description for ${ field . name } ` }
560536 />
561537 </ div >
0 commit comments