Skip to content

Commit dc787fc

Browse files
committed
address comments
1 parent e1780aa commit dc787fc

2 files changed

Lines changed: 114 additions & 19 deletions

File tree

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

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@
44
import { dbChainMock, dbChainMockFns, resetDbChainMock, schemaMock } from '@sim/testing'
55
import { beforeEach, describe, expect, it, vi } from 'vitest'
66

7+
vi.mock('@sim/audit', () => ({
8+
AuditAction: {
9+
MCP_SERVER_UPDATED: 'mcp_server_updated',
10+
MCP_TOOL_UPDATED: 'mcp_tool_updated',
11+
},
12+
AuditResourceType: {
13+
MCP_SERVER: 'mcp_server',
14+
MCP_TOOL: 'mcp_tool',
15+
},
16+
recordAudit: vi.fn(),
17+
}))
718
vi.mock('@sim/db', () => ({
819
...dbChainMock,
920
workflow: schemaMock.workflow,
@@ -28,8 +39,11 @@ vi.mock('@/lib/mcp/workflow-mcp-sync', () => ({
2839
generateParameterSchemaForWorkflow: vi.fn().mockResolvedValue({ type: 'object', properties: {} }),
2940
}))
3041

31-
import { MAX_MCP_TOOLS_PER_SERVER } from '@/lib/mcp/constants'
32-
import { performCreateWorkflowMcpServer } from '@/lib/mcp/orchestration/workflow-mcp-lifecycle'
42+
import { MAX_MCP_PARAMETER_SCHEMA_BYTES, MAX_MCP_TOOLS_PER_SERVER } from '@/lib/mcp/constants'
43+
import {
44+
performCreateWorkflowMcpServer,
45+
performUpdateWorkflowMcpTool,
46+
} from '@/lib/mcp/orchestration/workflow-mcp-lifecycle'
3347
import { hasValidStartBlock } from '@/lib/workflows/triggers/trigger-utils.server'
3448

3549
describe('workflow MCP lifecycle orchestration', () => {
@@ -151,4 +165,39 @@ describe('workflow MCP lifecycle orchestration', () => {
151165
})
152166
expect(dbChainMockFns.insert).not.toHaveBeenCalled()
153167
})
168+
169+
it('allows updating tool metadata when an unchanged stored schema exceeds the new cap', async () => {
170+
dbChainMockFns.limit.mockResolvedValueOnce([{ id: 'server-1' }]).mockResolvedValueOnce([
171+
{
172+
id: 'tool-1',
173+
toolName: 'tool_a',
174+
toolDescription: null,
175+
parameterSchemaBytes: MAX_MCP_PARAMETER_SCHEMA_BYTES + 1,
176+
},
177+
])
178+
dbChainMockFns.returning.mockResolvedValueOnce([
179+
{
180+
id: 'tool-1',
181+
serverId: 'server-1',
182+
toolName: 'tool_a',
183+
toolDescription: 'Updated description',
184+
},
185+
])
186+
187+
const result = await performUpdateWorkflowMcpTool({
188+
workspaceId: 'workspace-1',
189+
userId: 'user-1',
190+
serverId: 'server-1',
191+
toolId: 'tool-1',
192+
toolDescription: 'Updated description',
193+
})
194+
195+
expect(result).toMatchObject({
196+
success: true,
197+
tool: {
198+
toolDescription: 'Updated description',
199+
},
200+
})
201+
expect(dbChainMockFns.update).toHaveBeenCalled()
202+
})
154203
})

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

Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ import { createLogger } from '@sim/logger'
44
import { generateId } from '@sim/utils/id'
55
import { and, asc, eq, inArray, isNull, ne, sql } from 'drizzle-orm'
66
import type { DbOrTx } from '@/lib/db/types'
7-
import { MAX_MCP_SERVERS_PER_WORKFLOW, MAX_MCP_TOOLS_PER_SERVER } from '@/lib/mcp/constants'
7+
import {
8+
MAX_MCP_SERVER_PARAMETER_SCHEMAS_BYTES,
9+
MAX_MCP_SERVER_TOOLS_METADATA_BYTES,
10+
MAX_MCP_SERVERS_PER_WORKFLOW,
11+
MAX_MCP_TOOLS_PER_SERVER,
12+
} from '@/lib/mcp/constants'
813
import { mcpPubSub } from '@/lib/mcp/pubsub'
914
import {
1015
acquireWorkflowMcpServerLock,
@@ -13,9 +18,13 @@ import {
1318
} from '@/lib/mcp/server-locks'
1419
import {
1520
addMcpToolMetadataUsage,
21+
addMcpToolMetadataUsageRow,
22+
createMcpToolMetadataUsageRow,
1623
getMcpServerToolMetadataUsageRows,
1724
getMcpToolDescriptionForStorage,
25+
getMcpToolMetadataSizes,
1826
getMcpToolMetadataUsageFromRows,
27+
type McpToolMetadataUsage,
1928
validateMcpServerToolMetadataBudget,
2029
validateMcpToolMetadataForStorage,
2130
} from '@/lib/mcp/tool-limits'
@@ -181,6 +190,25 @@ async function validateServerToolMetadataBudget(
181190
return validateMcpServerToolMetadataBudget(usage)
182191
}
183192

193+
function validateServerToolMetadataBudgetForUpdate(
194+
currentUsage: McpToolMetadataUsage,
195+
proposedUsage: McpToolMetadataUsage
196+
): string | null {
197+
if (
198+
proposedUsage.schemaBytes > MAX_MCP_SERVER_PARAMETER_SCHEMAS_BYTES &&
199+
proposedUsage.schemaBytes > currentUsage.schemaBytes
200+
) {
201+
return `MCP server tool schemas exceed maximum size of ${MAX_MCP_SERVER_PARAMETER_SCHEMAS_BYTES} bytes`
202+
}
203+
if (
204+
proposedUsage.metadataBytes > MAX_MCP_SERVER_TOOLS_METADATA_BYTES &&
205+
proposedUsage.metadataBytes > currentUsage.metadataBytes
206+
) {
207+
return `MCP server tool metadata exceeds maximum size of ${MAX_MCP_SERVER_TOOLS_METADATA_BYTES} bytes`
208+
}
209+
return null
210+
}
211+
184212
async function prepareWorkflowMcpTool(params: {
185213
workflowRecord: WorkflowMcpToolWorkflowRecord
186214
toolName?: string
@@ -863,7 +891,7 @@ export async function performUpdateWorkflowMcpTool(
863891
id: workflowMcpTool.id,
864892
toolName: workflowMcpTool.toolName,
865893
toolDescription: workflowMcpTool.toolDescription,
866-
parameterSchema: workflowMcpTool.parameterSchema,
894+
parameterSchemaBytes: sql<number>`octet_length(${workflowMcpTool.parameterSchema}::text)`,
867895
})
868896
.from(workflowMcpTool)
869897
.where(
@@ -885,13 +913,13 @@ export async function performUpdateWorkflowMcpTool(
885913
? updateData.toolDescription
886914
: currentTool.toolDescription
887915
const effectiveParameterSchema =
888-
updateData.parameterSchema !== undefined
889-
? updateData.parameterSchema
890-
: currentTool.parameterSchema
916+
updateData.parameterSchema !== undefined ? updateData.parameterSchema : undefined
891917
const metadataLimitError = validateMcpToolMetadataForStorage({
892918
toolName: effectiveToolName,
893919
toolDescription: effectiveToolDescription,
894-
parameterSchema: effectiveParameterSchema,
920+
...(effectiveParameterSchema !== undefined && {
921+
parameterSchema: effectiveParameterSchema,
922+
}),
895923
})
896924
if (metadataLimitError) {
897925
throw new WorkflowMcpExpectedError(metadataLimitError, 'validation')
@@ -919,18 +947,36 @@ export async function performUpdateWorkflowMcpTool(
919947
}
920948
}
921949

922-
const budgetError = await validateServerToolMetadataBudget(
923-
params.serverId,
924-
[
925-
{
926-
toolName: effectiveToolName,
927-
toolDescription: effectiveToolDescription,
928-
parameterSchema: effectiveParameterSchema,
929-
},
930-
],
931-
tx,
932-
params.toolId
950+
const baseUsage = getMcpToolMetadataUsageFromRows(
951+
await getMcpServerToolMetadataUsageRows(tx, params.serverId, params.toolId)
952+
)
953+
const currentUsage = addMcpToolMetadataUsageRow(baseUsage, {
954+
id: currentTool.id,
955+
...getMcpToolMetadataSizes({
956+
toolName: currentTool.toolName,
957+
toolDescription: currentTool.toolDescription,
958+
}),
959+
parameterSchemaBytes: Number(currentTool.parameterSchemaBytes) || 0,
960+
})
961+
const proposedUsage = addMcpToolMetadataUsageRow(
962+
baseUsage,
963+
effectiveParameterSchema !== undefined
964+
? createMcpToolMetadataUsageRow({
965+
id: currentTool.id,
966+
toolName: effectiveToolName,
967+
toolDescription: effectiveToolDescription,
968+
parameterSchema: effectiveParameterSchema,
969+
})
970+
: {
971+
id: currentTool.id,
972+
...getMcpToolMetadataSizes({
973+
toolName: effectiveToolName,
974+
toolDescription: effectiveToolDescription,
975+
}),
976+
parameterSchemaBytes: Number(currentTool.parameterSchemaBytes) || 0,
977+
}
933978
)
979+
const budgetError = validateServerToolMetadataBudgetForUpdate(currentUsage, proposedUsage)
934980
if (budgetError) {
935981
throw new WorkflowMcpExpectedError(budgetError, 'validation')
936982
}

0 commit comments

Comments
 (0)