> For the complete documentation index, see llms.txt.
Skip to main content

Check out Port for yourself ➜ 

Build workflows with AI

Implement with AI

Send this guide to your coding agent.

Prerequisite: Install Port MCP

Open plan mode. Implement this Port guide in my org via MCP:

https://docs.port.io/guides/all/build-port-workflows-with-mcp/

Goal: get the guide's core flow working end-to-end in my org; adapting it to fit my existing setup takes priority over matching the guide 1:1.

Plan:
1. Confirm MCP is connected, in the right org, with sufficient permissions.
2. Diff the guide's data model (blueprints, properties, relations, actions, agents, automations, integrations, secrets) against mine.
3. Propose adaptations for gaps, reusing existing blueprints/relations over guide-named duplicates.
4. Flag what needs a UI click, credential, or secret from me, testing MCP capability empirically before ruling anything out.
5. Stop on any blocker and give me options. Approving this plan authorizes the writes it lists; pause only for writes beyond what's listed.

Build:
- Extend blueprint schema additively when upserting; don't remove or overwrite existing properties, and treat type conflicts as a blocker, not an auto-fix.
- List any mock data in the plan, minimal and labeled mock; once approved, seed it without re-asking, and tell me what you seeded.
- For anything the guide writes downstream (e.g. a webhook target), use a real entity, not a mock.
- For pages/widgets, use the real page identifier from the app URL, not a guessed slug.
- When you hit a UI step confirmed (not assumed) unsupported via MCP, pause, give exact clicks, then resume via MCP.
- Validate and give links after each meaningful step (only a tool-returned URL, no guessed paths); don't proceed if the last run wasn't a success.

Done:
- Confirm the guide's expected output exists and runs in Port.
- Summarize adaptations, seeded data, what was mocked or skipped, remaining UI steps, and how to verify.

Use Port's MCP (Model Context Protocol) server to build workflows through natural language conversations with AI. A workflow is a multi-step node graph, made up of a trigger, one or more action nodes, and optional branching, that runs on Port's governed execution layer. This guide shows how to design workflows, configure user inputs, wire up backends, and react to catalog events, all by describing what you need in plain language.

Instead of hand-authoring workflow JSON in the editor, you can have a conversation with your AI assistant to build workflows iteratively, review each change, and refine the node graph as you go.

Open Beta

Port workflows are currently in open beta and available to all users. Workflows may undergo changes without prior notice.

Common use cases

  • Build deployment workflows: Describe a deployment process and let AI assemble the trigger and action nodes.
  • Automate incident response: Ask AI to create a workflow that opens incidents and notifies the right people.
  • Add approval steps: Explain your approval requirements and have AI insert an input node with reviewers.
  • React to catalog events: Describe an event-driven flow and let AI configure the event trigger and downstream nodes.

Prerequisites

This guide assumes you have:

Create workflows with AI

The Port MCP server provides workflow tools that let AI agents build and run your workflows through conversation. You describe what you need, and the AI generates the node graph and creates it in Port.

ToolWhat it does
upsert_workflowCreate or update a workflow directly from a definition.
suggest_workflowPropose a workflow as a preview diff that you review and apply from the Workflow Editor.
list_workflowsDiscover the workflows that already exist in your organization.
get_workflowInspect a workflow's definition, nodes, and inputs.
trigger_runExecute a workflow run.
get_runTrack the status and outputs of a run.
Save directly or preview first

Use upsert_workflow to create or update a workflow immediately. When you are working inside the Workflow Editor, ask the assistant to use suggest_workflow instead. It presents the change as a preview diff that you review and apply from the editor.

Start with a simple description

Describe the workflow you want in natural language. The AI interprets your requirements and generates the node graph.

Example conversation:

"Create a workflow called 'Deploy to Staging' triggered by a self-service form. It takes a service name and environment, then dispatches a GitHub workflow to deploy the service."

The AI maps this to a SELF_SERVE_TRIGGER node for the form and an INTEGRATION_ACTION node that dispatches a GitHub Actions workflow through your installed GitHub integration, then saves it with upsert_workflow:

