feat(workflows): add base MCP tools for workflows product#60388
Conversation
MCP UI Apps size report
|
|
⏭️ Skipped snapshot commit because branch advanced to The new commit will trigger its own snapshot update workflow. If you expected this workflow to succeed: This can happen due to concurrent commits. To get a fresh workflow run, either:
|
|
|
Size Change: 0 B Total Size: 80.7 MB ℹ️ View Unchanged
|
Tighter help_text on HogFlow* serializers and tools.yaml entries to save LLM context tokens, plus a couple of cleanups: - Drop redundant `exit_condition: allow_null=True` (model column is NOT NULL, so null would 500 on save instead of validating cleanly) - Drop `exclude_params: trigger` from workflows-create/update — already excluded via `read_only_fields` on the serializer
| "actions:[...], filter_test_accounts:<bool>}. <cond>: {key, value, operator, " | ||
| "type: event|person|group}. " | ||
| "function*: {template_id, inputs}. " | ||
| "delay: {delay_duration: '<number><unit>'} where unit is m|h|d. Fractions OK ('0.5m'=30s; " |
There was a problem hiding this comment.
Added this because when I was testing, I'd say things like "wait 20 seconds and then do xyz" and it'd try 20s, which doesn't pass validation. Any reason not to add seconds to the UI as well?
| try { | ||
| await context.api.request({ | ||
| method: 'DELETE', | ||
| path: `/api/projects/${encodeURIComponent(String(projectId))}/hog_flows/${encodeURIComponent(id)}/`, |
There was a problem hiding this comment.
Used an API call here since we don't have a deleteWorkflow MCP tool. Any better way to do this?
PR overviewAll previously flagged issues have been addressed. No open security concerns remain on this pull request. Security reviewNo open security issues remain on this pull request. Fixed/addressed: 4 · PR risk: 0/10 |
MCP CI doesn't start the plugins container (CDP_API_URL), so the workflows-run integration tests 500'd on every run. Drop those and cover the same surface area at the layers that don't need CDP: Django pytest with CDP mocked, plus an MCP unit test for handler wiring.
edges was a bare serializers.JSONField, so drf-spectacular emitted no
type info downstream. MCP clients had to guess the shape and ended up
JSON-stringifying the edges array, which the backend then stored as a
string and the executor choked on.
Replace with a typed ListField(child=HogFlowEdgeSerializer). Also
tighten the function action 'config' help_text so agents know inputs
must be wrapped in {value: ...} for hog templating to compile.
dmarchuk
left a comment
There was a problem hiding this comment.
Looks good! 🙌 Just left 2 small suggestions
Splits the workflow lifecycle into dedicated MCP tools (workflows-enable, workflows-disable, workflows-archive) so agents have an idiomatic path beyond the generic update. Hand-rolled handlers PATCH the existing hog_flows_partial_update endpoint — no new REST surface. perform_create now rejects MCP requests with status='active' so workflows must go through create-as-draft → run → enable. perform_update's edit guard is rewritten to allow status-only PATCHes (so the lifecycle tools are idempotent) and reads request.data directly to avoid tripping on serializer-injected derived fields.
# Conflicts: # products/workflows/mcp/tools.yaml
Flag test-runs as destructive so MCP clients that honour the annotation prompt for confirmation before invoking — runs with mock_async_functions=false fire real HTTP/email/SMS side effects. Also regenerates MCP tool definitions so generated files match current OpenAPI.
workflows-update was advertised non-destructive but still forwarded status, so an MCP caller could enable a workflow without the destructive workflows-enable tool. Schema now excludes status (via exclude_params) so the field is hidden at the tool surface. Backend gains a second MCP guard rejecting any payload that mixes status with other fields — status transitions must go through the dedicated lifecycle tools, regardless of how the request is constructed.
|
🎭 Playwright report · View test results →
These issues are not necessarily caused by your changes. |
0b4df2c to
d629128
Compare
mariusandra
left a comment
There was a problem hiding this comment.
✅ for the hogql review/block, didn't really check the rest
|
Thank you @mariusandra 🙏🏻 |
Documents the new workflows-enable, workflows-disable, and workflows-archive tools added in PostHog/posthog#60388. Updates guardrails section to reflect the create-test-enable workflow enforced by MCP.
Problem
Workflows weren't available through MCP. This introduces a focused set of tools that let agents create, test, iterate, and run the full lifecycle of workflows.
Screen.Recording.2026-05-28.at.2.09.32.pm.mov
Changes
MCP tools (
products/workflows/mcp/tools.yaml+ hand-rolled handlers inservices/mcp/src/tools/workflows/):workflows-create/workflows-update/workflows-runworkflows-enable/workflows-disable/workflows-archive— thin wrappers over the existinghog_flows_partial_updateendpoint. No new REST endpoints; each tool just PATCHes the rightstatus. Enable/disable are flagged destructive so MCP clients prompt for confirmation.HogFlowSerializer/HogFlowActionSerializerhelp_texthardened so generated tool schemas (and frontend types) carry shape, format, and constraint info.Guardrails (
posthog/api/hog_flow.py), gated onx-posthog-client: mcp:perform_createrejects MCP creates withstatus='active'. The intended flow is create-as-draft →workflows-run→workflows-enable.perform_updaterejects non-status edits onactiveworkflows; status-only PATCHes are always allowed so the lifecycle tools work idempotently.workflows-archive.Frontend and raw API are unaffected.
How did you test this code?
posthog/api/test/test_hog_flow.py— all 72 tests passing, including the create/edit/lifecycle guard cases.lint-tool-names, and TS typecheck clean.workflows.integration.test.tscovers the new lifecycle tools; needs a live API + credentials, so it runs separately.Publish to changelog?
Yes — Workflows are now available in PostHog MCP. Agents can create drafts, test run, update, enable, disable, and archive workflows directly.
Docs update
Self-described via serializer
help_text→ generated tool schemas.🤖 Agent context
Claude Code. Decisions:
services/mcp/src/tools/workflows/lifecycle.tsrather than backed by new DRF@actionendpoints, to keep the public REST surface unchanged. They reusehog_flows_partial_update.request.data(the raw payload) instead ofserializer.validated_databecauseHogFlowSerializer.validateinjects derived fields (trigger,billable_action_types) that would make every status-only PATCH look like an edit.destructive: trueannotation for clients that honour it, plus an explicit "Confirm with the user before invoking" sentence in the tool description for clients that don't.