Skip to content

Configure Workflow Nodes

How do you turn an action into a working workflow step? Add the action, select its account, configure its fields in dependency order, and wire its inputs. Then resolve its output type. A node can be ready to run before its output type is known.

Work in the development version, where tests call providers for real. Before testing a read, choose the account and invoice record. Before a send or a write, choose a safe destination.

In the app: choose Workflows → Invoice intake → Dev → flow → Add action. Search Actions, select the integration, then select the action.

With an assistant: list the workflows first.

Terminal window
cai workflow list --limit 100 --json

Select Invoice intake’s id from data.items as <workflowId>. If several match, ask which one. If data.hasMore is true and it is absent, open it in the app and take the ID after /workflows/ in its URL. This page pins the workflow once, so later CLI commands omit --workflow. Scoped MCP tools always require workflowId.

Pin the workflow, read its outline, and search for the action.

Terminal window
cai use <workflowId> --json
cai workflow outline --json
cai integration actions "read invoice" --json

Take the intended data.flows[].id as <flowId>. From the action result’s data.items, select the exact nodeSlug as <slug> and key as <componentKey>. Narrow the query if data.hasMore is true.

Inspect the action, then add it.

Terminal window
cai integration action --integration <slug> --key <componentKey> --json
cai node add --flow <flowId> --node <slug> --key <componentKey> --name "Read invoice" --json

MCP uses integration_action_list with query, integration_action_get with integration and key, and node_add with flow, node, key, and name.

Note the added node’s data.nodeId as <nodeId>.

In the app: choose Connection, or Connect / Add new connection to authorize an account.

With an assistant: list the connected accounts.

Terminal window
cai connection list --integration <slug> --json

Choose the intended ACTIVE account from data.items, using its id as <connectionId>. If several active accounts match and no preference exists, ask which one. If none exists, start an authorization:

Terminal window
cai connection connect <slug> --no-open --json

MCP uses connection_connect_url with nodeSlug.

Have the person authorize data.url. Then rerun the connection list and select the new ID. Bind it, reload it, and read the props back:

Terminal window
cai node set-connection <nodeId> --connection <connectionId> --json
cai node reload <nodeId> --json
cai node props <nodeId> --json

MCP uses node_connection_set with connection, node_prop_reload, and node_prop_list.

Binding checks the saved status and the integration, not the provider’s authorization. Changing the connection resets remote-option values to defaults and clears the cached field catalog. Reselect the affected values after reloading.

Configure selectors before dependent fields

Section titled “Configure selectors before dependent fields”

In the app: choose the invoice spreadsheet before its sheet and columns. In CLI data.props, inspect required, reloadProps, and option metadata. Setting a concrete value on a reload selector re-resolves the later fields, which can be added, removed, or left unchanged, and selections can be cleared. Reread the props after each selector write. node props only reads. node reload re-resolves. If a selector is unset, an explicit reload preserves the dependent fields instead of removing them.

Use the field’s name as <name> and search by the resource’s label as <q>:

Terminal window
cai node options <nodeId> --prop <name> --query '<q>' --json

Copy a matching data.options entry’s value as <literal> and label as <label>, then write it and reread the props:

Terminal window
cai node set <nodeId> --prop <name> --value '<literal>' --label '<label>' --json
cai node props <nodeId> --json

MCP uses node_prop_options with query, and node_prop_set with value and a label array, even for one selection.

In a multi-select, labels must align with values. A response carries at most 100 options. For remote options, narrow the search when data.hasMore is true, or pass the returned data.context as JSON with --prev-context. Static lists ignore query, report hasMore: false, and hide entries beyond 100 through this operation. Repeat --for '<prop>=<value>' to preview an earlier selector without saving it. MCP uses parsed prevContext and a for array.

blocked_upstream means you must set blockedBy first. dynamic_upstream means an earlier expression prevents a concrete option list, so use a raw value or an expression. available means no selector blocks the lookup, and absent metadata means unknown. Configure concrete selectors and their dependent fields before switching to expressions, because an expression preserves the last concrete selection’s dependent schema instead of discovering a new one.

In the app: enable Optional inputs, draw an edge from the producer, then select its value in the field’s expression editor. An edge makes a value available. It does not assign an action field.

node set and expr set enable an optional field automatically before writing to it. A write that fails can leave the field enabled. Disable a field only to delete its stored value. When the base field list is available, disabling a reload field also prunes dependents outside that base set.

Terminal window
cai node disable-prop <nodeId> --prop <name> --json
cai node props <nodeId> --json

