Skip to content

Commit 4296a64

Browse files
committed
fix(mcp): answer an unpublishable workflow input as a validation failure
1 parent fc88c0c commit 4296a64

2 files changed

Lines changed: 41 additions & 1 deletion

File tree

‎apps/sim/lib/mcp/orchestration/workflow-mcp-lifecycle.test.ts‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ vi.mock('@/lib/mcp/workflow-mcp-sync', () => ({
1111
generateParameterSchemaForWorkflow: vi.fn().mockResolvedValue({ type: 'object', properties: {} }),
1212
}))
1313

14+
import { OrchestrationError } from '@/lib/core/orchestration/types'
1415
import { MAX_MCP_PARAMETER_SCHEMA_BYTES, MAX_MCP_TOOLS_PER_SERVER } from '@/lib/mcp/constants'
1516
import {
1617
performCreateWorkflowMcpServer,
1718
performUpdateWorkflowMcpTool,
1819
} from '@/lib/mcp/orchestration/workflow-mcp-lifecycle'
20+
import { generateParameterSchemaForWorkflow } from '@/lib/mcp/workflow-mcp-sync'
1921
import { hasValidStartBlock } from '@/lib/workflows/triggers/utils.server'
2022

2123
describe('workflow MCP lifecycle orchestration', () => {
@@ -97,6 +99,36 @@ describe('workflow MCP lifecycle orchestration', () => {
9799
expect(dbChainMockFns.insert).not.toHaveBeenCalled()
98100
})
99101

102+
it('answers an unpublishable workflow input as a validation failure naming the field', async () => {
103+
const message =
104+
'Workflow input "payload" has unsupported type "integer". Use one of: string, number.'
105+
vi.mocked(hasValidStartBlock).mockResolvedValueOnce(true)
106+
vi.mocked(generateParameterSchemaForWorkflow).mockRejectedValueOnce(
107+
new OrchestrationError('validation', message)
108+
)
109+
dbChainMockFns.where.mockResolvedValueOnce([
110+
{
111+
id: 'wf-1',
112+
name: 'Workflow',
113+
description: null,
114+
isDeployed: true,
115+
workspaceId: 'workspace-1',
116+
deployedAt: new Date('2026-01-01T00:00:00Z'),
117+
updatedAt: new Date('2026-01-01T00:00:00Z'),
118+
},
119+
])
120+
121+
const result = await performCreateWorkflowMcpServer({
122+
workspaceId: 'workspace-1',
123+
userId: 'user-1',
124+
name: 'Server',
125+
workflowIds: ['wf-1'],
126+
})
127+
128+
expect(result).toEqual({ success: false, error: message, errorCode: 'validation' })
129+
expect(dbChainMockFns.insert).not.toHaveBeenCalled()
130+
})
131+
100132
it('rejects workflow MCP server fan-out above the per-workflow limit', async () => {
101133
vi.mocked(hasValidStartBlock).mockResolvedValueOnce(true)
102134
dbChainMockFns.where.mockResolvedValueOnce([

‎apps/sim/lib/mcp/orchestration/workflow-mcp-lifecycle.ts‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { db, workflow, workflowMcpServer, workflowMcpTool } from '@sim/db'
33
import { createLogger } from '@sim/logger'
44
import { generateId } from '@sim/utils/id'
55
import { and, asc, eq, inArray, isNull, ne, sql } from 'drizzle-orm'
6+
import { OrchestrationError } from '@/lib/core/orchestration/types'
67
import type { DbOrTx } from '@/lib/db/types'
78
import {
89
MAX_MCP_SERVER_PARAMETER_SCHEMAS_BYTES,
@@ -232,7 +233,14 @@ async function prepareWorkflowMcpTool(params: {
232233
const toolDescription =
233234
params.toolDescription !== undefined ? params.toolDescription?.trim() || null : null
234235

235-
const baseSchema = await generateParameterSchemaForWorkflow(workflowRecord.id)
236+
const baseSchema = await generateParameterSchemaForWorkflow(workflowRecord.id).catch(
237+
(error: unknown) => {
238+
if (error instanceof OrchestrationError && error.code === 'validation') {
239+
throw new WorkflowMcpExpectedError(error.message, 'validation')
240+
}
241+
throw error
242+
}
243+
)
236244
const requestedOverrides =
237245
params.parameterDescriptionOverrides ??
238246
(params.legacyParameterSchema

0 commit comments

Comments
 (0)