Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
4bcade3
feat(agent-toolkit): add update_workflow_builder tool
amitrossmonday May 25, 2026
bd98773
feat(agent-toolkit): enrich update_workflow_builder description and p…
amitrossmonday May 26, 2026
591bb8d
fix(agent-toolkit): use correct workflowObjectId terminology in descr…
amitrossmonday May 26, 2026
01cd2a8
refactor(agent-toolkit): import WORKFLOW_BUILDER_AGENT_URL from const…
amitrossmonday May 26, 2026
759df2a
fix(agent-toolkit): replace 'entity ID' with 'object ID' in update_wo…
amitrossmonday May 26, 2026
e03c401
feat(agent-toolkit): rename update_workflow_builder to update_workflo…
amitrossmonday May 27, 2026
6e5d839
fix(agent-toolkit): update prompt example in update_workflow to a sup…
amitrossmonday May 27, 2026
626a87a
fix(agent-toolkit): use generic board ID in update_workflow prompt ex…
amitrossmonday May 27, 2026
b8f60ac
fix(agent-toolkit): use natural language board name in update_workflo…
amitrossmonday May 27, 2026
adf9810
chore(agent-toolkit): remove duplicate comment in platform-api-tools …
amitrossmonday May 27, 2026
89803d7
chore(agent-toolkit): remove incomplete workflow-builder-tools barrel
amitrossmonday May 27, 2026
23aa4db
fix(agent-toolkit): clarify workflowObjectId/workflowDraftId lifecycl…
amitrossmonday May 27, 2026
5a6c51e
fix(agent-toolkit): simplify workflowDraftId lifecycle note in update…
amitrossmonday May 27, 2026
645cd47
fix(agent-toolkit): clarify workflowDraftId should be read from lates…
amitrossmonday May 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/agent-toolkit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mondaydotcomorg/agent-toolkit",
"version": "5.16.0",
"version": "5.17.0",
"description": "monday.com agent toolkit",
"exports": {
"./mcp": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import { ListAutomationsTool } from './workflows-tools/list-workflows/list-workf
import { ManageWorkflowsTool } from './workflows-tools/manage-workflows/manage-workflows-tool';
import { CreateAutomationTool } from './workflows-tools/create-automation/create-automation-tool';
import { CreateWorkflowBuilderTool } from './workflow-builder-tools/create-workflow/create-workflow-tool';
import { UpdateWorkflowTool } from './workflow-builder-tools/update-workflow/update-workflow-tool';

export const allGraphqlApiTools: BaseMondayApiToolConstructor[] = [
DeleteItemTool,
Expand Down Expand Up @@ -156,6 +157,8 @@ export const allGraphqlApiTools: BaseMondayApiToolConstructor[] = [
CreateAutomationTool as unknown as BaseMondayApiToolConstructor,
// Workflow Builder Tools
CreateWorkflowBuilderTool,
// Cast: ctor signature (api, apiToken, context?) doesn't match BaseMondayApiToolConstructor.
UpdateWorkflowTool as unknown as BaseMondayApiToolConstructor,
];

export * from './all-monday-api-tool';
Expand Down Expand Up @@ -230,6 +233,7 @@ export * from './agents-tools';
export * from './workflows-tools';
// Workflow Builder Tools
export * from './workflow-builder-tools/create-workflow/create-workflow-tool';
export * from './workflow-builder-tools/update-workflow/update-workflow-tool';
// Dashboard Tools
export * from './dashboard-tools';
// Monday Dev Tools
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const WORKFLOW_BUILDER_AGENT_URL = 'https://api.monday.com/platform-ai-gateway/agents/workflow-builder';
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { MondayAgentToolkit } from 'src/mcp/toolkit';
import { callToolByNameRawAsync, createMockApiClient, parseToolResult } from '../../test-utils/mock-api-client';
import { WORKFLOW_BUILDER_AGENT_URL } from '../constants';

function mockFetchResponse({
ok = true,
status = 200,
body = {},
}: {
ok?: boolean;
status?: number;
body?: unknown;
} = {}): Response {
return {
ok,
status,
json: async () => body,
text: async () => (typeof body === 'string' ? body : JSON.stringify(body)),
} as unknown as Response;
}

describe('UpdateWorkflowTool', () => {
let mocks: ReturnType<typeof createMockApiClient>;
let fetchSpy: jest.SpyInstance;

beforeEach(() => {
mocks = createMockApiClient();
jest.spyOn(MondayAgentToolkit.prototype as any, 'createApiClient').mockReturnValue(mocks.mockApiClient);
fetchSpy = jest.spyOn(global, 'fetch');
});

afterEach(() => {
fetchSpy.mockRestore();
});

it('posts to the workflow-builder agent URL with correct body and Authorization header', async () => {
fetchSpy.mockResolvedValue(
mockFetchResponse({
body: { workflowObjectId: 5002722216, workflowDraftId: 43023, result: 'Added an email step' },
}),
);

await callToolByNameRawAsync('update_workflow', {
workflowObjectId: 5002722216,
workflowDraftId: 43023,
prompt: 'add a step that sends an email when an item is created',
});

expect(fetchSpy).toHaveBeenCalledTimes(1);
const [url, init] = fetchSpy.mock.calls[0];
expect(url).toBe(WORKFLOW_BUILDER_AGENT_URL);
expect(init.method).toBe('POST');
expect(init.headers).toMatchObject({
Authorization: 'test-token',
'Content-Type': 'application/json',
});
expect(JSON.parse(init.body)).toEqual({
workflowObjectId: 5002722216,
workflowDraftId: 43023,
prompt: 'add a step that sends an email when an item is created',
});
});

it('passes through the agent result on success', async () => {
fetchSpy.mockResolvedValue(
mockFetchResponse({
body: {
workflowObjectId: 5002722216,
workflowDraftId: 43023,
result: 'Added an email notification step after the trigger',
},
}),
);

const result = await callToolByNameRawAsync('update_workflow', {
workflowObjectId: 5002722216,
workflowDraftId: 43023,
prompt: 'add an email step',
});
const parsed = parseToolResult(result);

expect(parsed.workflowObjectId).toBe(5002722216);
expect(parsed.workflowDraftId).toBe(43023);
expect(parsed.result).toBe('Added an email notification step after the trigger');
});

it('rejects missing workflowObjectId before making any HTTP call', async () => {
const result = await callToolByNameRawAsync('update_workflow', {
workflowDraftId: 43023,
prompt: 'add a step',
});

expect(result.content[0].text).toContain('workflowObjectId');
expect(fetchSpy).not.toHaveBeenCalled();
});

it('rejects missing workflowDraftId before making any HTTP call', async () => {
const result = await callToolByNameRawAsync('update_workflow', {
workflowObjectId: 5002722216,
prompt: 'add a step',
});

expect(result.content[0].text).toContain('workflowDraftId');
expect(fetchSpy).not.toHaveBeenCalled();
});

it('rejects empty prompt before making any HTTP call', async () => {
const result = await callToolByNameRawAsync('update_workflow', {
workflowObjectId: 5002722216,
workflowDraftId: 43023,
prompt: ' ',
});

expect(result.content[0].text).toContain('prompt must be a non-empty string');
expect(fetchSpy).not.toHaveBeenCalled();
});

it('surfaces the error envelope on 4xx', async () => {
fetchSpy.mockResolvedValue(
mockFetchResponse({
ok: false,
status: 400,
body: { error: 'Prompt exceeds maximum length of 2000 characters', code: 'PROMPT_TOO_LONG' },
}),
);

const result = await callToolByNameRawAsync('update_workflow', {
workflowObjectId: 5002722216,
workflowDraftId: 43023,
prompt: 'add a step',
});

expect(result.content[0].text).toContain('Failed to update workflow');
expect(result.content[0].text).toContain('HTTP 400');
expect(result.content[0].text).toContain('PROMPT_TOO_LONG');
});

it('wraps network errors with operation context', async () => {
fetchSpy.mockRejectedValue(new Error('network failure'));

const result = await callToolByNameRawAsync('update_workflow', {
workflowObjectId: 5002722216,
workflowDraftId: 43023,
prompt: 'add a step',
});

expect(result.content[0].text).toContain('Failed to update workflow');
expect(result.content[0].text).toContain('network failure');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { ApiClient } from '@mondaydotcomorg/api';
import { z } from 'zod';
import { ToolInputType, ToolOutputType, ToolType } from '../../../../tool';
import { BaseMondayApiTool, MondayApiToolContext, createMondayApiAnnotations } from '../../base-monday-api-tool';
import { rethrowWithContext } from '../../../../../utils';
import { WORKFLOW_BUILDER_AGENT_URL } from '../constants';

const REQUEST_TIMEOUT_MS = 180_000;

export const updateWorkflowToolSchema = {
workflowObjectId: z
.number()
.describe(
'The workflow object ID returned by create_workflow. Identifies the workflow across all its drafts and published versions. Does not change across publishes.',
),
workflowDraftId: z
.number()
.describe(
'The draft version ID to update. Use the workflowDraftId from the previous create_workflow or update_workflow response — the agent may return a new draft ID, so always read it from the latest response rather than reusing an earlier value.',
),
prompt: z
.string()
.trim()
.min(1, 'prompt must be a non-empty string')
.max(2000, 'prompt must not exceed 2000 characters')
.describe(
'Natural-language description of the changes to make. Describe what steps to add, remove, or modify in plain English (e.g. "Add a trigger that fires when an item is created on the Marketing board"). The agent interprets this and applies the right structural changes. Maximum 2000 characters.',
),
};

export class UpdateWorkflowTool extends BaseMondayApiTool<typeof updateWorkflowToolSchema> {
name = 'update_workflow';
type = ToolType.WRITE;
annotations = createMondayApiAnnotations({
title: 'Update Workflow',
readOnlyHint: false,
destructiveHint: false,
idempotentHint: false,
});

constructor(
api: ApiClient,
private readonly apiToken: string,
context?: MondayApiToolContext,
) {
super(api, context);
}

getDescription(): string {
return `Updates an existing workflow draft using an AI agent.

The agent interprets the prompt and applies structural changes to the workflow — creating, updating, or deleting steps. Pass clear, descriptive instructions and the agent will decide which operations to perform, then return a summary of what it did.

Use this after create_workflow to build out the workflow step by step. You can call it multiple times on the same draft to iteratively refine the workflow.

Parameters:
- workflowObjectId and workflowDraftId: both returned by create_workflow — they identify which draft to update.
- prompt: describe what you want to change in plain English (e.g. "Add a trigger that fires when an item is created on the Marketing board"). Maximum 2000 characters.

Returns:
- workflowObjectId: the workflow object ID (unchanged)
- workflowDraftId: the draft version ID (unchanged)
- result: agent response describing the changes made

Note: the workflow runs only after it is published to live version.
`;
}

getInputSchema() {
return updateWorkflowToolSchema;
}

protected async executeInternal(
input: ToolInputType<typeof updateWorkflowToolSchema>,
): Promise<ToolOutputType<never>> {
try {
const response = await fetch(WORKFLOW_BUILDER_AGENT_URL, {
method: 'POST',
headers: {
Authorization: this.apiToken,
'Content-Type': 'application/json',
},
body: JSON.stringify({
workflowObjectId: input.workflowObjectId,
workflowDraftId: input.workflowDraftId,
prompt: input.prompt,
}),
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
});

if (!response.ok) {
const body = await response.text().catch(() => '');
throw new Error(`workflow-builder responded with HTTP ${response.status}${body ? `: ${body}` : ''}`);
}

const body = (await response.json()) as Record<string, unknown>;
return { content: body };
} catch (error) {
rethrowWithContext(error, 'update workflow');
}
}
}
Loading