mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
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:
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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'>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user