Skip to content

Commit b4064c5

Browse files
authored
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
1 parent eac41ca commit b4064c5

File tree

3 files changed

+177
-127
lines changed

3 files changed

+177
-127
lines changed
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
'use client'
2+
3+
import { useCallback, useEffect, useState } from 'react'
4+
import { createLogger } from '@sim/logger'
5+
import {
6+
Button,
7+
ButtonGroup,
8+
ButtonGroupItem,
9+
Combobox,
10+
type ComboboxOption,
11+
Input as EmcnInput,
12+
Modal,
13+
ModalBody,
14+
ModalContent,
15+
ModalFooter,
16+
ModalHeader,
17+
Textarea,
18+
} from '@/components/emcn'
19+
import { FormField } from '@/app/workspace/[workspaceId]/settings/components/mcp/components'
20+
import { useCreateWorkflowMcpServer } from '@/hooks/queries/workflow-mcp-servers'
21+
22+
const logger = createLogger('CreateWorkflowMcpServerModal')
23+
24+
const INITIAL_FORM_DATA: { name: string; description: string; isPublic: boolean } = {
25+
name: '',
26+
description: '',
27+
isPublic: false,
28+
}
29+
30+
interface CreateWorkflowMcpServerModalProps {
31+
open: boolean
32+
onOpenChange: (open: boolean) => void
33+
workspaceId: string
34+
workflowOptions?: ComboboxOption[]
35+
isLoadingWorkflows?: boolean
36+
}
37+
38+
export function CreateWorkflowMcpServerModal({
39+
open,
40+
onOpenChange,
41+
workspaceId,
42+
workflowOptions,
43+
isLoadingWorkflows = false,
44+
}: CreateWorkflowMcpServerModalProps) {
45+
const createServerMutation = useCreateWorkflowMcpServer()
46+
47+
const [formData, setFormData] = useState({ ...INITIAL_FORM_DATA })
48+
const [selectedWorkflowIds, setSelectedWorkflowIds] = useState<string[]>([])
49+
50+
const isFormValid = formData.name.trim().length > 0
51+
52+
useEffect(() => {
53+
if (open) {
54+
setFormData({ ...INITIAL_FORM_DATA })
55+
setSelectedWorkflowIds([])
56+
}
57+
}, [open])
58+
59+
const handleCreateServer = useCallback(async () => {
60+
if (!formData.name.trim()) return
61+
62+
try {
63+
await createServerMutation.mutateAsync({
64+
workspaceId,
65+
name: formData.name.trim(),
66+
description: formData.description.trim() || undefined,
67+
isPublic: formData.isPublic,
68+
workflowIds: selectedWorkflowIds.length > 0 ? selectedWorkflowIds : undefined,
69+
})
70+
onOpenChange(false)
71+
} catch (err) {
72+
logger.error('Failed to create server:', err)
73+
}
74+
}, [formData, selectedWorkflowIds, workspaceId, onOpenChange])
75+
76+
const showWorkflows = workflowOptions !== undefined
77+
78+
return (
79+
<Modal open={open} onOpenChange={onOpenChange}>
80+
<ModalContent>
81+
<ModalHeader>Add New MCP Server</ModalHeader>
82+
<ModalBody>
83+
<div className='flex flex-col gap-3'>
84+
<FormField label='Server Name'>
85+
<EmcnInput
86+
placeholder='e.g., My MCP Server'
87+
value={formData.name}
88+
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
89+
className='h-9'
90+
/>
91+
</FormField>
92+
93+
<FormField label='Description'>
94+
<Textarea
95+
placeholder='Describe what this MCP server does (optional)'
96+
value={formData.description}
97+
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
98+
className='min-h-[60px] resize-none'
99+
/>
100+
</FormField>
101+
102+
{showWorkflows && (
103+
<FormField label='Workflows'>
104+
<Combobox
105+
options={workflowOptions ?? []}
106+
multiSelect
107+
multiSelectValues={selectedWorkflowIds}
108+
onMultiSelectChange={setSelectedWorkflowIds}
109+
placeholder='Select workflows...'
110+
searchable
111+
searchPlaceholder='Search workflows...'
112+
isLoading={isLoadingWorkflows}
113+
disabled={createServerMutation.isPending}
114+
emptyMessage='No deployed workflows available'
115+
overlayContent={
116+
selectedWorkflowIds.length > 0 ? (
117+
<span className='text-[var(--text-primary)]'>
118+
{selectedWorkflowIds.length} workflow
119+
{selectedWorkflowIds.length !== 1 ? 's' : ''} selected
120+
</span>
121+
) : undefined
122+
}
123+
/>
124+
</FormField>
125+
)}
126+
127+
<FormField label='Access'>
128+
<div className='flex items-center gap-3'>
129+
<ButtonGroup
130+
value={formData.isPublic ? 'public' : 'private'}
131+
onValueChange={(value) =>
132+
setFormData({ ...formData, isPublic: value === 'public' })
133+
}
134+
>
135+
<ButtonGroupItem value='private'>API Key</ButtonGroupItem>
136+
<ButtonGroupItem value='public'>Public</ButtonGroupItem>
137+
</ButtonGroup>
138+
{formData.isPublic && (
139+
<span className='text-[var(--text-muted)] text-xs'>
140+
No authentication required
141+
</span>
142+
)}
143+
</div>
144+
</FormField>
145+
</div>
146+
</ModalBody>
147+
<ModalFooter>
148+
<Button variant='default' onClick={() => onOpenChange(false)}>
149+
Cancel
150+
</Button>
151+
<Button
152+
onClick={handleCreateServer}
153+
disabled={!isFormValid || createServerMutation.isPending}
154+
variant='primary'
155+
>
156+
{createServerMutation.isPending ? 'Adding...' : 'Add Server'}
157+
</Button>
158+
</ModalFooter>
159+
</ModalContent>
160+
</Modal>
161+
)
162+
}

