-
Notifications
You must be signed in to change notification settings - Fork 71
feat(agent-toolkit): agents subgraph tools expansion #343
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
NadavAvraham
wants to merge
28
commits into
master
Choose a base branch
from
feat/agent-tools-expansion
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
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 e4be478
docs: add agent tools expansion implementation plan
NadavAvraham a75204b
refactor(agent-toolkit): export agentFieldsFragment for reuse
NadavAvraham ec0d01b
feat(agent-toolkit): add GraphQL operations for 6 new agent tools
NadavAvraham 6cf4680
chore(agent-toolkit): regenerate dev graphql types for new agent oper…
NadavAvraham b924a9b
feat(agent-toolkit): add get_agent_catalog tool
NadavAvraham da245b0
fix(agent-toolkit): clean up get_agent_catalog variable assignment an…
NadavAvraham ca5cd0d
feat(agent-toolkit): add manage_agent_triggers tool
NadavAvraham 9897389
test(agent-toolkit): add error propagation tests for add and remove t…
NadavAvraham 45b5132
feat(agent-toolkit): add manage_agent_skills tool
NadavAvraham caec9a8
fix(agent-toolkit): improve manage_agent_skills control flow and add …
NadavAvraham 8de165d
feat(agent-toolkit): add update_agent tool
NadavAvraham 5deefcb
fix(agent-toolkit): improve update_agent null safety and test coverage
NadavAvraham 836573b
feat(agent-toolkit): add manage_agent_state tool
NadavAvraham 79a00f3
fix(agent-toolkit): improve manage_agent_state control flow, null saf…
NadavAvraham 4d64cfc
feat(agent-toolkit): add manage_agent_knowledge tool
NadavAvraham 30363fc
fix(agent-toolkit): fix update validation message and improve manage_…
NadavAvraham d23006e
fix(agent-toolkit): remove semicolons from tool descriptions to pass …
NadavAvraham 551af0e
chore(agent-toolkit): bump version to 5.11.0
NadavAvraham 4fd9a27
refactor(agent-toolkit): move returns inside try blocks and add skill…
NadavAvraham b798bbb
feat(agent-toolkit): add create_agent_skill tool
NadavAvraham ad43617
chore(agent-toolkit): bump version to 5.11.1
NadavAvraham 57346f2
chore(agent-toolkit): revert version to 5.11.0
NadavAvraham d0dabc9
refactor(agent-toolkit): fold create_agent_skill into manage_agent_sk…
NadavAvraham 560ea82
chore: remove design docs from branch (keep locally)
NadavAvraham 5470cc8
fix(agent-toolkit): address PR review feedback on agent tools
NadavAvraham 01b4755
fix(agent-toolkit): address PR #343 review feedback on agent tools
NadavAvraham 68dffd0
refactor(agent-toolkit): split create_agent_skill out of manage_agent…
NadavAvraham File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
.../tools/platform-api-tools/agents-tools/create-agent-skill/create-agent-skill-tool.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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'); | ||
| }); | ||
| }); |
76 changes: 76 additions & 0 deletions
76
.../core/tools/platform-api-tools/agents-tools/create-agent-skill/create-agent-skill-tool.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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'); | ||
| } | ||
| } | ||
| } |
11 changes: 11 additions & 0 deletions
11
...ools/platform-api-tools/agents-tools/create-agent-skill/create-agent-skill.graphql.dev.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| } | ||
| } | ||
| `; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
108 changes: 108 additions & 0 deletions
108
...re/tools/platform-api-tools/agents-tools/get-agent-catalog/get-agent-catalog-tool.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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'); | ||
| }); | ||
| }); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.