Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
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": "2.29.3",
"version": "2.30.3",
"description": "monday.com agent toolkit",
"exports": {
"./mcp": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { MoveObjectTool } from './move-object-tool/move-object-tool';
import { BoardInsightsTool } from './board-insights/board-insights-tool';
import { SearchTool } from './search-tool/search-tool';
import { CreateUpdateInMondayTool } from './create-update-tool-ui/create-update-ui-tool';
import { SendMcpSupportTool } from './send-mcp-support-tool';

export const allGraphqlApiTools: BaseMondayApiToolConstructor[] = [
DeleteItemTool,
Expand Down Expand Up @@ -84,6 +85,7 @@ export const allGraphqlApiTools: BaseMondayApiToolConstructor[] = [
CreateWidgetTool,
BoardInsightsTool,
SearchTool,
SendMcpSupportTool,
];

export * from './all-monday-api-tool';
Expand Down Expand Up @@ -124,6 +126,7 @@ export * from './create-folder-tool/create-folder-tool';
export * from './move-object-tool/move-object-tool';
export * from './board-insights/board-insights-tool';
export * from './search-tool/search-tool';
export * from './send-mcp-support-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,160 @@
import { z } from 'zod';
import { createMockApiClient } from './test-utils/mock-api-client';
import { SendMcpSupportTool, sendMcpSupportToolSchema } from './send-mcp-support-tool';
import { trackEvent } from '../../../utils/tracking.utils';
import { extractTokenInfo } from '../../../utils/token.utils';

jest.mock('../../../utils/tracking.utils');
jest.mock('../../../utils/token.utils');

const mockedTrackEvent = trackEvent as jest.MockedFunction<typeof trackEvent>;
const mockedExtractTokenInfo = extractTokenInfo as jest.MockedFunction<typeof extractTokenInfo>;

describe('Send MCP Support Tool', () => {
let mocks: ReturnType<typeof createMockApiClient>;

beforeEach(() => {
mocks = createMockApiClient();
jest.clearAllMocks();
mockedExtractTokenInfo.mockReturnValue({
tid: 123,
aai: 456,
uid: 789,
actid: 101112,
rgn: 'us-east',
per: 'admin',
});
});

it('Sends feedback with complete tracking data', async () => {
const tool = new SendMcpSupportTool(mocks.mockApiClient, 'fake_token');

const result = await tool.execute({
type: 'bug_report',
message: 'This is a test bug report with sufficient details.',
});

expect(result.content).toContain('Thank you for your bug report!');
expect(result.content).toContain('sent to the monday MCP team');

const feedbackCall = mockedTrackEvent.mock.calls.find(
call => call[0].name === 'monday_mcp_support_feedback'
);
expect(feedbackCall).toBeDefined();
expect(feedbackCall![0].data).toMatchObject({
feedbackType: 'bug_report',
message: 'This is a test bug report with sufficient details.',
tid: 123,
aai: 456,
uid: 789,
});
});

it('Handles optional title field', async () => {
const tool = new SendMcpSupportTool(mocks.mockApiClient, 'fake_token');

const withTitle = await tool.execute({
type: 'feedback',
message: 'Message with title',
title: 'Test Title',
});
expect(withTitle.content).toContain('Thank you');

let feedbackCall = mockedTrackEvent.mock.calls.find(
call => call[0].name === 'monday_mcp_support_feedback'
);
expect(feedbackCall![0].data.title).toBe('Test Title');

jest.clearAllMocks();

const withoutTitle = await tool.execute({
type: 'feedback',
message: 'Message without title',
});
expect(withoutTitle.content).toContain('Thank you');

feedbackCall = mockedTrackEvent.mock.calls.find(
call => call[0].name === 'monday_mcp_support_feedback'
);
expect(feedbackCall![0].data).not.toHaveProperty('title');
});

it('Captures token and context information', async () => {
const tool = new SendMcpSupportTool(mocks.mockApiClient, 'fake_token', {
boardId: 12345,
agentType: 'test-agent',
agentClientName: 'TestClient',
});

await tool.execute({
type: 'feedback',
message: 'Test message with context',
});

const feedbackCall = mockedTrackEvent.mock.calls.find(
call => call[0].name === 'monday_mcp_support_feedback'
);
expect(feedbackCall![0].data).toMatchObject({
boardId: 12345,
agentType: 'test-agent',
agentClientName: 'TestClient',
tid: 123,
uid: 789,
});
});

it('Works without token or context', async () => {
mockedExtractTokenInfo.mockReturnValue({});
const tool = new SendMcpSupportTool(mocks.mockApiClient);

await tool.execute({
type: 'feedback',
message: 'Test message without extras',
});

const feedbackCall = mockedTrackEvent.mock.calls.find(
call => call[0].name === 'monday_mcp_support_feedback'
);
expect(feedbackCall![0].data).toMatchObject({
feedbackType: 'feedback',
message: 'Test message without extras',
});
expect(feedbackCall![0].data).not.toHaveProperty('boardId');
});

it('Validates message length and type requirements', () => {
const schema = z.object(sendMcpSupportToolSchema);

// Too short
expect(schema.safeParse({ type: 'feedback', message: 'Short' }).success).toBe(false);

// Too long
expect(schema.safeParse({ type: 'feedback', message: 'a'.repeat(2001) }).success).toBe(false);

// Invalid type
expect(schema.safeParse({ type: 'invalid', message: 'Valid message' }).success).toBe(false);

// Valid
expect(schema.safeParse({ type: 'feedback', message: '1234567890' }).success).toBe(true);
});

it('Validates title length requirement', () => {
const schema = z.object(sendMcpSupportToolSchema);

// Too long title
const longTitle = 'a'.repeat(101);
expect(schema.safeParse({
type: 'bug_report',
message: 'Valid message',
title: longTitle
}).success).toBe(false);

// Valid title
expect(schema.safeParse({
type: 'bug_report',
message: 'Valid message',
title: 'Valid Title'
}).success).toBe(true);
});
});

Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { z } from 'zod';
import { ToolInputType, ToolOutputType, ToolType } from '../../tool';
import { BaseMondayApiTool, createMondayApiAnnotations } from './base-monday-api-tool';
import { trackEvent } from '../../../utils/tracking.utils';
import { extractTokenInfo } from '../../../utils/token.utils';

export const sendMcpSupportToolSchema = {
type: z
.enum(['feature_request', 'bug_report', 'feedback', 'other'])
.describe('The type of message being sent to the MCP support team'),
message: z
.string()
.min(10, 'Message must be at least 10 characters')
.max(2000, 'Message must be at most 2000 characters')
.describe('The detailed message, bug report, feature request, or feedback (max 2000 chars)'),
title: z
.string()
.max(100)
.optional()
.describe('Optional brief subject line summarizing the feedback'),
};

export class SendMcpSupportTool extends BaseMondayApiTool<typeof sendMcpSupportToolSchema> {
name = 'send_mcp_support';
type = ToolType.READ;
annotations = createMondayApiAnnotations({
title: 'Send Feedback to MCP Team',
readOnlyHint: true,
destructiveHint: false,
idempotentHint: false,
});

getDescription(): string {
return `Send the monday MCP team a feature_request, bug_report, feedback or any other message. We will look at every message to fix issues and design what to build next.

Use this tool proactively when:
- Something goes wrong that shouldn't have
- The user expresses frustration with the MCP
- The user requests a feature that doesn't exist
- The user provides general feedback about their experience

The MCP team reviews all submissions to improve the toolkit.`;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The MCP team reviews all submissions to improve the toolkit.`;

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}

getInputSchema(): typeof sendMcpSupportToolSchema {
return sendMcpSupportToolSchema;
}

protected async executeInternal(
input: ToolInputType<typeof sendMcpSupportToolSchema>,
): Promise<ToolOutputType<never>> {
// Extract token information
const tokenInfo = this.apiToken ? extractTokenInfo(this.apiToken) : {};

// Build the event data
const eventData: Record<string, unknown> = {
feedbackType: input.type,
message: input.message,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notice I did not add a toolType and toolName as I don't think it is needed (we do have those in our track tool event)

...tokenInfo,
};

// Add optional title if provided
if (input.title) {
eventData.title = input.title;
}

// Send feedback to BigBrain
trackEvent({
name: 'monday_mcp_support_feedback',
data: { ...(this.context || {}), ...eventData },
});

// Return success message
const feedbackTypeLabel = input.type.replace('_', ' ');
return {
content: `Thank you for your ${feedbackTypeLabel}! Your message has been sent to the monday MCP team. We review all feedback to improve the toolkit and will use this to make the MCP better.`,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think we should speak in first person like this - will be confusing as to who this message is coming from

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I fully understand your comment.
I changed the text to:

Thank you for your ${feedbackTypeLabel}! Your message has been sent to the monday MCP team. The team reviews all feedback to improve the toolkit.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh got it.
Changed it to:

Message has been sent to the monday MCP team. The team reviews all feedback to improve the toolkit.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We talked about it online:
Changed the message to:

Message has been sent successfully. Thank you for your feedback!

};
}
}