fix(mcp): use correct modal for creating workflow MCP servers in deploy (#3822)

* fix(mcp): use correct modal for creating workflow MCP servers in deploy

* fix(mcp): show workflows field during loading and when empty
This commit is contained in:
Waleed
2026-03-27 20:22:30 -07:00
committed by GitHub
parent eac41ca105
commit b4064c57fb
3 changed files with 177 additions and 127 deletions

View File

@@ -0,0 +1,162 @@
'use client'
import { useCallback, useEffect, useState } from 'react'
import { createLogger } from '@sim/logger'
import {
Button,
ButtonGroup,
ButtonGroupItem,
Combobox,
type ComboboxOption,
Input as EmcnInput,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
Textarea,
} from '@/components/emcn'
import { FormField } from '@/app/workspace/[workspaceId]/settings/components/mcp/components'
import { useCreateWorkflowMcpServer } from '@/hooks/queries/workflow-mcp-servers'
const logger = createLogger('CreateWorkflowMcpServerModal')
const INITIAL_FORM_DATA: { name: string; description: string; isPublic: boolean } = {
name: '',
description: '',
isPublic: false,
}
interface CreateWorkflowMcpServerModalProps {
open: boolean
onOpenChange: (open: boolean) => void
workspaceId: string
workflowOptions?: ComboboxOption[]
isLoadingWorkflows?: boolean
}
export function CreateWorkflowMcpServerModal({
open,
onOpenChange,
workspaceId,
workflowOptions,
isLoadingWorkflows = false,
}: CreateWorkflowMcpServerModalProps) {
const createServerMutation = useCreateWorkflowMcpServer()
const [formData, setFormData] = useState({ ...INITIAL_FORM_DATA })
const [selectedWorkflowIds, setSelectedWorkflowIds] = useState<string[]>([])
const isFormValid = formData.name.trim().length > 0
useEffect(() => {
if (open) {
setFormData({ ...INITIAL_FORM_DATA })
setSelectedWorkflowIds([])
}
}, [open])
const handleCreateServer = useCallback(async () => {
if (!formData.name.trim()) return
try {
await createServerMutation.mutateAsync({
workspaceId,
name: formData.name.trim(),
description: formData.description.trim() || undefined,
isPublic: formData.isPublic,
workflowIds: selectedWorkflowIds.length > 0 ? selectedWorkflowIds : undefined,
})
onOpenChange(false)
} catch (err) {
logger.error('Failed to create server:', err)
}
}, [formData, selectedWorkflowIds, workspaceId, onOpenChange])
const showWorkflows = workflowOptions !== undefined
return (
<Modal open={open} onOpenChange={onOpenChange}>
<ModalContent>
<ModalHeader>Add New MCP Server</ModalHeader>
<ModalBody>
<div className='flex flex-col gap-3'>
<FormField label='Server Name'>
<EmcnInput
placeholder='e.g., My MCP Server'
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className='h-9'
/>
</FormField>
<FormField label='Description'>
<Textarea
placeholder='Describe what this MCP server does (optional)'
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
className='min-h-[60px] resize-none'
/>
</FormField>
{showWorkflows && (
<FormField label='Workflows'>
<Combobox
options={workflowOptions ?? []}
multiSelect
multiSelectValues={selectedWorkflowIds}
onMultiSelectChange={setSelectedWorkflowIds}
placeholder='Select workflows...'
searchable
searchPlaceholder='Search workflows...'
isLoading={isLoadingWorkflows}
disabled={createServerMutation.isPending}
emptyMessage='No deployed workflows available'
overlayContent={
selectedWorkflowIds.length > 0 ? (
<span className='text-[var(--text-primary)]'>
{selectedWorkflowIds.length} workflow
{selectedWorkflowIds.length !== 1 ? 's' : ''} selected
</span>
) : undefined
}
/>
</FormField>
)}
<FormField label='Access'>
<div className='flex items-center gap-3'>
<ButtonGroup
value={formData.isPublic ? 'public' : 'private'}
onValueChange={(value) =>
setFormData({ ...formData, isPublic: value === 'public' })
}
>
<ButtonGroupItem value='private'>API Key</ButtonGroupItem>
<ButtonGroupItem value='public'>Public</ButtonGroupItem>
</ButtonGroup>
{formData.isPublic && (
<span className='text-[var(--text-muted)] text-xs'>
No authentication required
</span>
)}
</div>
</FormField>
</div>
</ModalBody>
<ModalFooter>
<Button variant='default' onClick={() => onOpenChange(false)}>
Cancel
</Button>
<Button
onClick={handleCreateServer}
disabled={!isFormValid || createServerMutation.isPending}
variant='primary'
>
{createServerMutation.isPending ? 'Adding...' : 'Add Server'}
</Button>
</ModalFooter>
</ModalContent>
</Modal>
)
}

View File

@@ -35,7 +35,6 @@ import { useApiKeys } from '@/hooks/queries/api-keys'
import { useCreateMcpServer } from '@/hooks/queries/mcp'
import {
useAddWorkflowMcpTool,
useCreateWorkflowMcpServer,
useDeleteWorkflowMcpServer,
useDeleteWorkflowMcpTool,
useDeployedWorkflows,
@@ -49,6 +48,7 @@ import {
import { useWorkspaceSettings } from '@/hooks/queries/workspace'
import { CreateApiKeyModal } from '../api-keys/components'
import { FormField, McpServerSkeleton } from '../mcp/components'
import { CreateWorkflowMcpServerModal } from './create-workflow-mcp-server-modal'
const logger = createLogger('WorkflowMcpServers')
@@ -955,13 +955,10 @@ export function WorkflowMcpServers() {
const { data: servers = [], isLoading, error } = useWorkflowMcpServers(workspaceId)
const { data: deployedWorkflows = [], isLoading: isLoadingWorkflows } =
useDeployedWorkflows(workspaceId)
const createServerMutation = useCreateWorkflowMcpServer()
const deleteServerMutation = useDeleteWorkflowMcpServer()
const [searchTerm, setSearchTerm] = useState('')
const [showAddModal, setShowAddModal] = useState(false)
const [formData, setFormData] = useState({ name: '', description: '', isPublic: false })
const [selectedWorkflowIds, setSelectedWorkflowIds] = useState<string[]>([])
const [selectedServerId, setSelectedServerId] = useState<string | null>(null)
const [serverToDelete, setServerToDelete] = useState<WorkflowMcpServer | null>(null)
const [deletingServers, setDeletingServers] = useState<Set<string>>(() => new Set())
@@ -979,29 +976,6 @@ export function WorkflowMcpServers() {
}))
}, [deployedWorkflows])
const resetForm = useCallback(() => {
setFormData({ name: '', description: '', isPublic: false })
setSelectedWorkflowIds([])
setShowAddModal(false)
}, [])
const handleCreateServer = async () => {
if (!formData.name.trim()) return
try {
await createServerMutation.mutateAsync({
workspaceId,
name: formData.name.trim(),
description: formData.description.trim() || undefined,
isPublic: formData.isPublic,
workflowIds: selectedWorkflowIds.length > 0 ? selectedWorkflowIds : undefined,
})
resetForm()
} catch (err) {
logger.error('Failed to create server:', err)
}
}
const handleDeleteServer = async () => {
if (!serverToDelete) return
@@ -1026,7 +1000,6 @@ export function WorkflowMcpServers() {
const hasServers = servers.length > 0
const showNoResults = searchTerm.trim() && filteredServers.length === 0 && hasServers
const isFormValid = formData.name.trim().length > 0
if (selectedServerId) {
return (
@@ -1123,86 +1096,13 @@ export function WorkflowMcpServers() {
</div>
</div>
<Modal open={showAddModal} onOpenChange={(open) => !open && resetForm()}>
<ModalContent>
<ModalHeader>Add New MCP Server</ModalHeader>
<ModalBody>
<div className='flex flex-col gap-3'>
<FormField label='Server Name'>
<EmcnInput
placeholder='e.g., My MCP Server'
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className='h-9'
/>
</FormField>
<FormField label='Description'>
<Textarea
placeholder='Describe what this MCP server does (optional)'
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
className='min-h-[60px] resize-none'
/>
</FormField>
<FormField label='Workflows'>
<Combobox
options={workflowOptions}
multiSelect
multiSelectValues={selectedWorkflowIds}
onMultiSelectChange={setSelectedWorkflowIds}
placeholder='Select workflows...'
searchable
searchPlaceholder='Search workflows...'
isLoading={isLoadingWorkflows}
disabled={createServerMutation.isPending}
emptyMessage='No deployed workflows available'
overlayContent={
selectedWorkflowIds.length > 0 ? (
<span className='text-[var(--text-primary)]'>
{selectedWorkflowIds.length} workflow
{selectedWorkflowIds.length !== 1 ? 's' : ''} selected
</span>
) : undefined
}
/>
</FormField>
<FormField label='Access'>
<div className='flex items-center gap-3'>
<ButtonGroup
value={formData.isPublic ? 'public' : 'private'}
onValueChange={(value) =>
setFormData({ ...formData, isPublic: value === 'public' })
}
>
<ButtonGroupItem value='private'>API Key</ButtonGroupItem>
<ButtonGroupItem value='public'>Public</ButtonGroupItem>
</ButtonGroup>
{formData.isPublic && (
<span className='text-[var(--text-muted)] text-xs'>
No authentication required
</span>
)}
</div>
</FormField>
</div>
</ModalBody>
<ModalFooter>
<Button variant='default' onClick={resetForm}>
Cancel
</Button>
<Button
onClick={handleCreateServer}
disabled={!isFormValid || createServerMutation.isPending}
variant='primary'
>
{createServerMutation.isPending ? 'Adding...' : 'Add Server'}
</Button>
</ModalFooter>
</ModalContent>
</Modal>
<CreateWorkflowMcpServerModal
open={showAddModal}
onOpenChange={setShowAddModal}
workspaceId={workspaceId}
workflowOptions={workflowOptions}
isLoadingWorkflows={isLoadingWorkflows}
/>
<Modal open={!!serverToDelete} onOpenChange={(open) => !open && setServerToDelete(null)}>
<ModalContent size='sm'>

View File

@@ -17,8 +17,7 @@ import { generateToolInputSchema, sanitizeToolName } from '@/lib/mcp/workflow-to
import { normalizeInputFormatValue } from '@/lib/workflows/input-format'
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
import type { InputFormatField } from '@/lib/workflows/types'
import { McpServerFormModal } from '@/app/workspace/[workspaceId]/settings/components/mcp/components/mcp-server-form-modal/mcp-server-form-modal'
import { useAllowedMcpDomains, useCreateMcpServer } from '@/hooks/queries/mcp'
import { CreateWorkflowMcpServerModal } from '@/app/workspace/[workspaceId]/settings/components/workflow-mcp-servers/create-workflow-mcp-server-modal'
import {
useAddWorkflowMcpTool,
useDeleteWorkflowMcpTool,
@@ -28,7 +27,6 @@ import {
type WorkflowMcpServer,
type WorkflowMcpTool,
} from '@/hooks/queries/workflow-mcp-servers'
import { useAvailableEnvVarKeys } from '@/hooks/use-available-env-vars'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
@@ -102,11 +100,7 @@ export function McpDeploy({
}: McpDeployProps) {
const params = useParams()
const workspaceId = params.workspaceId as string
const [showMcpModal, setShowMcpModal] = useState(false)
const createMcpServer = useCreateMcpServer()
const { data: allowedMcpDomains = null } = useAllowedMcpDomains()
const availableEnvVars = useAvailableEnvVarKeys(workspaceId)
const [showCreateModal, setShowCreateModal] = useState(false)
const { data: servers = [], isLoading: isLoadingServers } = useWorkflowMcpServers(workspaceId)
const addToolMutation = useAddWorkflowMcpTool()
@@ -473,22 +467,16 @@ export function McpDeploy({
<>
<div className='flex h-full flex-col items-center justify-center gap-3'>
<p className='text-[13px] text-[var(--text-muted)]'>
Create an MCP Server in Settings MCP Servers first.
Create an MCP Server to expose your workflows as tools.
</p>
<Button variant='tertiary' onClick={() => setShowMcpModal(true)}>
<Button variant='tertiary' onClick={() => setShowCreateModal(true)}>
Create MCP Server
</Button>
</div>
<McpServerFormModal
open={showMcpModal}
onOpenChange={setShowMcpModal}
mode='add'
onSubmit={async (config) => {
await createMcpServer.mutateAsync({ workspaceId, config: { ...config, enabled: true } })
}}
<CreateWorkflowMcpServerModal
open={showCreateModal}
onOpenChange={setShowCreateModal}
workspaceId={workspaceId}
availableEnvVars={availableEnvVars}
allowedMcpDomains={allowedMcpDomains}
/>
</>
)