Skip to content

Commit 7640af0

Browse files
authored
improvement(mothership): add workflow lint for custom tool/skills/mcp tool additions to agent block (#5199)
* improvement(workflow-linter): added custom tool validation to workflow linter * fix(comments): address pr comments * improvement(validation): ensure type is known
1 parent a68d38a commit 7640af0

4 files changed

Lines changed: 657 additions & 10 deletions

File tree

apps/sim/lib/copilot/tools/server/workflow/edit-workflow/index.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import {
4444
} from './lint'
4545
import { type EditWorkflowParams, isDeferredSkippedItem, type ValidationError } from './types'
4646
import {
47+
collectUnresolvedAgentToolReferences,
4748
collectUnresolvedReferences,
4849
preValidateCredentialInputs,
4950
UNRESOLVABLE_AT_LINT_NOTE,
@@ -190,6 +191,31 @@ export const editWorkflowServerTool: BaseServerTool<EditWorkflowParams, unknown>
190191
error: toError(error).message,
191192
})
192193
}
194+
195+
// Resolve agent-block tool/skill references (custom tools, MCP servers,
196+
// skills). A well-shaped entry whose id does not resolve is dropped at
197+
// runtime, so the agent silently loses the tool/skill - surface it through
198+
// the same lint + input-validation channels as credential/resource refs.
199+
try {
200+
const toolReferences = await collectUnresolvedAgentToolReferences(modifiedWorkflowState, {
201+
userId: context.userId,
202+
workspaceId,
203+
})
204+
unresolvedReferences.push(...toolReferences)
205+
validationErrors.push(
206+
...toolReferences.map((ref) => ({
207+
blockId: ref.blockId,
208+
blockType: ref.blockType ?? 'agent',
209+
field: ref.field,
210+
value: ref.value,
211+
error: ref.reason,
212+
}))
213+
)
214+
} catch (error) {
215+
logger.warn('Agent tool/skill reference validation failed', {
216+
error: toError(error).message,
217+
})
218+
}
193219
}
194220

195221
// Validate the workflow state

apps/sim/lib/copilot/tools/server/workflow/edit-workflow/lint.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,11 @@ export interface WorkflowLintFieldIssue extends WorkflowLintBlockRef {
6565
inactiveModeValues: InactiveModeValue[]
6666
}
6767

68-
/** Tier-2 (async, DB) credential/resource reference that does not resolve to an accessible entity. */
68+
/** Tier-2 (async, DB) reference that does not resolve to an accessible entity. */
6969
export interface WorkflowLintUnresolvedReference extends WorkflowLintBlockRef {
7070
field: string
7171
value: string | string[]
72-
kind: 'credential' | 'resource'
72+
kind: 'credential' | 'resource' | 'custom-tool' | 'mcp-tool' | 'skill'
7373
reason: string
7474
}
7575

@@ -351,9 +351,23 @@ export function formatWorkflowLintMessage(lint: WorkflowLintIssueView) {
351351
}
352352

353353
const unresolved = lint.unresolvedReferences ?? []
354-
if (unresolved.length > 0) {
354+
const credResourceRefs = unresolved.filter(
355+
(ref) => ref.kind === 'credential' || ref.kind === 'resource'
356+
)
357+
if (credResourceRefs.length > 0) {
358+
parts.push(
359+
`Credential/resource references that do not resolve: ${credResourceRefs
360+
.map((ref) => `"${ref.blockName || ref.blockId}".${ref.field} (${ref.reason})`)
361+
.join(', ')}`
362+
)
363+
}
364+
365+
const toolSkillRefs = unresolved.filter(
366+
(ref) => ref.kind === 'custom-tool' || ref.kind === 'mcp-tool' || ref.kind === 'skill'
367+
)
368+
if (toolSkillRefs.length > 0) {
355369
parts.push(
356-
`Credential/resource references that do not resolve: ${unresolved
370+
`Agent tool/skill references that do not resolve (they will not attach at runtime): ${toolSkillRefs
357371
.map((ref) => `"${ref.blockName || ref.blockId}".${ref.field} (${ref.reason})`)
358372
.join(', ')}`
359373
)

0 commit comments

Comments
 (0)