MCP uses node_optional_prop_set with enabled: false.

Take the producer’s id from the outline’s selected flow’s nodes as <sourceNodeId>. Connect it, then read the field’s bindings:

Terminal window
cai node connect --flow <flowId> --from <sourceNodeId> --to <nodeId> --json
cai expr context <nodeId> --prop <name> --json

Use the intended data.inputs[].js binding as <src>:

Terminal window
cai expr set <nodeId> --prop <name> --js '<src>' --json

MCP writes expressions with node_prop_set using js. Router and If / Else require branch connections.

In the app: open Output → Action output format, select a supported format, or test representative data.

With an assistant: check readiness, then read the inputs a test needs.

Terminal window
cai node status <nodeId> --json
cai run inputs <nodeId> --json

Status reports data.readyForRun, data.readyForDownstreamExpressions, and data.blockedBy. Readiness does not prove correct wiring. For an isolated test, build <json> from the input readback, keyed by every referenced data.flowInputs[].id, with safe invoice values. If you leave one out, the test fails with ISOLATED_FLOW_CONTEXT_REQUIRED. Explicit null, empty text, false, and zero count as supplied. Upstream nodes do not execute, so supply wired-input fixtures using data.nodeInputs[].id, then verify upstream expressions with the complete-flow test below.

Terminal window
cai run node <nodeId> --inputs '<json>' --json

MCP uses run_inputs with nodeIdOrFlowId, then run_node with parsed inputs.

Inspect data.resolvedConfigs, data.output, and data.outputTypeAdoption, then reread status and destination context. Adoption belongs to that exact node, and scratch discovery cannot type it. no_observed_output means no reusable type was found.

If you prefer not to run the node, declare the shape instead. An unresolved or still-declared dynamic action output accepts a documented JSON object or non-empty array saved as invoice-response.json. Static outputs, flow calls, nodes without an output, and already-observed contracts reject a declaration.

Terminal window
cai node declare-output <nodeId> --sample-file invoice-response.json --json

MCP uses node_output_type_declare with parsed sample, not a filename.

The shape stays unverified until a completed run of the unchanged node yields a reusable type. Adoption clears the declaration marker and replaces a declared type that does not match. A no_observed_output result preserves the declaration.

A missing connection calls for authorization. Choose Send HTTP request when the catalog action cannot perform the API operation you need.

In the app: add that action and configure these fields:

FieldBehavior
Method (method)GET, POST, PATCH, PUT, DELETE, HEAD.
URL (url)Required invoice API endpoint.
Authentication (authType)None by default; Basic Auth reveals username/password; Bearer Token reveals token. Have the person enter credentials in the app, never chat.
Body Type (body_type)None by default; JSON, Raw, or Form Data reveals body. Set the selector before its body.
Timeout (in seconds) (timeout)Empty means no HTTP-client timeout; set a limit such as 30.

With an assistant: find it first:

Terminal window
cai integration actions "Send HTTP request" --json

Select its returned nodeSlug as <slug> and key as <componentKey>:

Terminal window
cai integration action --integration <slug> --key <componentKey> --json
cai node add --flow <flowId> --node <slug> --key <componentKey> --name "Read invoice API" --json

Note data.nodeId as <httpNodeId>, then set your invoice API’s GET endpoint as <literal>:

Terminal window
cai node set <httpNodeId> --prop method --value GET --label GET --json
cai node set <httpNodeId> --prop url --value '<literal>' --json
cai node set <httpNodeId> --prop timeout --value 30 --json

Set authentication to match that API. A successful request returns only the body, without status or headers. A final non-2xx response fails the action. The output starts without a fixed type, so use the adoption procedure above.

In the app: choose the flow in Dev → Test, then inspect Run history.

With an assistant: lay out the flow, validate it, and read back its inputs.

Terminal window
cai flow layout <flowId> --json
cai workflow validate --json
cai run inputs <flowId> --flow --json

Resolve validation errors first. Then build <json> from the returned data.flowInputs[].id keys, with safe values such as INV-104 and 120:

Terminal window
cai run flow <flowId> --target dev --inputs '<json>' --json

MCP uses run_inputs with nodeIdOrFlowId and flow: true, then run_flow with flowId, target, and parsed inputs.

Check data.nodeExecutions for resolved inputs, statuses, and outputs, and confirm the invoice read or the intended external effect. Inspect data.outputTypeAdoptions, and reread the downstream context after an adoption. These changes and adopted types now belong to development.