Skip to content

Commit 6a731ef

Browse files
committed
fix(mcp): enforce tool name validation in deploy modal
1 parent 402472e commit 6a731ef

1 file changed

Lines changed: 30 additions & 6 deletions

File tree

  • apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/mcp

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/mcp/mcp.tsx

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
Textarea,
1515
} from '@/components/emcn'
1616
import { generateToolInputSchema, sanitizeToolName } from '@/lib/mcp/workflow-tool-schema'
17+
import { cn } from '@/lib/utils'
1718
import { normalizeInputFormatValue } from '@/lib/workflows/input-format'
1819
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
1920
import type { InputFormatField } from '@/lib/workflows/types'
@@ -32,6 +33,10 @@ import { useWorkflowStore } from '@/stores/workflows/workflow/store'
3233

3334
const logger = createLogger('McpToolDeploy')
3435

36+
/** MCP tool names allow lowercase letters, numbers, and underscores only */
37+
const TOOL_NAME_PATTERN = /^[a-z0-9_]+$/
38+
const MAX_TOOL_NAME_LENGTH = 64
39+
3540
/** InputFormatField with guaranteed name (after normalization) */
3641
type NormalizedField = InputFormatField & { name: string }
3742

@@ -166,6 +171,18 @@ export function McpDeploy({
166171
[inputFormat, parameterDescriptions]
167172
)
168173

174+
const toolNameError = useMemo(() => {
175+
const trimmed = toolName.trim()
176+
if (!trimmed) return null
177+
if (trimmed.length > MAX_TOOL_NAME_LENGTH) {
178+
return `Tool name must be ${MAX_TOOL_NAME_LENGTH} characters or fewer`
179+
}
180+
if (!TOOL_NAME_PATTERN.test(trimmed)) {
181+
return 'Tool name can only contain lowercase letters, numbers, and underscores'
182+
}
183+
return null
184+
}, [toolName])
185+
169186
const [serverToolsMap, setServerToolsMap] = useState<
170187
Record<string, { tool: WorkflowMcpTool | null; isLoading: boolean }>
171188
>({})
@@ -270,11 +287,11 @@ export function McpDeploy({
270287
(hasToolConfigurationChanges && selectedServerIdsForForm.length > 0)
271288

272289
useEffect(() => {
273-
onCanSaveChange?.(hasChanges && !!toolName.trim())
274-
}, [hasChanges, toolName, onCanSaveChange])
290+
onCanSaveChange?.(hasChanges && !!toolName.trim() && !toolNameError)
291+
}, [hasChanges, toolName, toolNameError, onCanSaveChange])
275292

276293
const handleSave = async () => {
277-
if (!toolName.trim()) return
294+
if (!toolName.trim() || toolNameError) return
278295

279296
const currentIds = new Set(selectedServerIds)
280297
const nextIds = new Set(selectedServerIdsForForm)
@@ -492,9 +509,16 @@ export function McpDeploy({
492509
value={toolName}
493510
onChange={(e) => setToolName(e.target.value)}
494511
placeholder='e.g., book_flight'
512+
aria-invalid={!!toolNameError}
513+
className={cn(toolNameError && 'border-[var(--text-error)]')}
495514
/>
496-
<p className='mt-[6.5px] text-[var(--text-secondary)] text-xs'>
497-
Use lowercase letters, numbers, and underscores only
515+
<p
516+
className={cn(
517+
'mt-[6.5px] text-xs',
518+
toolNameError ? 'text-[var(--text-error)]' : 'text-[var(--text-secondary)]'
519+
)}
520+
>
521+
{toolNameError ?? 'Use lowercase letters, numbers, and underscores only'}
498522
</p>
499523
</div>
500524

@@ -564,7 +588,7 @@ export function McpDeploy({
564588
placeholder='Select servers...'
565589
searchable
566590
searchPlaceholder='Search servers...'
567-
disabled={!toolName.trim() || isPending}
591+
disabled={!toolName.trim() || !!toolNameError || isPending}
568592
overlayContent={
569593
<span className='truncate text-[var(--text-primary)]'>{selectedServersLabel}</span>
570594
}

0 commit comments

Comments
 (0)