mirror of
https://github.com/n8n-io/n8n.git
synced 2026-06-01 17:27:14 +02:00
fix(core): Sort MCP search_workflows by most recently edited (#31245)
This commit is contained in:
parent
e07c8e6e6d
commit
3d452f7cb9
|
|
@ -145,6 +145,28 @@ describe('search-workflows MCP tool', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('defaults to sorting by most recently updated first', async () => {
|
||||
const workflowService = mockInstance(WorkflowService, {
|
||||
getMany: jest.fn().mockResolvedValue({ workflows: [], count: 0 }),
|
||||
});
|
||||
await searchWorkflows(user, workflowService as unknown as WorkflowService, {});
|
||||
|
||||
const [, optionsArg] = (workflowService.getMany as jest.Mock).mock.calls[0];
|
||||
expect(optionsArg.sortBy).toBe('updatedAt:desc');
|
||||
});
|
||||
|
||||
test('passes through explicit sortBy option', async () => {
|
||||
const workflowService = mockInstance(WorkflowService, {
|
||||
getMany: jest.fn().mockResolvedValue({ workflows: [], count: 0 }),
|
||||
});
|
||||
await searchWorkflows(user, workflowService as unknown as WorkflowService, {
|
||||
sortBy: 'name:asc',
|
||||
});
|
||||
|
||||
const [, optionsArg] = (workflowService.getMany as jest.Mock).mock.calls[0];
|
||||
expect(optionsArg.sortBy).toBe('name:asc');
|
||||
});
|
||||
|
||||
test('clamps non-positive limit up to 1', async () => {
|
||||
const workflowService = mockInstance(WorkflowService, {
|
||||
getMany: jest.fn().mockResolvedValue({ workflows: [], count: 0 }),
|
||||
|
|
|
|||
|
|
@ -24,10 +24,22 @@ export type ToolDefinition<InputArgs extends z.ZodRawShape = z.ZodRawShape> = {
|
|||
};
|
||||
|
||||
// Shared MCP tool types
|
||||
export const SEARCH_WORKFLOWS_SORT_BY_VALUES = [
|
||||
'updatedAt:desc',
|
||||
'updatedAt:asc',
|
||||
'createdAt:desc',
|
||||
'createdAt:asc',
|
||||
'name:asc',
|
||||
'name:desc',
|
||||
] as const;
|
||||
|
||||
export type SearchWorkflowsSortBy = (typeof SEARCH_WORKFLOWS_SORT_BY_VALUES)[number];
|
||||
|
||||
export type SearchWorkflowsParams = {
|
||||
limit?: number;
|
||||
query?: string;
|
||||
projectId?: string;
|
||||
sortBy?: SearchWorkflowsSortBy;
|
||||
};
|
||||
|
||||
export type SearchWorkflowsItem = {
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ import { type User, type WorkflowEntity } from '@n8n/db';
|
|||
import z from 'zod';
|
||||
|
||||
import { USER_CALLED_MCP_TOOL_EVENT } from '../mcp.constants';
|
||||
import { SEARCH_WORKFLOWS_SORT_BY_VALUES } from '../mcp.types';
|
||||
import type {
|
||||
ToolDefinition,
|
||||
SearchWorkflowsParams,
|
||||
SearchWorkflowsResult,
|
||||
SearchWorkflowsItem,
|
||||
SearchWorkflowsSortBy,
|
||||
UserCalledMCPToolEventPayload,
|
||||
} from '../mcp.types';
|
||||
|
||||
|
|
@ -17,10 +19,18 @@ import { createLimitSchema } from './schemas';
|
|||
|
||||
const MAX_RESULTS = 200;
|
||||
|
||||
const DEFAULT_SORT_BY: SearchWorkflowsSortBy = 'updatedAt:desc';
|
||||
|
||||
const inputSchema = {
|
||||
limit: createLimitSchema(MAX_RESULTS),
|
||||
query: z.string().optional().describe('Filter by name or description'),
|
||||
projectId: z.string().optional(),
|
||||
sortBy: z
|
||||
.enum(SEARCH_WORKFLOWS_SORT_BY_VALUES)
|
||||
.optional()
|
||||
.describe(
|
||||
`Sort order for results (default: ${DEFAULT_SORT_BY}). Use updatedAt:desc to find the most recently edited workflows first.`,
|
||||
),
|
||||
} satisfies z.ZodRawShape;
|
||||
|
||||
const outputSchema = {
|
||||
|
|
@ -38,7 +48,9 @@ const outputSchema = {
|
|||
updatedAt: z
|
||||
.string()
|
||||
.nullable()
|
||||
.describe('The ISO timestamp when the workflow was last updated'),
|
||||
.describe(
|
||||
'ISO timestamp the workflow definition was last saved. Use this to identify recently edited workflows.',
|
||||
),
|
||||
triggerCount: z
|
||||
.number()
|
||||
.nullable()
|
||||
|
|
@ -82,12 +94,14 @@ export const createSearchWorkflowsTool = (
|
|||
limit = MAX_RESULTS,
|
||||
query,
|
||||
projectId,
|
||||
sortBy,
|
||||
}: {
|
||||
limit?: number;
|
||||
query?: string;
|
||||
projectId?: string;
|
||||
sortBy?: SearchWorkflowsSortBy;
|
||||
}) => {
|
||||
const parameters = { limit, query, projectId };
|
||||
const parameters = { limit, query, projectId, sortBy };
|
||||
const telemetryPayload: UserCalledMCPToolEventPayload = {
|
||||
user_id: user.id,
|
||||
tool_name: 'search_workflows',
|
||||
|
|
@ -99,6 +113,7 @@ export const createSearchWorkflowsTool = (
|
|||
limit,
|
||||
query,
|
||||
projectId,
|
||||
sortBy,
|
||||
});
|
||||
|
||||
// Track successful execution
|
||||
|
|
@ -136,12 +151,13 @@ export const createSearchWorkflowsTool = (
|
|||
export async function searchWorkflows(
|
||||
user: User,
|
||||
workflowService: WorkflowService,
|
||||
{ limit = MAX_RESULTS, query, projectId }: SearchWorkflowsParams,
|
||||
{ limit = MAX_RESULTS, query, projectId, sortBy = DEFAULT_SORT_BY }: SearchWorkflowsParams,
|
||||
): Promise<SearchWorkflowsResult> {
|
||||
const safeLimit = Math.min(Math.max(1, limit), MAX_RESULTS);
|
||||
|
||||
const options: ListQuery.Options = {
|
||||
take: safeLimit,
|
||||
sortBy,
|
||||
filter: {
|
||||
isArchived: false,
|
||||
...(query ? { query } : {}),
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user