Skip to content

Commit 15ead0e

Browse files
committed
Add deploy mcp tools
1 parent 02d4f68 commit 15ead0e

File tree

9 files changed

+1123
-132
lines changed

9 files changed

+1123
-132
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/tool-call/tool-call.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -906,7 +906,8 @@ function isSpecialToolCall(toolCall: CopilotToolCall): boolean {
906906
'edit_workflow',
907907
'build_workflow',
908908
'run_workflow',
909-
'deploy_workflow',
909+
'deploy_api',
910+
'deploy_chat',
910911
]
911912

912913
return workflowOperationTools.includes(toolCall.name)

apps/sim/lib/copilot/registry.ts

Lines changed: 141 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ export const ToolIds = z.enum([
2929
'set_global_workflow_variables',
3030
'oauth_request_access',
3131
'get_trigger_blocks',
32-
'deploy_workflow',
32+
'deploy_api',
33+
'deploy_chat',
34+
'deploy_mcp',
35+
'list_workspace_mcp_servers',
36+
'create_workspace_mcp_server',
3337
'check_deployment_status',
3438
'navigate_ui',
3539
'knowledge_base',
@@ -78,9 +82,66 @@ export const ToolArgSchemas = {
7882
providerName: z.string(),
7983
}),
8084

81-
deploy_workflow: z.object({
85+
deploy_api: z.object({
8286
action: z.enum(['deploy', 'undeploy']).optional().default('deploy'),
83-
deployType: z.enum(['api', 'chat']).optional().default('api'),
87+
}),
88+
89+
deploy_chat: z.object({
90+
action: z.enum(['deploy', 'undeploy']).optional().default('deploy'),
91+
identifier: z
92+
.string()
93+
.optional()
94+
.describe('URL slug for the chat (lowercase letters, numbers, hyphens only)'),
95+
title: z.string().optional().describe('Display title for the chat interface'),
96+
description: z.string().optional().describe('Optional description for the chat'),
97+
authType: z
98+
.enum(['public', 'password', 'email', 'sso'])
99+
.optional()
100+
.default('public')
101+
.describe('Authentication type: public, password, email, or sso'),
102+
password: z.string().optional().describe('Password for password-protected chats'),
103+
allowedEmails: z
104+
.array(z.string())
105+
.optional()
106+
.describe('List of allowed emails/domains for email or SSO auth'),
107+
welcomeMessage: z.string().optional().describe('Welcome message shown to users'),
108+
outputConfigs: z
109+
.array(
110+
z.object({
111+
blockId: z.string().describe('The block UUID'),
112+
path: z.string().describe('The output path (e.g. "response", "response.content")'),
113+
})
114+
)
115+
.optional()
116+
.describe('Output configurations specifying which block outputs to display in chat'),
117+
}),
118+
119+
deploy_mcp: z.object({
120+
serverId: z
121+
.string()
122+
.describe('The MCP server ID to deploy to (get from list_workspace_mcp_servers)'),
123+
workflowId: z.string().optional().describe('Optional workflow ID (defaults to active workflow)'),
124+
toolName: z.string().optional().describe('Custom tool name (defaults to workflow name)'),
125+
toolDescription: z.string().optional().describe('Custom tool description'),
126+
parameterDescriptions: z
127+
.array(
128+
z.object({
129+
name: z.string().describe('Parameter name'),
130+
description: z.string().describe('Parameter description'),
131+
})
132+
)
133+
.optional()
134+
.describe('Parameter descriptions to include in the MCP tool schema'),
135+
}),
136+
137+
list_workspace_mcp_servers: z.object({
138+
workspaceId: z.string().optional().describe('Optional workspace ID (defaults to current)'),
139+
}),
140+
141+
create_workspace_mcp_server: z.object({
142+
name: z.string().describe('Name of the MCP server'),
143+
description: z.string().optional().describe('Optional description for the server'),
144+
workspaceId: z.string().optional().describe('Optional workspace ID (defaults to current)'),
84145
}),
85146

86147
check_deployment_status: z.object({
@@ -359,7 +420,17 @@ export const ToolSSESchemas = {
359420
reason: toolCallSSEFor('reason', ToolArgSchemas.reason),
360421
// New
361422
oauth_request_access: toolCallSSEFor('oauth_request_access', ToolArgSchemas.oauth_request_access),
362-
deploy_workflow: toolCallSSEFor('deploy_workflow', ToolArgSchemas.deploy_workflow),
423+
deploy_api: toolCallSSEFor('deploy_api', ToolArgSchemas.deploy_api),
424+
deploy_chat: toolCallSSEFor('deploy_chat', ToolArgSchemas.deploy_chat),
425+
deploy_mcp: toolCallSSEFor('deploy_mcp', ToolArgSchemas.deploy_mcp),
426+
list_workspace_mcp_servers: toolCallSSEFor(
427+
'list_workspace_mcp_servers',
428+
ToolArgSchemas.list_workspace_mcp_servers
429+
),
430+
create_workspace_mcp_server: toolCallSSEFor(
431+
'create_workspace_mcp_server',
432+
ToolArgSchemas.create_workspace_mcp_server
433+
),
363434
check_deployment_status: toolCallSSEFor(
364435
'check_deployment_status',
365436
ToolArgSchemas.check_deployment_status
@@ -581,24 +652,83 @@ export const ToolResultSchemas = {
581652
}),
582653
}),
583654
reason: z.object({ reasoning: z.string() }),
584-
deploy_workflow: z.object({
655+
deploy_api: z.object({
585656
action: z.enum(['deploy', 'undeploy']).optional(),
586-
deployType: z.enum(['api', 'chat']).optional(),
587657
isDeployed: z.boolean().optional(),
588658
deployedAt: z.string().optional(),
589659
needsApiKey: z.boolean().optional(),
590660
message: z.string().optional(),
591661
endpoint: z.string().optional(),
592662
curlCommand: z.string().optional(),
593663
apiKeyPlaceholder: z.string().optional(),
594-
openedModal: z.boolean().optional(),
664+
}),
665+
deploy_chat: z.object({
666+
success: z.boolean(),
667+
action: z.enum(['deploy', 'undeploy']).optional(),
668+
isDeployed: z.boolean().optional(),
669+
chatId: z.string().optional(),
670+
chatUrl: z.string().optional(),
671+
identifier: z.string().optional(),
672+
title: z.string().optional(),
673+
authType: z.enum(['public', 'password', 'email', 'sso']).optional(),
674+
message: z.string().optional(),
675+
error: z.string().optional(),
676+
errorCode: z
677+
.enum(['IDENTIFIER_TAKEN', 'VALIDATION_ERROR', 'AUTH_ERROR', 'SERVER_ERROR'])
678+
.optional(),
679+
}),
680+
deploy_mcp: z.object({
681+
success: z.boolean(),
682+
toolId: z.string().optional(),
683+
toolName: z.string().optional(),
684+
toolDescription: z.string().optional(),
685+
serverId: z.string().optional(),
686+
error: z.string().optional(),
687+
}),
688+
list_workspace_mcp_servers: z.object({
689+
servers: z.array(
690+
z.object({
691+
id: z.string(),
692+
name: z.string(),
693+
description: z.string().nullable(),
694+
toolCount: z.number(),
695+
toolNames: z.array(z.string()),
696+
})
697+
),
698+
count: z.number(),
699+
}),
700+
create_workspace_mcp_server: z.object({
701+
success: z.boolean(),
702+
serverId: z.string().optional(),
703+
serverName: z.string().optional(),
704+
description: z.string().nullable().optional(),
705+
error: z.string().optional(),
595706
}),
596707
check_deployment_status: z.object({
597708
isDeployed: z.boolean(),
598-
deploymentTypes: z.array(z.string()),
599-
apiDeployed: z.boolean(),
600-
chatDeployed: z.boolean(),
601-
deployedAt: z.string().nullable(),
709+
deploymentTypes: z.array(z.enum(['api', 'chat', 'mcp'])),
710+
api: z.object({
711+
isDeployed: z.boolean(),
712+
deployedAt: z.string().nullable(),
713+
endpoint: z.string().nullable(),
714+
}),
715+
chat: z.object({
716+
isDeployed: z.boolean(),
717+
chatId: z.string().nullable(),
718+
identifier: z.string().nullable(),
719+
chatUrl: z.string().nullable(),
720+
}),
721+
mcp: z.object({
722+
isDeployed: z.boolean(),
723+
servers: z.array(
724+
z.object({
725+
serverId: z.string(),
726+
serverName: z.string(),
727+
toolName: z.string(),
728+
toolDescription: z.string().nullable(),
729+
})
730+
),
731+
}),
602732
}),
603733
navigate_ui: z.object({
604734
destination: z.enum(['workflow', 'logs', 'templates', 'vector_db', 'settings']),

apps/sim/lib/copilot/tools/client/workflow/check-deployment-status.ts

Lines changed: 110 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,29 @@ interface CheckDeploymentStatusArgs {
1111
workflowId?: string
1212
}
1313

14+
interface ApiDeploymentDetails {
15+
isDeployed: boolean
16+
deployedAt: string | null
17+
endpoint: string | null
18+
}
19+
20+
interface ChatDeploymentDetails {
21+
isDeployed: boolean
22+
chatId: string | null
23+
identifier: string | null
24+
chatUrl: string | null
25+
}
26+
27+
interface McpDeploymentDetails {
28+
isDeployed: boolean
29+
servers: Array<{
30+
serverId: string
31+
serverName: string
32+
toolName: string
33+
toolDescription: string | null
34+
}>
35+
}
36+
1437
export class CheckDeploymentStatusClientTool extends BaseClientTool {
1538
static readonly id = 'check_deployment_status'
1639

@@ -45,52 +68,116 @@ export class CheckDeploymentStatusClientTool extends BaseClientTool {
4568
try {
4669
this.setState(ClientToolCallState.executing)
4770

48-
const { activeWorkflowId } = useWorkflowRegistry.getState()
71+
const { activeWorkflowId, workflows } = useWorkflowRegistry.getState()
4972
const workflowId = args?.workflowId || activeWorkflowId
5073

5174
if (!workflowId) {
5275
throw new Error('No workflow ID provided')
5376
}
5477

55-
// Fetch deployment status from API
56-
const [apiDeployRes, chatDeployRes] = await Promise.all([
78+
const workflow = workflows[workflowId]
79+
const workspaceId = workflow?.workspaceId
80+
81+
// Fetch deployment status from all sources
82+
const [apiDeployRes, chatDeployRes, mcpServersRes] = await Promise.all([
5783
fetch(`/api/workflows/${workflowId}/deploy`),
5884
fetch(`/api/workflows/${workflowId}/chat/status`),
85+
workspaceId ? fetch(`/api/mcp/workflow-servers?workspaceId=${workspaceId}`) : null,
5986
])
6087

6188
const apiDeploy = apiDeployRes.ok ? await apiDeployRes.json() : null
6289
const chatDeploy = chatDeployRes.ok ? await chatDeployRes.json() : null
90+
const mcpServers = mcpServersRes?.ok ? await mcpServersRes.json() : null
6391

92+
// API deployment details
6493
const isApiDeployed = apiDeploy?.isDeployed || false
94+
const appUrl = typeof window !== 'undefined' ? window.location.origin : ''
95+
const apiDetails: ApiDeploymentDetails = {
96+
isDeployed: isApiDeployed,
97+
deployedAt: apiDeploy?.deployedAt || null,
98+
endpoint: isApiDeployed ? `${appUrl}/api/workflows/${workflowId}/execute` : null,
99+
}
100+
101+
// Chat deployment details
65102
const isChatDeployed = !!(chatDeploy?.isDeployed && chatDeploy?.deployment)
103+
const chatDetails: ChatDeploymentDetails = {
104+
isDeployed: isChatDeployed,
105+
chatId: chatDeploy?.deployment?.id || null,
106+
identifier: chatDeploy?.deployment?.identifier || null,
107+
chatUrl: isChatDeployed ? `${appUrl}/chat/${chatDeploy?.deployment?.identifier}` : null,
108+
}
66109

67-
const deploymentTypes: string[] = []
110+
// MCP deployment details - find servers that have this workflow as a tool
111+
const mcpServerList = mcpServers?.data?.servers || []
112+
const mcpToolDeployments: McpDeploymentDetails['servers'] = []
68113

69-
if (isApiDeployed) {
70-
// Default to sync API, could be extended to detect streaming/async
71-
deploymentTypes.push('api')
114+
for (const server of mcpServerList) {
115+
// Check if this workflow is deployed as a tool on this server
116+
if (server.toolNames && Array.isArray(server.toolNames)) {
117+
// We need to fetch the actual tools to check if this workflow is there
118+
try {
119+
const toolsRes = await fetch(
120+
`/api/mcp/workflow-servers/${server.id}/tools?workspaceId=${workspaceId}`
121+
)
122+
if (toolsRes.ok) {
123+
const toolsData = await toolsRes.json()
124+
const tools = toolsData.data?.tools || []
125+
for (const tool of tools) {
126+
if (tool.workflowId === workflowId) {
127+
mcpToolDeployments.push({
128+
serverId: server.id,
129+
serverName: server.name,
130+
toolName: tool.toolName,
131+
toolDescription: tool.toolDescription,
132+
})
133+
}
134+
}
135+
}
136+
} catch {
137+
// Skip this server if we can't fetch tools
138+
}
139+
}
72140
}
73141

74-
if (isChatDeployed) {
75-
deploymentTypes.push('chat')
142+
const isMcpDeployed = mcpToolDeployments.length > 0
143+
const mcpDetails: McpDeploymentDetails = {
144+
isDeployed: isMcpDeployed,
145+
servers: mcpToolDeployments,
76146
}
77147

78-
const isDeployed = isApiDeployed || isChatDeployed
148+
// Build deployment types list
149+
const deploymentTypes: string[] = []
150+
if (isApiDeployed) deploymentTypes.push('api')
151+
if (isChatDeployed) deploymentTypes.push('chat')
152+
if (isMcpDeployed) deploymentTypes.push('mcp')
79153

80-
this.setState(ClientToolCallState.success)
81-
await this.markToolComplete(
82-
200,
83-
isDeployed
84-
? `Workflow is deployed as: ${deploymentTypes.join(', ')}`
85-
: 'Workflow is not deployed',
86-
{
87-
isDeployed,
88-
deploymentTypes,
89-
apiDeployed: isApiDeployed,
90-
chatDeployed: isChatDeployed,
91-
deployedAt: apiDeploy?.deployedAt || null,
154+
const isDeployed = isApiDeployed || isChatDeployed || isMcpDeployed
155+
156+
// Build summary message
157+
let message = ''
158+
if (!isDeployed) {
159+
message = 'Workflow is not deployed'
160+
} else {
161+
const parts: string[] = []
162+
if (isApiDeployed) parts.push('API')
163+
if (isChatDeployed) parts.push(`Chat (${chatDetails.identifier})`)
164+
if (isMcpDeployed) {
165+
const serverNames = mcpToolDeployments.map((d) => d.serverName).join(', ')
166+
parts.push(`MCP (${serverNames})`)
92167
}
93-
)
168+
message = `Workflow is deployed as: ${parts.join(', ')}`
169+
}
170+
171+
this.setState(ClientToolCallState.success)
172+
await this.markToolComplete(200, message, {
173+
isDeployed,
174+
deploymentTypes,
175+
api: apiDetails,
176+
chat: chatDetails,
177+
mcp: mcpDetails,
178+
})
179+
180+
logger.info('Checked deployment status', { isDeployed, deploymentTypes })
94181
} catch (e: any) {
95182
logger.error('Check deployment status failed', { message: e?.message })
96183
this.setState(ClientToolCallState.error)

0 commit comments

Comments
 (0)