Resulting workflow definition (Click to expand)
{
"identifier": "deploy_to_staging",
"title": "Deploy to Staging",
"icon": "Rocket",
"description": "Deploy a service to the staging environment",
"category": "Deployments",
"nodes": [
{
"identifier": "trigger",
"title": "Deploy to Staging",
"config": {
"type": "SELF_SERVE_TRIGGER",
"userInputs": {
"properties": {
"service": {
"type": "string",
"title": "Service name",
"description": "The service (repository) to deploy"
},
"environment": {
"type": "string",
"title": "Environment",
"enum": ["staging"],
"default": "staging"
}
},
"required": ["service"]
}
}
},
{
"identifier": "dispatch_deploy",
"title": "Dispatch GitHub Deployment",
"config": {
"type": "INTEGRATION_ACTION",
"installationId": "<GITHUB_INTEGRATION_ID>",
"integrationProvider": "github-ocean",
"integrationInvocationType": "dispatch_workflow",
"integrationActionExecutionProperties": {
"org": "<GITHUB_ORG>",
"repo": "{{ .outputs.trigger.service }}",
"workflow": "deploy.yml",
"workflowInputs": {
"environment": "{{ .outputs.trigger.environment }}"
},
"reportWorkflowStatus": true
}
}
}
],
"connections": [
{
"sourceIdentifier": "trigger",
"targetIdentifier": "dispatch_deploy"
}
]
}

Replace <GITHUB_INTEGRATION_ID> with your GitHub integration's installation ID (found on the Data sources page) and <GITHUB_ORG> with your GitHub organization. If your assistant does not know these values, it can save the node with deferIntegrationInstallation: true so you connect the integration later.

Iterative refinement

After creating a workflow, refine it with follow-up requests like "Add an approval step before the deployment" or "Also update the service entity with the deployed version when the deploy succeeds". The assistant updates the node graph and saves it again.

Add user inputs

For self-service workflows, inputs live in the trigger node's userInputs.properties, with a required list for the mandatory ones. Describe the input and the AI configures it.

Example conversation:

"Add a multi-select input for deployment regions with options us-east-1, us-west-2, and eu-west-1."

The AI updates the SELF_SERVE_TRIGGER node's userInputs with an array input:

{
"regions": {
"type": "array",
"title": "Deployment regions",
"items": {
"type": "string",
"enum": ["us-east-1", "us-west-2", "eu-west-1"]
}
}
}

Any later node reads the selection with {{ .outputs.trigger.regions }}. For all supported input types, see user inputs.

Configure node types and backends

Workflows perform work through action and flow nodes. Describe what each step should do and the AI picks the right node type:

Node typeUse it for
WEBHOOKCall any HTTP API (GitLab, Bitbucket, Azure DevOps, Slack, PagerDuty, or your own service).
INTEGRATION_ACTIONDispatch a GitHub Actions workflow through your installed GitHub integration.
UPSERT_ENTITYCreate or update an entity in your catalog.
CONDITIONBranch the flow based on JQ expressions.
INPUTPause for approval or extra input from one or more users.
AI / AI_AGENTInvoke Port AI or a pre-configured agent for reasoning or enrichment.
GitHub versus other providers

Integration actions currently support GitHub only, through the github-ocean provider. For GitLab, Bitbucket, Azure DevOps, and any third-party API, use a WEBHOOK node that calls the provider's pipeline-trigger or REST endpoint.

Example conversations:

  • "Add a step that dispatches the deploy.yml GitHub workflow in the ops-automation repo." becomes an INTEGRATION_ACTION node.
  • "Add a step that triggers a GitLab pipeline in the infrastructure project." becomes a WEBHOOK node.
  • "Add a step that calls our internal API at https://api.internal.com/deploy." becomes a WEBHOOK node.

Real webhook backend examples

When your assistant has web search access, it can research an API's documentation and build a complete WEBHOOK node. Here are examples:

"Add a step that calls the Jira REST API to create an issue. Include inputs for project key, issue type, summary, and description."

Resulting webhook node (Click to expand)
{
"identifier": "create_jira_issue",
"title": "Create Jira Issue",
"config": {
"type": "WEBHOOK",
"url": "https://your-domain.atlassian.net/rest/api/3/issue",
"method": "POST",
"headers": {
"Content-Type": "application/json",
"Authorization": "Basic {{ .secrets[\"JIRA_API_TOKEN\"] }}"
},
"body": {
"fields": {
"project": { "key": "{{ .outputs.trigger.project_key }}" },
"issuetype": { "name": "{{ .outputs.trigger.issue_type }}" },
"summary": "{{ .outputs.trigger.summary }}",
"description": "{{ .outputs.trigger.description }}"
}
}
}
}
Secrets belong in webhook nodes

