Allow workflows listing in MCP (#22013)

This resolves https://github.com/twentyhq/twenty/issues/21986

Add `list_workflows `MCP tool

Workflow objects are excluded from the generic database CRUD tools
exposed via MCP, which meant the only way to list workflows was through
a direct API call.

This adds a `list_workflows `tool to the `WorkflowToolProvider`, making
it available via MCP alongside the existing workflow builder tools. It
supports optional filtering by status (`DRAFT`, `ACTIVE`, `DEACTIVATED`)
and pagination (`limit`/`offset`). The status filter uses an
array-membership predicate (`ANY`) since `statuses `is a multi-value
field.

---------

Co-authored-by: Souheyl Gouadria <souheyl.gouadria@medius.com>
Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
This commit is contained in:
souheyl gouadria
2026-06-24 14:52:29 +01:00
committed by GitHub
parent 56e20a81ea
commit bf345bb177
4 changed files with 103 additions and 1 deletions
@@ -24,7 +24,7 @@ export const buildMcpServerInstructions = (
``,
`Non-CRUD tools — use learn_tools for schemas:`,
` ACTION: http_request | send_email | draft_email | navigate_app | code_interpreter | search_help_center`,
` WORKFLOW: create_complete_workflow | create/update/delete_workflow_version_step | activate/deactivate_workflow_version`,
` WORKFLOW: list_workflows | create_complete_workflow | create/update/delete_workflow_version_step | activate/deactivate_workflow_version | list_workflow_runs | get_workflow_run | get_workflow_current_version`,
` METADATA: get/create/update/delete_object_metadata | get/create/update/delete_field_metadata`,
` Both GET tools return system items as compact summaries by default — keep that default for listing/inspecting; only set includeFullSystemObjects / includeFullSystemFields=true when you specifically need a system item's full configuration`,
` VIEW: get_views | get_view_query_parameters | create/update/delete_view | manage view fields, filters, sorts`,
@@ -68,6 +68,10 @@ LOGIC_FUNCTION steps execute logic functions provided by installed applications.
{ "stepType": "LOGIC_FUNCTION", "workflowVersionId": "<version-id>", "defaultSettings": { "input": { "logicFunctionId": "<logic-function-id>" } } }
3. Or when using \`create_complete_workflow\`, include a step with type "LOGIC_FUNCTION" and settings.input.logicFunctionId.
## Listing Workflows
To discover existing workflows in the workspace, use \`list_workflows\`. Use this before modifying a workflow when the user refers to it by name rather than id — resolve the \`id\` here first, then call \`get_workflow_current_version\` with it.
## Troubleshooting Workflow Runs
When a user reports a failing or misbehaving workflow, diagnose it with two read-only tools:
@@ -27,6 +27,7 @@ import { createGetWorkflowCurrentVersionTool } from 'src/modules/workflow/workfl
import { createGetWorkflowRunTool } from 'src/modules/workflow/workflow-tools/tools/get-workflow-run.tool';
import { createListLogicFunctionToolsTool } from 'src/modules/workflow/workflow-tools/tools/list-logic-function-tools.tool';
import { createListWorkflowRunsTool } from 'src/modules/workflow/workflow-tools/tools/list-workflow-runs.tool';
import { createListWorkflowsTool } from 'src/modules/workflow/workflow-tools/tools/list-workflows.tool';
import { createUpdateAgentTool } from 'src/modules/workflow/workflow-tools/tools/update-agent.tool';
import { createUpdateLogicFunctionSourceTool } from 'src/modules/workflow/workflow-tools/tools/update-logic-function-source.tool';
import { createUpdateWorkflowVersionPositionsTool } from 'src/modules/workflow/workflow-tools/tools/update-workflow-version-positions.tool';
@@ -134,6 +135,10 @@ export class WorkflowToolWorkspaceService {
this.deps,
contextWithPermissions,
);
const listWorkflows = createListWorkflowsTool(
this.deps,
contextWithPermissions,
);
const updateLogicFunctionSource = createUpdateLogicFunctionSourceTool(
this.deps,
context,
@@ -159,6 +164,7 @@ export class WorkflowToolWorkspaceService {
[deactivateWorkflowVersion.name]: deactivateWorkflowVersion,
[computeStepOutputSchema.name]: computeStepOutputSchema,
[getWorkflowCurrentVersion.name]: getWorkflowCurrentVersion,
[listWorkflows.name]: listWorkflows,
[getWorkflowRun.name]: getWorkflowRun,
[listWorkflowRuns.name]: listWorkflowRuns,
[updateLogicFunctionSource.name]: updateLogicFunctionSource,
@@ -0,0 +1,92 @@
import { z } from 'zod';
import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config';
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
import {
WorkflowStatus,
type WorkflowWorkspaceEntity,
} from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
import {
type WorkflowToolContext,
type WorkflowToolDependencies,
} from 'src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type';
type ListWorkflowsToolContext = WorkflowToolContext & {
rolePermissionConfig: RolePermissionConfig;
};
const listWorkflowsSchema = z.object({
status: z
.nativeEnum(WorkflowStatus)
.optional()
.describe('Filter by status (DRAFT, ACTIVE, DEACTIVATED)'),
limit: z.number().int().min(1).max(100).optional().default(50),
offset: z.number().int().min(0).optional().default(0),
});
type ListWorkflowsInput = z.infer<typeof listWorkflowsSchema>;
export const createListWorkflowsTool = (
deps: Pick<WorkflowToolDependencies, 'globalWorkspaceOrmManager'>,
context: ListWorkflowsToolContext,
) => ({
name: 'list_workflows' as const,
description:
'List all workflows in the workspace. Supports filtering by status and pagination.',
inputSchema: listWorkflowsSchema,
execute: async (parameters: ListWorkflowsInput) => {
try {
const authContext = buildSystemAuthContext(context.workspaceId);
return await deps.globalWorkspaceOrmManager.executeInWorkspaceContext(
async () => {
const workflowRepository =
await deps.globalWorkspaceOrmManager.getRepository<WorkflowWorkspaceEntity>(
context.workspaceId,
'workflow',
context.rolePermissionConfig,
);
const queryBuilder =
workflowRepository.createQueryBuilder('workflow');
if (parameters.status) {
queryBuilder.where(':status = ANY(workflow.statuses)', {
status: parameters.status,
});
}
queryBuilder
.orderBy('workflow.createdAt', 'DESC')
.take(parameters.limit)
.skip(parameters.offset);
const [workflows, totalCount] = await queryBuilder.getManyAndCount();
return {
success: true,
workflows: workflows.map((workflow) => ({
id: workflow.id,
name: workflow.name,
statuses: workflow.statuses,
lastPublishedVersionId: workflow.lastPublishedVersionId,
createdAt: workflow.createdAt,
updatedAt: workflow.updatedAt,
})),
totalCount,
};
},
authContext,
);
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
return {
success: false,
error: errorMessage,
message: `Failed to list workflows: ${errorMessage}`,
};
}
},
});