diff --git a/packages/@n8n/db/src/repositories/workflow.repository.ts b/packages/@n8n/db/src/repositories/workflow.repository.ts index 5fcb4bf813..3b88378967 100644 --- a/packages/@n8n/db/src/repositories/workflow.repository.ts +++ b/packages/@n8n/db/src/repositories/workflow.repository.ts @@ -435,6 +435,30 @@ export class WorkflowRepository extends Repository { this.applyTagsFilter(qb, filter); this.applyProjectFilter(qb, filter); this.applyParentFolderFilter(qb, filter); + this.applyAvailableInMCPFilter(qb, filter); + } + + private applyAvailableInMCPFilter( + qb: SelectQueryBuilder, + filter: ListQuery.Options['filter'], + ): void { + if (typeof filter?.availableInMCP === 'boolean') { + const dbType = this.globalConfig.database.type; + + if (['postgresdb'].includes(dbType)) { + qb.andWhere("workflow.settings ->> 'availableInMCP' = :availableInMCP", { + availableInMCP: filter.availableInMCP.toString(), + }); + } else if (['mysqldb', 'mariadb'].includes(dbType)) { + qb.andWhere("JSON_EXTRACT(workflow.settings, '$.availableInMCP') = :availableInMCP", { + availableInMCP: filter.availableInMCP, + }); + } else if (dbType === 'sqlite') { + qb.andWhere("JSON_EXTRACT(workflow.settings, '$.availableInMCP') = :availableInMCP", { + availableInMCP: filter.availableInMCP ? 1 : 0, // SQLite stores booleans as 0/1 + }); + } + } } private applyNameFilter( diff --git a/packages/cli/src/middlewares/list-query/dtos/workflow.filter.dto.ts b/packages/cli/src/middlewares/list-query/dtos/workflow.filter.dto.ts index 851069b3a8..299f63a67a 100644 --- a/packages/cli/src/middlewares/list-query/dtos/workflow.filter.dto.ts +++ b/packages/cli/src/middlewares/list-query/dtos/workflow.filter.dto.ts @@ -35,6 +35,11 @@ export class WorkflowFilter extends BaseFilter { @Expose() parentFolderId?: string; + @IsBoolean() + @IsOptional() + @Expose() + availableInMCP?: boolean; + static async fromString(rawFilter: string) { return await this.toFilter(rawFilter, WorkflowFilter); } diff --git a/packages/cli/src/workflows/workflow.request.ts b/packages/cli/src/workflows/workflow.request.ts index 1bbd66b275..57598068cb 100644 --- a/packages/cli/src/workflows/workflow.request.ts +++ b/packages/cli/src/workflows/workflow.request.ts @@ -53,6 +53,7 @@ export declare namespace WorkflowRequest { includeScopes?: string; includeFolders?: string; onlySharedWithMe?: string; + availableInMCP?: string; } > & { listQueryOptions: ListQuery.Options; diff --git a/packages/cli/test/integration/workflows/workflows.controller.test.ts b/packages/cli/test/integration/workflows/workflows.controller.test.ts index 5cf7040c94..15030bf860 100644 --- a/packages/cli/test/integration/workflows/workflows.controller.test.ts +++ b/packages/cli/test/integration/workflows/workflows.controller.test.ts @@ -859,6 +859,21 @@ describe('GET /workflows', () => { }); }); + test('should filter workflows by field: availableInMCP', async () => { + const workflow1 = await createWorkflow({ settings: { availableInMCP: true } }, owner); + await createWorkflow({ settings: {} }, owner); + + const response = await authOwnerAgent + .get('/workflows') + .query('filter={ "availableInMCP": true }') + .expect(200); + + expect(response.body).toEqual({ + count: 1, + data: [objectContaining({ id: workflow1.id })], + }); + }); + test('should filter workflows by field: tags (AND operator)', async () => { const workflow1 = await createWorkflow({ name: 'First' }, owner); const workflow2 = await createWorkflow({ name: 'Second' }, owner); diff --git a/packages/workflow/src/interfaces.ts b/packages/workflow/src/interfaces.ts index e0b3d73108..21119d5d86 100644 --- a/packages/workflow/src/interfaces.ts +++ b/packages/workflow/src/interfaces.ts @@ -2604,6 +2604,7 @@ export interface IWorkflowSettings { executionTimeout?: number; executionOrder?: 'v0' | 'v1'; timeSavedPerExecution?: number; + availableInMCP?: boolean; } export interface WorkflowFEMeta {