apps/sim/app/workspace/[workspaceId]/settings/components/workflow-mcp-servers/workflow-mcp-servers.tsx

Lines changed: 8 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ import { useApiKeys } from '@/hooks/queries/api-keys'
3535
import { useCreateMcpServer } from '@/hooks/queries/mcp'
3636
import {
3737
useAddWorkflowMcpTool,
38-
useCreateWorkflowMcpServer,
3938
useDeleteWorkflowMcpServer,
4039
useDeleteWorkflowMcpTool,
4140
useDeployedWorkflows,
@@ -49,6 +48,7 @@ import {
4948
import { useWorkspaceSettings } from '@/hooks/queries/workspace'
5049
import { CreateApiKeyModal } from '../api-keys/components'
5150
import { FormField, McpServerSkeleton } from '../mcp/components'
51+
import { CreateWorkflowMcpServerModal } from './create-workflow-mcp-server-modal'
5252

5353
const logger = createLogger('WorkflowMcpServers')
5454

@@ -955,13 +955,10 @@ export function WorkflowMcpServers() {
955955
const { data: servers = [], isLoading, error } = useWorkflowMcpServers(workspaceId)
956956
const { data: deployedWorkflows = [], isLoading: isLoadingWorkflows } =
957957
useDeployedWorkflows(workspaceId)
958-
const createServerMutation = useCreateWorkflowMcpServer()
959958
const deleteServerMutation = useDeleteWorkflowMcpServer()
960959

961960
const [searchTerm, setSearchTerm] = useState('')
962961
const [showAddModal, setShowAddModal] = useState(false)
963-
const [formData, setFormData] = useState({ name: '', description: '', isPublic: false })
964-
const [selectedWorkflowIds, setSelectedWorkflowIds] = useState<string[]>([])
965962
const [selectedServerId, setSelectedServerId] = useState<string | null>(null)
966963
const [serverToDelete, setServerToDelete] = useState<WorkflowMcpServer | null>(null)
967964
const [deletingServers, setDeletingServers] = useState<Set<string>>(() => new Set())
@@ -979,29 +976,6 @@ export function WorkflowMcpServers() {
979976
}))
980977
}, [deployedWorkflows])
981978

