Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b53b718
docs: add agent tools expansion design spec
NadavAvraham May 13, 2026
e4be478
docs: add agent tools expansion implementation plan
NadavAvraham May 13, 2026
a75204b
refactor(agent-toolkit): export agentFieldsFragment for reuse
NadavAvraham May 13, 2026
ec0d01b
feat(agent-toolkit): add GraphQL operations for 6 new agent tools
NadavAvraham May 13, 2026
6cf4680
chore(agent-toolkit): regenerate dev graphql types for new agent oper…
NadavAvraham May 13, 2026
b924a9b
feat(agent-toolkit): add get_agent_catalog tool
NadavAvraham May 13, 2026
da245b0
fix(agent-toolkit): clean up get_agent_catalog variable assignment an…
NadavAvraham May 13, 2026
ca5cd0d
feat(agent-toolkit): add manage_agent_triggers tool
NadavAvraham May 13, 2026
9897389
test(agent-toolkit): add error propagation tests for add and remove t…
NadavAvraham May 13, 2026
45b5132
feat(agent-toolkit): add manage_agent_skills tool
NadavAvraham May 13, 2026
caec9a8
fix(agent-toolkit): improve manage_agent_skills control flow and add …
NadavAvraham May 13, 2026
8de165d
feat(agent-toolkit): add update_agent tool
NadavAvraham May 13, 2026
5deefcb
fix(agent-toolkit): improve update_agent null safety and test coverage
NadavAvraham May 13, 2026
836573b
feat(agent-toolkit): add manage_agent_state tool
NadavAvraham May 13, 2026
79a00f3
fix(agent-toolkit): improve manage_agent_state control flow, null saf…
NadavAvraham May 13, 2026
4d64cfc
feat(agent-toolkit): add manage_agent_knowledge tool
NadavAvraham May 13, 2026
30363fc
fix(agent-toolkit): fix update validation message and improve manage_…
NadavAvraham May 13, 2026
d23006e
fix(agent-toolkit): remove semicolons from tool descriptions to pass …
NadavAvraham May 13, 2026
551af0e
chore(agent-toolkit): bump version to 5.11.0
NadavAvraham May 13, 2026
4fd9a27
refactor(agent-toolkit): move returns inside try blocks and add skill…
NadavAvraham May 13, 2026
b798bbb
feat(agent-toolkit): add create_agent_skill tool
NadavAvraham May 13, 2026
ad43617
chore(agent-toolkit): bump version to 5.11.1
NadavAvraham May 13, 2026
57346f2
chore(agent-toolkit): revert version to 5.11.0
NadavAvraham May 13, 2026
d0dabc9
refactor(agent-toolkit): fold create_agent_skill into manage_agent_sk…
NadavAvraham May 13, 2026
560ea82
chore: remove design docs from branch (keep locally)
NadavAvraham May 13, 2026
5470cc8
fix(agent-toolkit): address PR review feedback on agent tools
NadavAvraham May 13, 2026
01b4755
fix(agent-toolkit): address PR #343 review feedback on agent tools
NadavAvraham May 14, 2026
68dffd0
refactor(agent-toolkit): split create_agent_skill out of manage_agent…
NadavAvraham May 14, 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
13 changes: 13 additions & 0 deletions packages/agent-toolkit/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## 5.11.0

### Add agent management tools

Six new tools enabling agents to create and manage monday.com platform agents end-to-end:

- `manage_agent_state` — activate, deactivate, or manually trigger a run
- `manage_agent_knowledge` — grant, update, or revoke a board/doc access for an agent
- `manage_agent_skills` — create account-wide skills and attach/detach them to agents
- `manage_agent_triggers` — list, add, or remove triggers; catalog lookup workflow built in
- `get_agent_catalog` — fetch available trigger types and skills from the account catalog
- Extended `update_agent` with `agent_id` naming alignment, enum-safe `agent_model`, empty-input guard, and consistent return shape

## 5.7.1

### form_questions_editor — fix ConditionOperator and remove existing_column_id
Expand Down
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.10.3",
"version": "5.11.0",
Comment thread
NadavAvraham marked this conversation as resolved.


"description": "monday.com agent toolkit",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { MondayAgentToolkit } from 'src/mcp/toolkit';
import { callToolByNameRawAsync, createMockApiClient, parseToolResult } from '../../test-utils/mock-api-client';
import { CreateAgentSkillMutation } from 'src/monday-graphql/generated/graphql.dev/graphql';

const mockSkill = { id: 'skill-123', name: 'Send Slack Message', description: 'Posts to Slack' };