Store credentials as organization secrets and reference them in WEBHOOK nodes with {{ .secrets["NAME"] }}. Secrets are not supported in INTEGRATION_ACTION nodes, because GitHub authenticates through your installed integration rather than a token you supply.

Build event-driven workflows

Workflows can start automatically when something changes in your catalog. Instead of a self-service form, the workflow begins with an EVENT_TRIGGER node.

Describe the trigger condition

Explain what change should start the workflow.

Example conversation:

"Create a workflow that posts to Slack whenever a service's health score drops below 50."

The AI configures an EVENT_TRIGGER on the service blueprint with a JQ condition, and reads the changed entity from the diff object:

Resulting workflow definition (Click to expand)
{
"identifier": "notify_low_health",
"title": "Notify on Low Health Score",
"nodes": [
{
"identifier": "trigger",
"title": "On Health Score Change",
"config": {
"type": "EVENT_TRIGGER",
"event": {
"type": "ENTITY_UPDATED",
"blueprintIdentifier": "service"
},
"condition": {
"type": "JQ",
"expressions": [
".diff.after.properties.health_score < 50",
".diff.before.properties.health_score >= 50"
],
"combinator": "and"
}
}
},
{
"identifier": "notify_slack",
"title": "Post to Slack",
"config": {
"type": "WEBHOOK",
"url": "https://hooks.slack.com/services/xxx/yyy/zzz",
"method": "POST",
"body": {
"text": "Service {{ .outputs.trigger.diff.after.title }} health score dropped to {{ .outputs.trigger.diff.after.properties.health_score }}."
}
}
}
],
"connections": [
{
"sourceIdentifier": "trigger",
"targetIdentifier": "notify_slack"
}
]
}
Reading event data

Event triggers expose the change through a diff object. Read the new state with {{ .outputs.trigger.diff.after.* }} and the previous state with {{ .outputs.trigger.diff.before.* }}. For ENTITY_CREATED events there is no before, and for ENTITY_DELETED events there is no after.

Common event patterns

Here are patterns you can describe to AI:

Entity lifecycle events:

  • "When a new service is created, send a welcome message to the owning team's Slack channel." uses ENTITY_CREATED.
  • "When a service is deleted, call our cleanup API." uses ENTITY_DELETED.

Property change events:

  • "When a service's status changes to deprecated, notify the platform team." uses ENTITY_UPDATED with a JQ condition.

Timer-based events:

  • "When an environment's TTL expires, mark it as expired and notify the owner." uses TIMER_EXPIRED.
Timer triggers need a property

TIMER_EXPIRED is an event type inside an EVENT_TRIGGER node. It requires a propertyIdentifier pointing at the timer property that should fire the workflow.

Chain steps into a flow

Workflows chain steps through connections. Each connection links a source node to a target node, and data flows forward through .outputs. Ask the AI to design the whole flow at once.

Example conversation:

"When a PagerDuty incident is created with high urgency, create a tracking entity in Port, post to the #incidents Slack channel, then dispatch a scale-up GitHub workflow for the affected service."

The AI builds a sequential chain: event trigger, then upsert entity, then Slack webhook, then GitHub integration action.

Resulting workflow definition (Click to expand)
{
"identifier": "critical_incident_response",
"title": "Critical Incident Response",
"nodes": [
{
"identifier": "trigger",
"title": "On Critical Incident Created",
"config": {
"type": "EVENT_TRIGGER",
"event": {
"type": "ENTITY_CREATED",
"blueprintIdentifier": "pagerdutyIncident"
},
"condition": {
"type": "JQ",
"expressions": [".diff.after.properties.urgency == \"high\""],
"combinator": "and"
}
}
},
{
"identifier": "track_incident",
"title": "Create Tracking Entity",
"config": {
"type": "UPSERT_ENTITY",
"blueprintIdentifier": "incident_tracker",
"mapping": {
"identifier": "{{ .outputs.trigger.diff.after.identifier }}",
"title": "{{ .outputs.trigger.diff.after.title }}",
"properties": {
"status": "open"
}
}
}
},
{
"identifier": "notify_slack",
"title": "Post to #incidents",
"config": {
"type": "WEBHOOK",
"url": "https://hooks.slack.com/services/xxx/yyy/zzz",
"method": "POST",
"body": {
"text": "Critical incident: {{ .outputs.trigger.diff.after.title }}"
}
}
},
{
"identifier": "scale_up",
"title": "Dispatch Scale-Up Workflow",
"config": {
"type": "INTEGRATION_ACTION",
"installationId": "<GITHUB_INTEGRATION_ID>",
"integrationProvider": "github-ocean",
"integrationInvocationType": "dispatch_workflow",
"integrationActionExecutionProperties": {
"org": "<GITHUB_ORG>",
"repo": "ops-automation",
"workflow": "scale-up.yml",
"workflowInputs": {
"service": "{{ .outputs.trigger.diff.after.relations.service }}"
}
}
}
}
],
"connections": [
{ "sourceIdentifier": "trigger", "targetIdentifier": "track_incident" },
{ "sourceIdentifier": "track_incident", "targetIdentifier": "notify_slack" },
{ "sourceIdentifier": "notify_slack", "targetIdentifier": "scale_up" }
]
}
Fan-out is not supported

