Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 8 additions & 0 deletions packages/agent-toolkit/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 5.70.0

### search — scope DOCUMENTS search to specific documents with `docIds`

`search` accepts a new optional `docIds` argument, forwarded to `search.docs(ids:)`. It scopes a DOCUMENTS search to the given documents (ids as they appear in document URLs, at most 512), which lets a caller ask which of a known set of documents mention a term instead of searching the whole account. It combines with `workspaceIds`, and like the other id filters an empty array or `null` means "no filter".

`search.docs(ids:)` exists from API version `2026-10`, which is the version the toolkit pins.

## 5.69.1

### Default `retrieval_only` to `true` for `get_monday_knowledge` developer docs queries
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.69.1",
"version": "5.70.0",
"description": "monday.com agent toolkit",
"exports": {
"./mcp": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ export const searchBoards = gql`
`;

export const searchDocs = gql`
query SearchDocs($query: String!, $limit: Int, $workspaceIds: [ID!]) {
query SearchDocs($query: String!, $limit: Int, $workspaceIds: [ID!], $docIds: [ID!]) {
search {
docs(query: $query, limit: $limit, workspace_ids: $workspaceIds) {
docs(query: $query, limit: $limit, workspace_ids: $workspaceIds, ids: $docIds) {
results {
id
indexed_data {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ describe('SearchTool', () => {
);
expect(description).toContain('BOARD search returns id, title, url, and workspaceId');
expect(description).toContain('DOCUMENTS search returns id, title, workspaceId, and highlights');
expect(description).toContain('Optionally scope it with workspaceIds and/or docIds');
expect(description).toContain('ITEMS search returns id, title, url, boardId, and workspaceId');
expect(description).toContain('Optionally scope it with workspaceIds, boardIds, and/or creatorIds');
expect(description).toContain('FOLDERS search returns id and title');
Expand Down Expand Up @@ -611,11 +612,12 @@ describe('SearchTool', () => {
});

expect(mocks.getMockRequest()).toHaveBeenCalledWith(
expect.stringContaining('query SearchDocs'),
expect.stringContaining('query SearchDocs('),
{
query: 'Document',
limit: 20,
workspaceIds: undefined,
docIds: undefined,
},
expect.objectContaining({ timeout: expect.any(Number) }),
);
Expand Down Expand Up @@ -681,6 +683,98 @@ describe('SearchTool', () => {
);
});

it('should pass docIds to the searchDocs request', async () => {
mocks.setResponse(mockDevDocsResponse);

const args: inputType = {
searchType: GlobalSearchType.DOCUMENTS,
searchTerm: 'Document',
docIds: [111, 222],
};

const parsedResult = await callToolByNameAsync('search', args);

expect(parsedResult.data).toHaveLength(3);
expect(mocks.getMockRequest()).toHaveBeenCalledWith(
expect.stringContaining('query SearchDocs('),
expect.objectContaining({ docIds: ['111', '222'] }),
expect.objectContaining({ timeout: expect.any(Number) }),
);
});

it('should combine docIds with workspaceIds', async () => {
mocks.setResponse(mockDevDocsResponse);

const args: inputType = {
searchType: GlobalSearchType.DOCUMENTS,
searchTerm: 'Document',
workspaceIds: [11111],
docIds: [111],
};

await callToolByNameAsync('search', args);

expect(mocks.getMockRequest()).toHaveBeenCalledWith(
expect.stringContaining('query SearchDocs('),
expect.objectContaining({ workspaceIds: ['11111'], docIds: ['111'] }),
expect.objectContaining({ timeout: expect.any(Number) }),
);
});

it('should send docIds as undefined when given an empty array', async () => {
mocks.setResponse(mockDevDocsResponse);

const args: inputType = {
searchType: GlobalSearchType.DOCUMENTS,
searchTerm: 'Document',
docIds: [],
};

await callToolByNameAsync('search', args);

expect(mocks.getMockRequest()).toHaveBeenCalledWith(
expect.stringContaining('query SearchDocs('),
expect.objectContaining({ docIds: undefined }),
expect.objectContaining({ timeout: expect.any(Number) }),
);
});

it('should treat a null docIds as omitted', async () => {
mocks.setResponse(mockDevDocsResponse);

const args = {
searchType: GlobalSearchType.DOCUMENTS,
searchTerm: 'Document',
docIds: null,
} as any;

await callToolByNameAsync('search', args);

expect(mocks.getMockRequest()).toHaveBeenCalledWith(
expect.stringContaining('query SearchDocs('),
expect.objectContaining({ docIds: undefined }),
expect.objectContaining({ timeout: expect.any(Number) }),
);
});

it('should accept a single docId passed as a scalar', async () => {
mocks.setResponse(mockDevDocsResponse);

const args = {
searchType: GlobalSearchType.DOCUMENTS,
searchTerm: 'Document',
docIds: 111,
} as any;

await callToolByNameAsync('search', args);

expect(mocks.getMockRequest()).toHaveBeenCalledWith(
expect.stringContaining('query SearchDocs('),
expect.objectContaining({ docIds: ['111'] }),
expect.objectContaining({ timeout: expect.any(Number) }),
);
});

it('should return highlights when present in the response', async () => {
const responseWithHighlights: SearchDocsQuery = {
search: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,11 @@ export const searchSchema = {
creatorIds: optionalIdArray(
'Array of user IDs (numbers) to filter by creator. Applies to ITEMS (filters by item creator), UPDATES (filters by update author), and DASHBOARDS (filters by dashboard creator). Only pass it if the user explicitly asked to filter by specific creators. Example: [12345, 67890].',
),

// for docs
docIds: optionalIdArray(
'Array of document IDs (numbers), as they appear in document URLs, to scope the search to. Applies to DOCUMENTS search, with at most 512 ids. Use it to search within a known set of documents, for example to find which of them mention a term. Only pass it if the user explicitly asked to search within specific documents. Example: [12345, 67890].',
),
};

export type SearchToolInput = typeof searchSchema;
Expand All @@ -233,7 +238,7 @@ For browsing all boards, docs, or folders within a workspace without a search te
For groups, use get_board_info tool.
For listing items within a specific board, use get_board_items_page tool. ITEMS search here queries items across the account.
BOARD search returns id, title, url, and workspaceId. Optionally scope it with boardIds.
DOCUMENTS search returns id, title, workspaceId, and highlights. highlights is an array of { field, fragments } entries (field is "name" or "content") where fragments contain matched text snippets with <em> tags around matched terms. highlights is omitted when no lexical match was made.
DOCUMENTS search returns id, title, workspaceId, and highlights. highlights is an array of { field, fragments } entries (field is "name" or "content") where fragments contain matched text snippets with <em> tags around matched terms. highlights is omitted when no lexical match was made. Optionally scope it with workspaceIds and/or docIds.
ITEMS search returns id, title, url, boardId, and workspaceId. Optionally scope it with workspaceIds, boardIds, and/or creatorIds.
WORKSPACES search returns id, title, and description.
UPDATES search returns id, title (the update body), itemId, boardId, and creatorId. Optionally scope it with workspaceIds, boardIds, and/or creatorIds.
Expand Down Expand Up @@ -285,7 +290,8 @@ FOLDERS search returns id and title. Optionally scope it with workspaceIds, whic
}

if (input.searchType === GlobalSearchType.DOCUMENTS) {
return this.searchDocsAsync(searchTerm, input.limit, workspaceIds);
const docIds = toFilterIds(input.docIds?.map((id) => id.toString()));
return this.searchDocsAsync(searchTerm, input.limit, workspaceIds, docIds);
}

if (input.searchType === GlobalSearchType.WORKSPACES) {
Expand Down Expand Up @@ -340,8 +346,13 @@ FOLDERS search returns id and title. Optionally scope it with workspaceIds, whic
}));
}

private async searchDocsAsync(query: string, limit: number, workspaceIds?: string[]): Promise<SearchResult[]> {
const variables: SearchDocsQueryVariables = { query, limit, workspaceIds };
private async searchDocsAsync(
query: string,
limit: number,
workspaceIds?: string[],
docIds?: string[],
): Promise<SearchResult[]> {
const variables: SearchDocsQueryVariables = { query, limit, workspaceIds, docIds };

const response = await this.mondayApi.request<SearchDocsQuery>(searchDocs, variables, {
timeout: SEARCH_TIMEOUT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ type Documents = {
"\n query GetFolders($page: Int!, $limit: Int!, $workspace_ids: [ID]) {\n folders(page: $page, limit: $limit, workspace_ids: $workspace_ids) {\n id\n name\n }\n }\n": typeof types.GetFoldersDocument,
"\n query SearchItems($query: String!, $limit: Int, $workspaceIds: [ID!], $boardIds: [ID!]) {\n search {\n items(query: $query, limit: $limit, workspace_ids: $workspaceIds, board_ids: $boardIds) {\n results {\n id\n indexed_data {\n id\n name\n url\n board_id\n workspace_id\n }\n }\n }\n }\n }\n": typeof types.SearchItemsDocument,
"\n query SearchBoards($query: String!, $limit: Int, $workspaceIds: [ID!], $boardIds: [ID!]) {\n search {\n boards(query: $query, limit: $limit, workspace_ids: $workspaceIds, board_ids: $boardIds) {\n results {\n id\n indexed_data {\n id\n name\n url\n workspace_id\n }\n }\n }\n }\n }\n": typeof types.SearchBoardsDocument,
"\n query SearchDocs($query: String!, $limit: Int, $workspaceIds: [ID!]) {\n search {\n docs(query: $query, limit: $limit, workspace_ids: $workspaceIds) {\n results {\n id\n indexed_data {\n id\n name\n workspace_id\n highlights {\n name\n content\n }\n }\n }\n }\n }\n }\n": typeof types.SearchDocsDocument,
"\n query SearchDocs($query: String!, $limit: Int, $workspaceIds: [ID!], $docIds: [ID!]) {\n search {\n docs(query: $query, limit: $limit, workspace_ids: $workspaceIds, ids: $docIds) {\n results {\n id\n indexed_data {\n id\n name\n workspace_id\n highlights {\n name\n content\n }\n }\n }\n }\n }\n }\n": typeof types.SearchDocsDocument,
"\n query SearchWorkspaces($query: String!, $limit: Int) {\n search {\n workspaces(query: $query, limit: $limit) {\n results {\n id\n indexed_data {\n id\n name\n description\n }\n }\n }\n }\n }\n": typeof types.SearchWorkspacesDocument,
"\n query SearchUpdates(\n $query: String!\n $limit: Int\n $workspaceIds: [ID!]\n $boardIds: [ID!]\n $creatorIds: [ID!]\n ) {\n search {\n updates(\n query: $query\n limit: $limit\n workspace_ids: $workspaceIds\n board_ids: $boardIds\n creator_ids: $creatorIds\n ) {\n results {\n id\n indexed_data {\n id\n body\n creator_id\n item_id\n board_id\n }\n }\n }\n }\n }\n": typeof types.SearchUpdatesDocument,
"\n query SearchTimelineItems($query: String!, $limit: Int, $workspaceIds: [ID!], $boardIds: [ID!]) {\n search {\n timeline_items(query: $query, limit: $limit, workspace_ids: $workspaceIds, board_ids: $boardIds) {\n results {\n id\n indexed_data {\n id\n title\n summary\n content\n item_id\n board_id\n }\n }\n }\n }\n }\n": typeof types.SearchTimelineItemsDocument,
Expand Down Expand Up @@ -223,7 +223,7 @@ const documents: Documents = {
"\n query GetFolders($page: Int!, $limit: Int!, $workspace_ids: [ID]) {\n folders(page: $page, limit: $limit, workspace_ids: $workspace_ids) {\n id\n name\n }\n }\n": types.GetFoldersDocument,
"\n query SearchItems($query: String!, $limit: Int, $workspaceIds: [ID!], $boardIds: [ID!]) {\n search {\n items(query: $query, limit: $limit, workspace_ids: $workspaceIds, board_ids: $boardIds) {\n results {\n id\n indexed_data {\n id\n name\n url\n board_id\n workspace_id\n }\n }\n }\n }\n }\n": types.SearchItemsDocument,
"\n query SearchBoards($query: String!, $limit: Int, $workspaceIds: [ID!], $boardIds: [ID!]) {\n search {\n boards(query: $query, limit: $limit, workspace_ids: $workspaceIds, board_ids: $boardIds) {\n results {\n id\n indexed_data {\n id\n name\n url\n workspace_id\n }\n }\n }\n }\n }\n": types.SearchBoardsDocument,
"\n query SearchDocs($query: String!, $limit: Int, $workspaceIds: [ID!]) {\n search {\n docs(query: $query, limit: $limit, workspace_ids: $workspaceIds) {\n results {\n id\n indexed_data {\n id\n name\n workspace_id\n highlights {\n name\n content\n }\n }\n }\n }\n }\n }\n": types.SearchDocsDocument,
"\n query SearchDocs($query: String!, $limit: Int, $workspaceIds: [ID!], $docIds: [ID!]) {\n search {\n docs(query: $query, limit: $limit, workspace_ids: $workspaceIds, ids: $docIds) {\n results {\n id\n indexed_data {\n id\n name\n workspace_id\n highlights {\n name\n content\n }\n }\n }\n }\n }\n }\n": types.SearchDocsDocument,
"\n query SearchWorkspaces($query: String!, $limit: Int) {\n search {\n workspaces(query: $query, limit: $limit) {\n results {\n id\n indexed_data {\n id\n name\n description\n }\n }\n }\n }\n }\n": types.SearchWorkspacesDocument,
"\n query SearchUpdates(\n $query: String!\n $limit: Int\n $workspaceIds: [ID!]\n $boardIds: [ID!]\n $creatorIds: [ID!]\n ) {\n search {\n updates(\n query: $query\n limit: $limit\n workspace_ids: $workspaceIds\n board_ids: $boardIds\n creator_ids: $creatorIds\n ) {\n results {\n id\n indexed_data {\n id\n body\n creator_id\n item_id\n board_id\n }\n }\n }\n }\n }\n": types.SearchUpdatesDocument,
"\n query SearchTimelineItems($query: String!, $limit: Int, $workspaceIds: [ID!], $boardIds: [ID!]) {\n search {\n timeline_items(query: $query, limit: $limit, workspace_ids: $workspaceIds, board_ids: $boardIds) {\n results {\n id\n indexed_data {\n id\n title\n summary\n content\n item_id\n board_id\n }\n }\n }\n }\n }\n": types.SearchTimelineItemsDocument,
Expand Down Expand Up @@ -589,7 +589,7 @@ export function graphql(source: "\n query SearchBoards($query: String!, $limit:
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query SearchDocs($query: String!, $limit: Int, $workspaceIds: [ID!]) {\n search {\n docs(query: $query, limit: $limit, workspace_ids: $workspaceIds) {\n results {\n id\n indexed_data {\n id\n name\n workspace_id\n highlights {\n name\n content\n }\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query SearchDocs($query: String!, $limit: Int, $workspaceIds: [ID!]) {\n search {\n docs(query: $query, limit: $limit, workspace_ids: $workspaceIds) {\n results {\n id\n indexed_data {\n id\n name\n workspace_id\n highlights {\n name\n content\n }\n }\n }\n }\n }\n }\n"];
export function graphql(source: "\n query SearchDocs($query: String!, $limit: Int, $workspaceIds: [ID!], $docIds: [ID!]) {\n search {\n docs(query: $query, limit: $limit, workspace_ids: $workspaceIds, ids: $docIds) {\n results {\n id\n indexed_data {\n id\n name\n workspace_id\n highlights {\n name\n content\n }\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query SearchDocs($query: String!, $limit: Int, $workspaceIds: [ID!], $docIds: [ID!]) {\n search {\n docs(query: $query, limit: $limit, workspace_ids: $workspaceIds, ids: $docIds) {\n results {\n id\n indexed_data {\n id\n name\n workspace_id\n highlights {\n name\n content\n }\n }\n }\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
Expand Down
Loading
Loading