982-
const resetForm = useCallback(() => {
983-
setFormData({ name: '', description: '', isPublic: false })
984-
setSelectedWorkflowIds([])
985-
setShowAddModal(false)
986-
}, [])
987-
988-
const handleCreateServer = async () => {
989-
if (!formData.name.trim()) return
990-
991-
try {
992-
await createServerMutation.mutateAsync({
993-
workspaceId,
994-
name: formData.name.trim(),
995-
description: formData.description.trim() || undefined,
996-
isPublic: formData.isPublic,
997-
workflowIds: selectedWorkflowIds.length > 0 ? selectedWorkflowIds : undefined,
998-
})
999-
resetForm()
1000-
} catch (err) {
1001-
logger.error('Failed to create server:', err)
1002-
}
1003-
}
1004-
1005979
const handleDeleteServer = async () => {
1006980
if (!serverToDelete) return
1007981

@@ -1026,7 +1000,6 @@ export function WorkflowMcpServers() {
10261000

10271001
const hasServers = servers.length > 0
10281002
const showNoResults = searchTerm.trim() && filteredServers.length === 0 && hasServers
1029-
const isFormValid = formData.name.trim().length > 0
10301003

10311004
if (selectedServerId) {
10321005
return (
@@ -1123,86 +1096,13 @@ export function WorkflowMcpServers() {
11231096
</div>
11241097
</div>
11251098

1126-
<Modal open={showAddModal} onOpenChange={(open) => !open && resetForm()}>
1127-
<ModalContent>
1128-
<ModalHeader>Add New MCP Server</ModalHeader>
1129-
<ModalBody>
1130-
<div className='flex flex-col gap-3'>
1131-
<FormField label='Server Name'>
1132-
<EmcnInput
1133-
placeholder='e.g., My MCP Server'
1134-
value={formData.name}
1135-
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
1136-
className='h-9'
1137-
/>
1138-
</FormField>
1139-
1140-
<FormField label='Description'>
1141-
<Textarea
1142-
placeholder='Describe what this MCP server does (optional)'
1143-
value={formData.description}
1144-
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
1145-
className='min-h-[60px] resize-none'
1146-
/>
1147-
</FormField>
1148-
1149-
<FormField label='Workflows'>
1150-
<Combobox
1151-
options={workflowOptions}
1152-
multiSelect
1153-
multiSelectValues={selectedWorkflowIds}
1154-
onMultiSelectChange={setSelectedWorkflowIds}
1155-
placeholder='Select workflows...'
1156-
searchable
1157-
searchPlaceholder='Search workflows...'
1158-
isLoading={isLoadingWorkflows}
1159-
disabled={createServerMutation.isPending}
1160-
emptyMessage='No deployed workflows available'
1161-
overlayContent={
1162-
selectedWorkflowIds.length > 0 ? (
1163-
<span className='text-[var(--text-primary)]'>
1164-
{selectedWorkflowIds.length} workflow
1165-
{selectedWorkflowIds.length !== 1 ? 's' : ''} selected
1166-
</span>
1167-
) : undefined
1168-
}
1169-
/>
1170-
</FormField>
1171-
1172-
<FormField label='Access'>
1173-
<div className='flex items-center gap-3'>
1174-
<ButtonGroup
1175-
value={formData.isPublic ? 'public' : 'private'}
1176-
onValueChange={(value) =>
1177-
setFormData({ ...formData, isPublic: value === 'public' })
1178-
}
1179-
>
1180-
<ButtonGroupItem value='private'>API Key</ButtonGroupItem>
1181-
<ButtonGroupItem value='public'>Public</ButtonGroupItem>
1182-
</ButtonGroup>
1183-
{formData.isPublic && (
1184-
<span className='text-[var(--text-muted)] text-xs'>
1185-
No authentication required
1186-
</span>
1187-
)}
1188-
</div>
1189-
</FormField>
1190-
</div>
1191-
</ModalBody>
1192-
<ModalFooter>
1193-
<Button variant='default' onClick={resetForm}>
1194-
Cancel
1195-
</Button>
1196-
<Button
1197-
onClick={handleCreateServer}
1198-
disabled={!isFormValid || createServerMutation.isPending}
1199-
variant='primary'
1200-
>
1201-
{createServerMutation.isPending ? 'Adding...' : 'Add Server'}
1202-
</Button>
1203-
</ModalFooter>
1204-
</ModalContent>
1205-
</Modal>
1099+
<CreateWorkflowMcpServerModal
1100+
open={showAddModal}
1101+
onOpenChange={setShowAddModal}
1102+
workspaceId={workspaceId}
1103+
workflowOptions={workflowOptions}
1104+
isLoadingWorkflows={isLoadingWorkflows}
1105+
/>
12061106

12071107
<Modal open={!!serverToDelete} onOpenChange={(open) => !open && setServerToDelete(null)}>
12081108
<ModalContent size='sm'>

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

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ import { generateToolInputSchema, sanitizeToolName } from '@/lib/mcp/workflow-to
1717
import { normalizeInputFormatValue } from '@/lib/workflows/input-format'
1818
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
1919
import type { InputFormatField } from '@/lib/workflows/types'
20-
import { McpServerFormModal } from '@/app/workspace/[workspaceId]/settings/components/mcp/components/mcp-server-form-modal/mcp-server-form-modal'
21-
import { useAllowedMcpDomains, useCreateMcpServer } from '@/hooks/queries/mcp'
20+
import { CreateWorkflowMcpServerModal } from '@/app/workspace/[workspaceId]/settings/components/workflow-mcp-servers/create-workflow-mcp-server-modal'
2221
import {
2322
useAddWorkflowMcpTool,
2423
useDeleteWorkflowMcpTool,
@@ -28,7 +27,6 @@ import {
2827
type WorkflowMcpServer,
2928
type WorkflowMcpTool,
3029
} from '@/hooks/queries/workflow-mcp-servers'
31-
import { useAvailableEnvVarKeys } from '@/hooks/use-available-env-vars'
3230
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
3331
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
3432

@@ -102,11 +100,7 @@ export function McpDeploy({
102100
}: McpDeployProps) {
103101
const params = useParams()
104102
const workspaceId = params.workspaceId as string
105-
const [showMcpModal, setShowMcpModal] = useState(false)
106-
107-
const createMcpServer = useCreateMcpServer()
108-
const { data: allowedMcpDomains = null } = useAllowedMcpDomains()
109-
const availableEnvVars = useAvailableEnvVarKeys(workspaceId)
103+
const [showCreateModal, setShowCreateModal] = useState(false)
110104

111105
const { data: servers = [], isLoading: isLoadingServers } = useWorkflowMcpServers(workspaceId)
112106
const addToolMutation = useAddWorkflowMcpTool()
@@ -473,22 +467,16 @@ export function McpDeploy({
473467
<>
474468
<div className='flex h-full flex-col items-center justify-center gap-3'>
475469
<p className='text-[13px] text-[var(--text-muted)]'>
476-
Create an MCP Server in Settings → MCP Servers first.
470+
Create an MCP Server to expose your workflows as tools.
477471
</p>
478-
<Button variant='tertiary' onClick={() => setShowMcpModal(true)}>
472+
<Button variant='tertiary' onClick={() => setShowCreateModal(true)}>
479473
Create MCP Server
480474
</Button>
481475
</div>
482-
<McpServerFormModal
483-
open={showMcpModal}
484-
onOpenChange={setShowMcpModal}
485-
mode='add'
486-
onSubmit={async (config) => {
487-
await createMcpServer.mutateAsync({ workspaceId, config: { ...config, enabled: true } })
488-
}}
476+
<CreateWorkflowMcpServerModal
477+
open={showCreateModal}
478+
onOpenChange={setShowCreateModal}
489479
workspaceId={workspaceId}
490-
availableEnvVars={availableEnvVars}
491-
allowedMcpDomains={allowedMcpDomains}
492480
/>
493481
</>
494482
)

0 commit comments

Comments
 (0)