Each node, and each condition option, can have at most one outgoing target. To run several steps, chain them one after another rather than branching a single node into multiple parallel targets.

// Not supported - one node pointing to two targets
{ "sourceIdentifier": "call_api", "targetIdentifier": "notify_slack" },
{ "sourceIdentifier": "call_api", "targetIdentifier": "update_entity" }

// Supported - chain the steps sequentially
{ "sourceIdentifier": "call_api", "targetIdentifier": "notify_slack" },
{ "sourceIdentifier": "notify_slack", "targetIdentifier": "update_entity" }

To branch based on a value, ask for a condition node. The AI adds a CONDITION node with named options, and each branch becomes a separate connection that carries a sourceOptionIdentifier.

Let's test it

After creating a workflow, test it with an MCP-enabled AI assistant. In this example, we run the "Deploy to Staging" workflow we built earlier.

Prompt

Ask your AI assistant:

"Trigger the 'Deploy to Staging' workflow for the checkout-service."

What happens

The agent will:

  1. Discover the workflow - Find it and its identifier with list_workflows.
  2. Inspect the inputs - Use get_workflow to read the SELF_SERVE_TRIGGER node's userInputs and determine required fields.
  3. Trigger the run - Call trigger_run with type: "WORKFLOW", the workflow identifier, and the inputs.
  4. Track the result - Poll get_run with the returned run ID and report the outcome.
Workflow runs are asynchronous

Workflow runs execute asynchronously, so the agent uses get_run to poll until the run completes. Event-driven workflows fire automatically on catalog changes and cannot be triggered manually.

Best practices for AI-driven workflow creation

When using AI to build workflows, follow these practices to get the best results.

Be specific about inputs

The more detail you provide about inputs, the better the trigger node's schema:

  • Good: "Add a trigger with inputs for environment (enum: production, staging, dev), version (string, required), and notify_slack (boolean, default true)."
  • Less effective: "Add a deploy trigger."

Specify the backend

Tell AI which backend each step should use, so it picks the right node type:

  • "...that dispatches a GitHub workflow in the deployments repo." uses an integration action.
  • "...that triggers a GitLab pipeline." uses a webhook.
  • "...that calls a webhook at https://api.example.com/deploy." uses a webhook.

Describe approval requirements

If a step needs approval, describe who approves and how many people are required:

"Add an approval step before the production deploy that requires two reviewers from the platform-admins team."

The AI adds an input node with approve and decline options.

Use clear, consistent identifiers

Use short, snake_case node identifiers with a verb-noun pattern:

  • Good: deploy_to_production, restart_service, notify_team.
  • Less clear: prod-dep, svc_rst, n1.
Hyphens break JQ expressions

Always use snake_case for node identifiers. A hyphen in an identifier is read as subtraction inside a JQ expression, so .outputs.my-node.field fails while .outputs.my_node.field works.

Reference data correctly

Keep these data-flow rules in mind when reviewing what the AI generates:

  • Reference self-service inputs and event data with {{ .outputs.trigger.* }}.
  • Reference a previous node's output with {{ .outputs.<node_identifier>.* }}.
  • Reference organization secrets with {{ .secrets["NAME"] }}, and only inside WEBHOOK nodes.
  • Calls to Port's API (https://api.port.io) are authenticated automatically, so no authorization header is needed.