describe('CreateAgentSkillTool', () => {
let mocks: ReturnType<typeof createMockApiClient>;

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

it('should create a skill and return it', async () => {
mocks.setResponseOnce({ create_agent_skill: mockSkill } as CreateAgentSkillMutation);

const result = await callToolByNameRawAsync('create_agent_skill', {
name: 'Send Slack Message',
content: '## Instructions\nPost a message.',
});
const parsed = parseToolResult(result);

expect(parsed.skill.id).toBe('skill-123');
});

it('should pass name, content, and description to the mutation', async () => {
mocks.setResponseOnce({ create_agent_skill: mockSkill } as CreateAgentSkillMutation);

await callToolByNameRawAsync('create_agent_skill', {
name: 'Send Slack Message',
content: '## Instructions\nPost a message.',
description: 'Posts to Slack',
});

expect(mocks.getMockRequest()).toHaveBeenCalledWith(
expect.stringContaining('createAgentSkill'),
{ name: 'Send Slack Message', content: '## Instructions\nPost a message.', description: 'Posts to Slack' },
expect.objectContaining({ versionOverride: 'dev' }),
);
});

it('should pass versionOverride dev', async () => {
mocks.setResponseOnce({ create_agent_skill: mockSkill } as CreateAgentSkillMutation);

await callToolByNameRawAsync('create_agent_skill', {
name: 'Send Slack Message',
content: '## Instructions',
});

expect(mocks.getMockRequest()).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
expect.objectContaining({ versionOverride: 'dev' }),
);
});

it('should throw when create_agent_skill returns null', async () => {
mocks.setResponseOnce({ create_agent_skill: null } as CreateAgentSkillMutation);

const result = await callToolByNameRawAsync('create_agent_skill', {
name: 'Send Slack Message',
content: '## Instructions',
});

expect(result.content[0].text).toContain('create_agent_skill returned no data');
});

it('should propagate API errors with context', async () => {
mocks.setError('API error');

const result = await callToolByNameRawAsync('create_agent_skill', {
name: 'Send Slack Message',
content: '## Instructions',
});

expect(result.content[0].text).toContain('Failed to create monday platform agent skill');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { z } from 'zod';
import {
CreateAgentSkillMutation,
CreateAgentSkillMutationVariables,
} from '../../../../../monday-graphql/generated/graphql.dev/graphql';
import { createAgentSkillMutation } from './create-agent-skill.graphql.dev';
import { ToolInputType, ToolOutputType, ToolType } from '../../../../tool';
import { BaseMondayApiTool, createMondayApiAnnotations } from '../../base-monday-api-tool';
import { rethrowWithContext } from '../../../../../utils';

export const createAgentSkillToolSchema = {
name: z.string().trim().min(1).describe('Display name of the new skill.'),
content: z
.string()
.trim()
.min(1)
.describe('Markdown instructions defining what the skill does and how to execute it.'),
description: z
.string()
.trim()
.min(1)
.optional()
.describe('Short description of the skill shown in the catalog.'),
};

export class CreateAgentSkillTool extends BaseMondayApiTool<typeof createAgentSkillToolSchema> {
name = 'create_agent_skill';
type = ToolType.WRITE;
annotations = createMondayApiAnnotations({
title: 'Create monday Platform Agent Skill',
readOnlyHint: false,
destructiveHint: false,
idempotentHint: false,
});

getDescription(): string {
return `Create a new custom skill in the account-wide skill catalog.

Skills extend what an agent can do — for example, sending emails, searching the web, or querying databases. A skill created here is available to all agents in the account.

After creating a skill, use manage_agent_skills with action:"add" and the returned id to attach it to a specific agent.

USAGE EXAMPLE:
- { "name": "Send Slack Message", "content": "## Instructions\\nPost a message to a Slack channel.", "description": "Sends a message to Slack" }`;
}

getInputSchema() {
return createAgentSkillToolSchema;
}

protected async executeInternal(
input: ToolInputType<typeof createAgentSkillToolSchema>,
): Promise<ToolOutputType<never>> {
try {
const variables: CreateAgentSkillMutationVariables = {
name: input.name,
content: input.content,
description: input.description,
};
const res = await this.mondayApi.request<CreateAgentSkillMutation>(createAgentSkillMutation, variables, {
versionOverride: 'dev',
});
if (!res.create_agent_skill) {
throw new Error('create_agent_skill returned no data');
}
return {
content: {
message: 'Skill created. Use the returned id with manage_agent_skills action:add to attach it to an agent.',
skill: res.create_agent_skill,
},
};
} catch (error) {
rethrowWithContext(error, 'create monday platform agent skill');
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { gql } from 'graphql-request';

export const createAgentSkillMutation = gql`
mutation createAgentSkill($name: String!, $content: String!, $description: String) {
create_agent_skill(name: $name, content: $content, description: $description) {
id
name
description
}
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ Two modes:

Do not mix prompt with manual profile fields in one request.

Created agents start in state INACTIVE and must be activated before they can be triggered. Instruct the user to activate from the monday.com agent settings UI.
Created agents start in state INACTIVE and must be activated before they can be triggered. After creation, call manage_agent_state with action:activate and the returned agent id.

created_at and updated_at are null in the response — call get_agent with the returned id afterward to fetch them.

Expand Down Expand Up @@ -137,7 +137,7 @@ USAGE EXAMPLES:

return {
content: {
message: `monday platform agent ${res.create_blank_agent.id} created in state INACTIVE — user must activate it from the monday.com UI before it can be triggered`,
message: `monday platform agent ${res.create_blank_agent.id} created in state INACTIVE — call manage_agent_state with action:activate and agent_id:${res.create_blank_agent.id} to activate it`,
agent: res.create_blank_agent,
},
};
Expand Down Expand Up @@ -165,7 +165,7 @@ USAGE EXAMPLES:

return {
content: {
message: `monday platform agent ${res.create_agent.id} created in state INACTIVE — user must activate it from the monday.com UI before it can be triggered`,
message: `monday platform agent ${res.create_agent.id} created in state INACTIVE — call manage_agent_state with action:activate and agent_id:${res.create_agent.id} to activate it`,
agent: res.create_agent,
},
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { MondayAgentToolkit } from 'src/mcp/toolkit';
import { callToolByNameRawAsync, createMockApiClient, parseToolResult } from '../../test-utils/mock-api-client';
import { GetAgentTriggersCatalogQuery, GetAgentSkillsCatalogQuery } from 'src/monday-graphql/generated/graphql.dev/graphql';

describe('GetAgentCatalogTool', () => {
let mocks: ReturnType<typeof createMockApiClient>;

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

const mockTrigger = {
block_reference_id: 'status-change-ref',
name: 'Status Change',
description: 'Fires when a status column changes',
field_schemas: [{ field_key: 'board_id', value_schema: 'The ID of the board to watch' }],
required_fields: [{ field_key: 'board_id', depends_on: [], optional: false }],
};

const mockSkill = {
id: 'skill-1',
name: 'Board Manager',
description: 'Manages boards and items',
};

it('should return triggers catalog when type is triggers', async () => {
mocks.setResponseOnce({ agent_triggers_catalog: [mockTrigger] } as GetAgentTriggersCatalogQuery);

const result = await callToolByNameRawAsync('get_agent_catalog', { type: 'triggers' });
const parsed = parseToolResult(result);

expect(parsed.count).toBe(1);
expect(parsed.triggers[0].block_reference_id).toBe('status-change-ref');
});

it('should pass versionOverride dev when fetching triggers', async () => {
mocks.setResponseOnce({ agent_triggers_catalog: [] } as GetAgentTriggersCatalogQuery);

await callToolByNameRawAsync('get_agent_catalog', { type: 'triggers' });

expect(mocks.getMockRequest()).toHaveBeenCalledWith(
expect.stringContaining('getAgentTriggersCatalog'),
expect.objectContaining({ block_reference_ids: undefined }),
expect.objectContaining({ versionOverride: 'dev' }),
);
});

it('should pass block_reference_ids when provided', async () => {
mocks.setResponseOnce({ agent_triggers_catalog: [mockTrigger] } as GetAgentTriggersCatalogQuery);

await callToolByNameRawAsync('get_agent_catalog', { type: 'triggers', block_reference_ids: ['status-change-ref'] });

expect(mocks.getMockRequest()).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({ block_reference_ids: ['status-change-ref'] }),
expect.anything(),
);
});

it('should return skills catalog when type is skills', async () => {
mocks.setResponseOnce({ agent_skills_catalog: [mockSkill] } as GetAgentSkillsCatalogQuery);

const result = await callToolByNameRawAsync('get_agent_catalog', { type: 'skills' });
const parsed = parseToolResult(result);

expect(parsed.count).toBe(1);
expect(parsed.skills[0].id).toBe('skill-1');
});

it('should pass versionOverride dev when fetching skills', async () => {
mocks.setResponseOnce({ agent_skills_catalog: [] } as GetAgentSkillsCatalogQuery);

await callToolByNameRawAsync('get_agent_catalog', { type: 'skills' });

expect(mocks.getMockRequest()).toHaveBeenCalledWith(
expect.stringContaining('getAgentSkillsCatalog'),
expect.anything(),
expect.objectContaining({ versionOverride: 'dev' }),
);
});

it('should return empty list with count 0 when no triggers exist', async () => {
mocks.setResponseOnce({ agent_triggers_catalog: [] } as GetAgentTriggersCatalogQuery);

const result = await callToolByNameRawAsync('get_agent_catalog', { type: 'triggers' });
const parsed = parseToolResult(result);

expect(parsed.count).toBe(0);
expect(parsed.triggers).toEqual([]);
});

it('should propagate errors when fetching triggers catalog', async () => {
mocks.setError('Unauthorized');

const result = await callToolByNameRawAsync('get_agent_catalog', { type: 'triggers' });

expect(result.content[0].text).toContain('Failed to fetch monday platform agent triggers catalog');
});

it('should propagate errors when fetching skills catalog', async () => {
mocks.setError('Unauthorized');

const result = await callToolByNameRawAsync('get_agent_catalog', { type: 'skills' });

expect(result.content[0].text).toContain('Failed to fetch monday platform agent skills catalog');
});
});
Loading
Loading