# Build Mule Integration

> Workflow required before any Mule flow and integration work. Call use_skill as your FIRST action — before reading project files — whenever the user asks to create, generate, update, fix, modify, change, edit, tweak, adjust, or rework any Mule flow, sub-flow, or component. Do not read project files and attempt the change yourself — even targeted single-component changes like 'modify the choice router', 'fix the until-successful', or 'update the catch block' require this workflow. Covers all change types, new integrations and targeted changes to error handlers, catch blocks, choice routers, DataWeave transforms, HTTP listeners, foreach loops, retry policies, scatter-gathers, connectors, and variable assignments. Prompts beginning with 'This code defines...' or 'This flow...' are generation requests, not analysis. When you call this skill, it must be the only tool call in that response.

- Skill: `mulesoft/build-mule-integration` (Agent Skill, multi-file: 16 files)
- Install (CLI): `npx skillmds@latest add mulesoft/build-mule-integration`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mulesoft/build-mule-integration/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- License: Apache-2.0
- Author: mulesoft (https://skillmd.com/u/mulesoft)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mulesoft/build-mule-integration

---


# Mule Developer

Build professional Mule integrations with intelligent connector discovery and data-driven XML generation.

## When to Use This Skill

**Use this skill when users request:**

- "Create a Mule app/integration/flow"
- "Build integration between X and Y"
- "Sync data from X to Y" (e.g., "Salesforce to Slack", "MySQL to Salesforce")
- "Query Salesforce", "Send Slack notifications"
- "Schedule jobs", "Poll data every N minutes"
- "Create webhooks", "Build event-driven flows"

**Trigger keywords:** create, build, integrate, sync, connect · mule, integration, flow, app, project · salesforce, slack, servicenow, jira, netsuite · mysql, postgresql, database · api, http endpoint, rest api · schedule, poll, every N minutes · alert, notify, webhook.

Always search Exchange for system-specific connectors first. Most SaaS applications have dedicated connectors.

---

## Prerequisites

```bash
anypoint-cli-v4 --version
anypoint-cli-v4 dx --help
echo $JAVA_HOME && java -version   # Java 11+
anypoint-cli-v4 conf
```

If tools are missing:

```bash
npm install -g @mulesoft/anypoint-cli-v4
npm install -g @salesforce/anypoint-cli-dx-mule-plugin
anypoint-cli-v4 conf username <username>
anypoint-cli-v4 conf password <password>
```

---

## Bundled scripts

This skill ships small bash scripts under `scripts/`. Invoke them with the `Bash` tool — do not inline their contents into a response. The scripts persist their output to disk so later steps can consume it mechanically and are not at the mercy of shell variables that vanish when a Bash tool call returns:

| Script | Purpose | Output location |
| --- | --- | --- |
| `scripts/validate_prerequisites.sh` | Step 1 — validate toolchain (CLI, DX plugin, Java 11+, JAVA_HOME, Mule runtime presence). Validation-ONLY | `tmp/mule-dev-env.json` (workspace-relative; contains `mule_version`, `runtime_path`, `errors[]`, ...) |
| `scripts/get_latest_connector.sh <search> [nickname]` | Step 3 — search Exchange and print ranked connector candidates (`groupId:assetId:version`, one per line, no score, no winner cue). Writes nothing. | stdout only |
| `scripts/pick_connector.sh <nickname> <gav>` | Step 3 — record the agent's chosen GAV (after reasoning or an `AskUserQuestion`) as a draft | `tmp/connector-choices/<nickname>.json` (`{groupId, assetId, version}`) |
| `scripts/commit_connectors.sh` | Step 8 (post-TDD-approval) — promote every draft under `tmp/connector-choices/` to the pinned `tmp/connector-versions/` directory that Phase 2 reads | `tmp/connector-versions/*.json` |
| `scripts/build_gav.sh <json>` | Turn a saved connector JSON into its `groupId:assetId:version` string | stdout |
| `scripts/build_deps.sh [versions-dir]` | Step 8 — read every connector pin in `tmp/connector-versions/` and emit a comma-joined GAV string, ready for `dx mule project create --dependencies`. Skips `db-driver.json` and any non-pin file. | stdout |
| `scripts/describe_connector.sh <nickname> [--type operation\|source --name <name>]` | Step 4 (no flags) / Step 13 (with flags) — run `dx mule describe-connector` for the drafted GAV, save full JSON, echo digest to stdout, AND cache `.errorTypes` to `tmp/connector-errors/` for the Step 16 validator | `tmp/connector-metadata/<nick>[-<name>].json` + `tmp/connector-errors/<nick>[.<name>].json` + digest on stdout |
| `scripts/validate_before_build.sh <project-dir>` | Step 16 pre-mvn — error-type whitelist (Cluster D), namespace↔dependency parity (Cluster A2–A5), canonical XSD URL shape | stderr + non-zero exit on first violation |
| `scripts/maybe_add_http_connector.sh --project <dir> <providers...>` | Phase 2 — defensive check that HTTP connector is present when OAuth providers were chosen; edits `<dir>/pom.xml` | `<dir>/pom.xml` |
| `scripts/search_templates.sh <search-term>` | Step 1b (template path) — search Anypoint Exchange for `type == "template"` assets via parallel calls (unscoped + org-scoped), dedup, rank by token overlap, enrich top 10 with `description`, `minMuleVersion`, and `sourceLocation`. | Single JSON array on stdout (max 10 rows, private-first). Exits 1 on no match. |

Invoke scripts by the absolute path you were given in the "skill is now active" message (it is the directory containing this `SKILL.md`). Do **not** construct relative paths like `../scripts/...` — Cline's working directory shifts across turns and relative paths have produced "No such file or directory" errors in real runs. The inline step examples below write `scripts/...` as shorthand; substitute `<skill-dir>/scripts/...` when you actually execute them.

**Why scripts instead of inline bash:** in earlier iterations connector search was a shell *function* defined inside a single `Bash` tool call. When the call returned the subshell died and the resolved GAV went with it. By the time a later step assembled `dx mule project create`, the only trace of the version was in scrolled-past tool output — and the agent frequently pasted a fictional version from training-time memory instead. Persisting to a file on disk makes the version something we can `jq` at the command site, which removes that failure mode entirely.

---

## Workflow shape (two phases)

This workflow has two phases separated by a hard user-approval gate.

- **Phase 1: Technical Design (Steps 1–7).** Identify systems, search Exchange, describe connectors, pick trigger and providers, present a Technical Design Summary, wait for the user to approve. Phase 1 writes **nothing** to the user's project directory — all artifacts live under workspace-relative paths: `tmp/mule-dev-env.json` (env cache owned by `validate_prerequisites.sh`), `tmp/connector-choices/*.json` (draft connector picks), and `tmp/connector-metadata/*.json`. The pinned `tmp/connector-versions/*.json` directory that Phase 2 reads is only populated after Step 7's approval, by `commit_connectors.sh`.
- **Phase 2: Build (Steps 8–17).** Create the real project, generate config and flow XML, run the build, declare completion. Phase 2 is the only phase that touches the user's project directory.

Phase 2 MUST NOT start until Step 7's approval gate has been passed explicitly. Skipping Phase 1 — or collapsing it into a single "I'll just use HTTP" decision — is the single highest-impact failure mode of this skill and is what the two-phase structure exists to prevent.

## Workflow-wide discipline (read before Phase 1)

- **Build → cleanup → completion separation (Step 16 → Step 17 → Step 18).** Three responses, in order, each with a single tool call: `mvn clean package`, then `rm -r tmp/`, then the completion signal. Do not bundle them. Wait for each result before moving on.
- **One mvn invocation per response.** When re-running a build after a fix, emit only the `mvn` command in that response. Do not bundle it with further edits, follow-up shell commands, or the completion signal.
- **"Completion" means the build already passed.** You may only declare completion after a response that ran `mvn clean package` came back with `BUILD SUCCESS`.
- **Connector versions come ONLY from the Step 3 flow.** Never paste a version from `references/connector-catalog.md`, from training-time memory, or from extrapolation. Step 3 is a three-script dance: `get_latest_connector.sh` lists ranked candidates (stdout only, no pin file), `pick_connector.sh <nickname> <gav>` records the chosen row as a draft in `tmp/connector-choices/`, and `commit_connectors.sh` (Step 8, first action after TDD approval) promotes every draft to `tmp/connector-versions/`. Every GAV that reaches `dx mule project create --dependencies` or `pom.xml` must be pulled from a `tmp/connector-versions/*.json` file via `scripts/build_deps.sh` (for the full `--dependencies` string at Step 8) or `scripts/build_gav.sh` (for a single connector's GAV elsewhere in Phase 2). The catalog's versions are snapshots that drift — treat it only as a connector-identity reference, not as a version source.
- **The agent does the picking, not the script.** `get_latest_connector.sh` deliberately emits a plain ranked list with no score, no emoji, and no "winner" signal. When the list has one row the choice is obvious. When it has several rows the agent must decide which one matches the user's stated system — and if the rows represent real variants of the same family (Slack `mule4-slack-connector` vs `mule-slack-connector`; FTP vs FTPS; Dynamics 365 vs Dynamics GP/NAV/BC; IBM MQ vs Solace vs JMS), the decision belongs to the user via `AskUserQuestion`, not to the agent's guess. The cost of one extra prompt is one turn; the cost of a silent wrong variant is a full Phase-2 rewrite.
- **No HTTP fallback without evidence.** You may only classify a system as "no dedicated connector exists, use HTTP" AFTER `scripts/get_latest_connector.sh <system>` has run AND returned zero matches (exit 1) OR every row in the ranked list is obviously a different product (no assetId shares tokens with the system name beyond noise words like `mule`/`connector`). Declaring HTTP as the answer before the search has run is forbidden. Exchange carries dedicated connectors for hundreds of SaaS products that are easy to miss when reasoning from training-time knowledge alone — the helper script is the authoritative check. A dedicated connector gives metadata discovery, typed operations, and correct authentication; HTTP gives raw request plumbing the user would then have to wire up by hand, so quietly falling back to HTTP is a real loss, not a neutral choice. Note that any ranked row whose `groupId` is a UUID (e.g. `a50e4364-a38c-4340-b05a-c1f8ebed0748:…`) is a connector the user's organization has published privately to Exchange — treat these as first-class candidates, not noise. An org that took the trouble to publish a private connector usually wants it used, often because it wraps internal endpoints, custom auth, or a golden-path the public connector can't cover.

---

# Phase 1: Technical Design

## Step 1: Validate Prerequisites

Run the prerequisite validation script. It only handles validation. It writes the prereq validation findings to `tmp/mule-dev-env.json` in the current workspace; Step 8 reads `mule_version` from there.

```bash
bash scripts/validate_prerequisites.sh
```

If the script exits non-zero, STOP progressing any further in the skill and inform the user to act on the `errors` array in `tmp/mule-dev-env.json`. Do not invent a fallback Mule version — `mule_version` is empty when no runtime is detected.

What `validate_prerequisites.sh` checks: Anypoint CLI v4 installed · DX plugin available · `JAVA_HOME` set · Java 11+ · Mule runtime detected at `~/.mule-dx/config.json:.runtimePath` or under `~/AnypointCodeBuilder/runtime/mule-*`. If runtime or other tools are missing, the script reports the error and informs the user to run the necessary commands for proper installation.

---

## Step 1b: Project Source Decision

**Always ask this when creating a new project.** After prerequisites pass, determine how the user wants to create the project:

```xml
<ask_followup_question>
<question>I can create this project in one of three ways. Which would you prefer?</question>
<options>["I want to generate from scratch without a template.", "I want to use organizational templates from Anypoint Exchange.", "I have a local template file (.jar or .zip) I want to use."]</options>
</ask_followup_question>
```

**Based on the answer:**

1. **Exchange or Local template** → Read `references/template-project-creation.md` and follow the appropriate flow (Exchange E1–E4, or Local L1–L4). The sub-skill ends with an explicit **continue-or-stop** prompt (Steps E5 / L5) — if the user picks "stop", declare completion via `<attempt_completion>`; if they pick "continue", return here and proceed to Step 2.
2. **Scratch** → Continue to Step 2. Project creation will happen at Step 8 (after connector discovery provides the `--dependencies`).

**When to skip this step entirely:**
- User is working on an existing project (modifying/updating flows, not creating a new one)

---

## Step 2: Identify Systems and Trigger Hints

**[BLOCKER] Step 2 MUST NOT prompt the user.** Do not emit an `<ask_followup_question>` or `<AskUserQuestion>` here. The trigger decision happens in Step 5 after connector metadata is on disk — prompting now would force generic options (HTTP Listener / Scheduler) instead of real connector sources, which is the single highest-impact anti-pattern this workflow exists to prevent.

Produce two records in your response text. These are plain prose — not a thinking block, not a tool call — so later steps (and the user) can read them.

**1. Systems list.** Identify **EXACT system names**: source systems (where data comes from), target systems (where data goes to). Use specific names (Slack, Jira, ServiceNow, Stripe, Shopify), NOT generic terms (chat, ticketing, payments, commerce). Every name on this list will be searched in Step 3; every search must result in a `tmp/connector-choices/<nick>.json` draft on disk (the agent picks from the ranked list and calls `pick_connector.sh`).

**Anti-pattern — inferring a backend from a destination name.** When the prompt mentions a queue, topic, bus, or similar messaging destination, the string that names the destination (e.g. `foo.queue`, `orders.topic`, `events-stream`) is a *label*, not a technology. It does NOT identify which broker is behind it. Do NOT add a specific broker (Anypoint MQ, Kafka, IBM MQ, Solace, SQS, etc.) to the Systems list unless the prompt names it explicitly. If the prompt only says "queue" / "topic" / names a bare destination, list the system as `messaging broker (backend unspecified)` and plan to escalate in Step 3 — the user picks the backend, not the agent. Why this matters: a silently-chosen broker anchors Phase 2 against the wrong connector family, which is a full Phase-2 rewrite to correct.

**2. Trigger hints.** In one or two sentences, note the **verbatim phrases** from the user prompt that describe what starts the flow or names a cadence — e.g. *"every 3 seconds"* or *"listens for new Stripe charges"* or *"makes a GET request to retrieve customers"*. Do NOT classify yet. No class label, no decision tree, no "so the trigger is…". Step 5 does the trigger decision from connector metadata that does not exist yet; committing to a trigger now — even implicitly via a class — tends to anchor the agent against the real `sources[]` that Step 4 will produce.

If the prompt mentions no trigger or only describes outbound work ("makes a request", "fetches", "calls"), say so explicitly: "No explicit trigger phrase — outbound-only description." Step 5's metadata-first ladder handles this.

**Connector strategy per system:**

- **Major SaaS platforms** (Salesforce, ServiceNow, NetSuite, Workday) → search for dedicated connector in Step 3.
- **Standard protocols** (Database, JMS, FTPS, SFTP) → search for protocol-specific connector in Step 3.
- **Mid-market SaaS** (Stripe, Shopify, HubSpot, Twilio, Plaid, etc.) → search for dedicated connector in Step 3. Do NOT assume these have no connector — Exchange has dedicated connectors for many of them. Training-data intuition that "Stripe/Shopify/etc. is a REST-API-only system" is unreliable; the search is the authority.
- **Queue or Pub/Sub with a named backend** ("Kafka topic", "IBM MQ", "SQS", "Solace") → search for that specific connector in Step 3.
- **Queue or Pub/Sub without a named backend** (the prompt uses "queue", "topic", or a bare destination name without naming the broker technology) → per the Step-2 anti-pattern above, the destination name is not the backend. In Step 3, escalate via `AskUserQuestion` to let the user pick the backend (JMS via any broker provider, Kafka, IBM MQ, Solace, SQS, etc.). Only if the user declines to choose should you default to `mule-jms-connector` — JMS is the generic protocol layer that fits any broker, with the broker selected in Step 6 via the connection provider (active-mq, active-mq-nct, generic).
- **Unknown/Unclear / custom internal APIs** → still search Exchange first in Step 3; HTTP is the fallback only when Step 3's search returns nothing plausibly related.

Your next tool call after Step 2 MUST be `get_latest_connector.sh` for a system from your Systems list — NOT an `ask_followup_question`, NOT a describe-connector, NOT anything else. Step 3 is the non-negotiable next step.

---

## Step 3: Search Exchange for Connectors, Decide, Draft the Choice

Step 3 is a three-move loop run **once per named system** from Step 2:

1. **List** candidates with `get_latest_connector.sh`.
2. **Decide** which row is the right fit — inline rationale if the choice is obvious, `AskUserQuestion` if the rows are real variants of the same system family.
3. **Draft** the chosen GAV with `pick_connector.sh`. The draft lands in `tmp/connector-choices/<nick>.json` and stays there through Phase 1.

The script does not pin a winner. There is no emoji, no score, no "Picked" line in its output. When the list has one row the shape of the output is "one row"; when it has several the shape is "several rows" and that is the cue to read the names and reason about intent — or to escalate.

**Mandatory search rule.** Run `get_latest_connector.sh` for EVERY named system from Step 2 — including systems whose prominence in your training data leads you to assume they have no dedicated connector. Declaring "system X has no dedicated connector" without the script having run is forbidden. This is the rule that prevents silent HTTP fallback — see "No HTTP fallback without evidence" in the workflow-wide discipline.

**Term breadth.** Always prefer the broader system name (`databricks`, `aws-storage`, `snowflake`, `database`, `salesforce`) over a narrow suffix-pinned term like `mule-amazon-s3-connector` or `mule-db-connector`. Narrow terms can miss org-private connectors whose assetId shares no tokens with the public connector's name — e.g. searching `mule-amazon-s3-connector` won't surface a private `mule-plugin-aws-storage-api`, and `mule-db-connector` won't surface a private `<uuid>:mule-plugin-customer-warehouse-database`. The "Common search terms" table further down is a starting hint for well-known systems, not a constraint — if the broader term is noisy, you can always re-run with a narrower one.

**Version source-of-truth rule (MANDATORY):**

- The **only** acceptable source for a connector's version number is `get_latest_connector.sh` run against live Exchange in the current session, recorded via `pick_connector.sh`, and later promoted to `tmp/connector-versions/` by `commit_connectors.sh`. This applies equally to `dx mule project create --dependencies`, `pom.xml` `<dependency>` blocks, and every other place a version appears.
- **Do not** paste a version from `references/connector-catalog.md`. The catalog exists to help identify *which* connector to use (asset ID, purpose); its version numbers are best-effort snapshots that drift.
- **Do not** invent a version from memory or by extrapolating. Version numbers on Exchange are not predictable.
- **Do not** write a version before the corresponding `tmp/connector-choices/<name>.json` draft exists on disk.

### Move 1 — list

One invocation per named system. The nickname is how the draft and later pin will be named, so pick something short and use it consistently:

```bash
bash scripts/get_latest_connector.sh mule-salesforce-connector sfdc
```

Stdout shape, one GAV per line, ranked best-guess-first:

```
com.mulesoft.connectors:mule-salesforce-connector:11.1.0
com.mulesoft.connectors:mule-salesforce-analytics-connector:4.1.0
com.mulesoft.connectors:mule-salesforce-composite-connector:2.5.2
com.mulesoft.connectors:mule-salesforce-marketing-cloud-connector:5.0.0
```

Exit 0 means at least one row was returned; exit 1 means no Mule 4 extension matched and you need to treat this system as HTTP-fallback territory (see the workflow-wide discipline).

### Move 2 — decide & confirm

Read the list. Two cases, in order:

**Case A — one row.** Only one Mule 4 extension matches. Acknowledge the choice inline in one sentence ("Only match for HTTP: `org.mule.connectors:mule-http-connector:1.11.1`") and go to Move 3.

**Case B — multiple rows that are real variants of the same system family.** Rows whose assetId does not plausibly name the user's system are noise — filter them out logically. If more than one row/choice remains after the logical filtering for the user's system (e.g., Salesforce query returned `mule-salesforce-connector` plus three Salesforce sub-products for Analytics / Composite / Marketing Cloud etc.), always escalate via `AskUserQuestion`. Some common variant families that requires user confirmation:

- `slack` → `mule-slack-connector` (community, v4.x) **vs** `mule4-slack-connector` (premium MuleSoft, v2.x). Different OAuth shapes, different operations — neither is a "newer version" of the other.
- `ftp` → `mule-ftp-connector` (plain) **vs** `mule-ftps-connector` (TLS). Always confirm and suggest FTPS for best security practices.
- `ibm-mq` vs `solace` vs `kafka` vs `jms` — a destination name in the prompt (e.g. `foo.queue`, `bar.topic`) does NOT identify the backend. Unless the prompt explicitly names the broker technology, escalate with these as options; `mule-jms-connector` is the generic-protocol fallback when the user declines to pick.
- `microsoft-dynamics-*` — 365, GP, NAV, 365-Business-Central, CRM are different products the user may have meant interchangeably.
- `oracle-ebs` vs `oracle-ebs-122` — alternate EBS major versions.
- Any pair whose assetIds share the target system's name and differ only in a variant suffix, protocol marker, major-version marker, or the `mule4-` vs `mule-` prefix.
- A public connector + a private (UUID-groupId) connector for the same system family — e.g. `com.mulesoft.connectors:mule4-snowflake-connector` and `<uuid>:mule-plugin-snowflake-sys-api-json` both showing up in a `snowflake` search. The org-published asset usually exists for a reason (custom auth, internal endpoints, a wrapper around the public connector), so NEVER silently pick the public one when a private alternative is in the same ranked list — surface both with `AskUserQuestion` and let the user choose.

Prompt shape when you escalate:

```xml
<ask_followup_question>
<question>Two Mule 4 Slack connectors exist on Exchange. Which should this integration use?</question>
<options>[
  "org.mule.connectors:mule-slack-connector:4.3.2 — community Slack connector",
  "com.mulesoft.connectors:mule4-slack-connector:2.0.1 — MuleSoft premium Slack connector",
  "Other — describe which Slack variant you need"
]</options>
</ask_followup_question>
```

When more than one choice exists, confirm with user; a silent wrong variant costs a Phase-2 rewrite.

If the user's prompt explicitly names one variant ("use the Dynamics 365 connector, not BC", "the premium Slack connector only"), that pins the choice and you proceed without asking.

### Move 3 — draft

Record the chosen GAV as a draft. Idempotent — you can re-run with a different GAV if Step 4 or Step 5 metadata reveals a better fit:

```bash
bash scripts/pick_connector.sh sfdc   com.mulesoft.connectors:mule-salesforce-connector:11.1.0
bash scripts/pick_connector.sh slack  com.mulesoft.connectors:mule4-slack-connector:2.0.1
bash scripts/pick_connector.sh jms    org.mule.connectors:mule-jms-connector:1.10.3
# → tmp/connector-choices/{sfdc,slack,jms}.json now each contain {groupId, assetId, version}
```

If you realize after Step 4's metadata digest that the draft is wrong, re-run `pick_connector.sh` with the corrected GAV. Drafts remain mutable until Step 8's `commit_connectors.sh` promotes them.

**Why drafts instead of pins during Phase 1.** If a connector choice bakes into `tmp/connector-versions/` before the user has seen the Technical Design Summary, the agent — and every downstream check — treats it as settled. Holding the choice as a draft until TDD approval keeps the whole design reversible, and lets the approval gate be the real commitment point.

### Selection rules the script applies internally

(Reference only — you don't need to reimplement them. The list is ordered best-guess-first; treat ordering as a soft hint, not a directive.)

- Only `"type": "extension"` (Mule 4 compatible) — Mule 3 `type=connector` assets, templates, examples, and rest-apis are filtered out.
- Any groupId whose asset is `type="extension"` is admissible; ranking keeps first-party connectors on top via a 3-tier preference (`com.mulesoft.connectors` > `org.mule.connectors` > other).
- Latest semantic version within each `(groupId, assetId)` group.
- Token-overlap scoring with the search term; the premium groupId and shorter assetId break ties. Scores are used for ordering only and are never emitted.
- Two pages fetched in parallel (offsets 0 and 200) so broad searches like `salesforce` don't drop candidates off a single page.

### Common search terms

These are starting hints for well-known systems. For anything else — and especially for any system you suspect the user's org may have a private connector for — search by system name first.

| System | Search term |
| --- | --- |
| Salesforce | `salesforce` |
| Database | `database` |
| HTTP | `http` |
| NetSuite | `netsuite` |
| ServiceNow | `servicenow` |
| Amazon S3 | `amazon` |
| JMS | `jms` |
| Slack | `slack` (returns both Slack variants — this is the ambiguous case above) |

For any system not in the list, search dynamically with the system name (e.g. `stripe`, `shopify`, `hubspot`, `databricks`, `snowflake`) — don't assume naming patterns and don't assume the system has no connector. Broader system-name terms also surface any private (UUID-groupId) connectors the user's organization has published in the same domain, which a narrow `mule-<name>-connector` term tends to miss.

---

## Step 4: Describe Connectors

For each connector resolved in Step 3, retrieve its full metadata and **read the digest** that the wrapper script prints. Use `describe_connector.sh` rather than writing the `describe-connector` pipeline by hand — the wrapper resolves the probe path, saves the full JSON to disk for later steps, AND echoes `sources[]` and `configs[]` to stdout so you see what Step 5 will need:

```bash
bash scripts/describe_connector.sh sfdc       # nickname from Step 3
bash scripts/describe_connector.sh stripe
bash scripts/describe_connector.sh http
```

Each invocation writes `tmp/connector-metadata/<nickname>.json` (full response, consumed again by Phase 2) and prints a digest shaped like this:

```json
{
  "namespace_prefix": "stripe",
  "sources": [
    "on-canceled-subscription-listener",
    "on-new-charge-listener",
    ...
  ],
  "configs": [
    { "name": "config", "providers": ["api-key"] }
  ],
  "operations_count": 335,
  "operations_sample": ["createV13dSecure", "..."]
}
```

**Read the `sources[]` array that comes back.** That list is the set of real native triggers the connector supports; it is what Step 5 branches on. Do not skip past the digest straight to the next `describe_connector.sh` call — Step 5's trigger decision depends on you knowing which sources each connector exposes, and the digest is the cheapest place to get that information.

The script also writes `tmp/connector-errors/<nickname>.json` with the connector-wide error-type whitelist. The Step 16 validator reads this directory; you don't need to invoke it manually.

If you ever need the full response (e.g. to introspect `childElements[]` for `oauth-callback-config`), read `tmp/connector-metadata/<nickname>.json` directly.

**Manual fallback** — if for some reason the wrapper is unavailable, you can reproduce it by hand. In Phase 1 the draft file is authoritative; `build_gav.sh` accepts either location:

```bash
NODE_NO_WARNINGS=1 anypoint-cli-v4 dx mule describe-connector \
  --connector "$(bash scripts/build_gav.sh tmp/connector-choices/sfdc.json)" \
  --output json > tmp/connector-metadata/sfdc.json
jq '{namespace: .namespace.prefix, sources, configs: [.configs[] | {name, providers: [.connectionProviders[]?]}]}' tmp/connector-metadata/sfdc.json
```

But in the common case prefer the wrapper — it is one line instead of three and it makes the sources list visible in your turn's tool output without a follow-up call.

---

## Step 5: Select Trigger

Every top-level flow (that is not a sub-flow or a `<flow-ref>` target) needs exactly ONE trigger. Step 5 decides *which* trigger by letting connector metadata drive the choice — not prompt-text intuition.

**[BLOCKER] Explore-before-decide gate.** Before committing to a trigger — whether inline or via `AskUserQuestion` — both of the following must be true for EVERY named system from Step 2's Systems list:

1. `tmp/connector-choices/<nick>.json` exists on disk (Step 3, via `pick_connector.sh`).
2. `tmp/connector-metadata/<nick>.json` exists on disk (Step 4).

AND you must have the `sources[]` content in view. Run:

```bash
for f in tmp/connector-metadata/*.json; do
  echo "--- $f ---"
  jq '{namespace: .namespace.prefix, sources}' "$f"
done
```

in the response that begins Step 5, and read the output. This is the same data the `describe_connector.sh` digest already showed per connector in Step 4, but re-echoing it here puts every connector's sources side-by-side in one place right before the decision. Do not commit to a trigger without having those lists in the current tool output — past turns scroll out of context quickly.

Why this gate exists: if the agent commits to a trigger before reading `sources[]`, the usual failure mode is to default to `http:listener` (treating the prompt as a webhook) and silently ignore a real connector source that Step 4 just fetched. A connector's `sources[]` array is the authoritative list of triggers it supports; Step 5 must branch from that list, not from prompt-text intuition.

### Decision ladder (evaluate in order)

Work through the rungs below in order. Each rung is one of the possible *paths* — there is no "fallback" ranking; the first path whose preconditions all match is the one you take.

#### Rung 1 — Connector-source path

For each connector in scope, examine `sources[]` from the digest. For any source whose **name** plausibly relates to the user's stated need (noun match: "product", "order", "charge", "customer"; AND verb-prefix consistency: `on-new-*`, `on-updated-*`, `on-modified-*`, `on-*-arrived`, `poll-*`, `*-listener`, `*-trigger`), inspect its shape via the unified `describe-connector` command with `--type source`:

```bash
NODE_NO_WARNINGS=1 anypoint-cli-v4 dx mule describe-connector \
  --connector "$(bash scripts/build_gav.sh tmp/connector-choices/<nick>.json)" \
  --type source \
  --name <source-name> \
  --output json
```

Do **not** call `source-detail` on every source — only on those whose name plausibly fits the user's intent per the noun+verb-prefix check above. On rich connectors this is the difference between 1–2 CLI calls and 7+ (Shopify, Salesforce, etc.).

Compare the returned **shapes** — not the names alone — to the user's intent:

- If the source's `childElements[]` includes a `scheduling-strategy` element, this is a **polling source** — the connector itself handles the cadence natively. When the user's prompt names a cadence ("every N", "daily", "hourly") AND the matched source is a polling source, the cadence goes inside the source's `<scheduling-strategy>` child. **Do NOT introduce a separate top-level `<scheduler>` alongside it.** Example correct shape:
  ```xml
  <shopify:on-updated-product-trigger config-ref="shopifyConfig">
      <scheduling-strategy>
          <fixed-frequency frequency="3000" startDelay="5000"/>
      </scheduling-strategy>
  </shopify:on-updated-product-trigger>
  ```
- If the source has no `scheduling-strategy` child and exposes an event (`on-new-*`, `on-modified-*`), it's an **event source** — use it directly; no top-level scheduler needed.
- If the source's shape includes `listenerConfig` references, it's a **webhook receiver** — suitable for "receive callback at endpoint" prompts.

**Commit inline** to a connector source when exactly one source's shape fits the user's intent and the match is explainable in one sentence. State the choice with a one-line rationale citing the source name.

**Prompt via `AskUserQuestion`** when two or more sources both pass the shape check against the user's intent, OR when the user's language is genuinely ambiguous about which source semantics they want. Options list the real source names, not generic placeholders:

```xml
<ask_followup_question>
<question>Which Salesforce event should trigger this flow?</question>
<options>[
  "salesforce:replay-topic-listener — subscribe to a Salesforce streaming topic",
  "salesforce:modified-object-listener — fire when a record of a given sObject is modified",
  "Other — pick a different source"
]</options>
</ask_followup_question>
```

If no source passes the shape check against any connector in scope, move to Rung 2.

#### Rung 2 — Generic scheduler path

Take this path when:

- No connector source fits the user's intent (Rung 1 examined the candidates and none matched), AND
- The prompt names a cadence ("every N", "daily", "hourly", "poll every…"), AND
- The flow body will call connector operations (not event-driven).

Use `<scheduler>` with `<fixed-frequency>` or `<cron>`. State the choice inline.

**Record the rejection.** When taking this path, note in one sentence why Rung 1's sources were rejected — e.g. "Shopify's `on-updated-product-trigger` matches 'every 3 seconds' but the user wants to pull by custom date range not 'updated since last poll', so a generic `<scheduler>` + `shopify:product-list` is more appropriate." Step 7's TDD requires this list; capture it now while the reasoning is fresh.

#### Rung 3 — HTTP Listener path

Take this path when:

- The prompt explicitly says "expose endpoint", "receive HTTP request", "provide REST API", "webhook at /path", AND
- No connector source in scope is a webhook-style receiver.

Use `<http:listener>`. State the choice inline. Record which connector sources were considered and why they were rejected (see Step 7).

#### Rung 4 — Ask the user

Take this path when none of Rungs 1–3 clearly apply — e.g. the prompt is outbound-only ("makes a request", "fetches", "retrieves") with no cadence and no endpoint language. `AskUserQuestion` with options derived from the actual `sources[]` of connectors in scope PLUS Scheduler and HTTP Listener:

```xml
<ask_followup_question>
<question>The prompt describes outbound calls but does not name a trigger. What should start this flow?</question>
<options>[
  "Scheduler — run on a time-based schedule",
  "HTTP Listener — receive an inbound HTTP request",
  "<connector>:<source-name> — native event from one of the connectors in scope (list any that apply based on sources[])",
  "Other — please describe"
]</options>
</ask_followup_question>
```

### After the decision

Record the selected trigger, its owning connector (if any), and — if the path is Rung 2 or Rung 3 — the list of connector sources that were considered and one-line reasons each was dismissed. Step 7's TDD surfaces this list; if it is missing, the TDD is incomplete and Phase 2 cannot start.

**[BLOCKER] WAIT for the user's response before moving to Step 6 when this step prompts.**

---

## Step 6: Select Connection Providers

**Ask the user only when there is an actual choice to make.** For each connector, look at the `configs[]` metadata captured in Step 4 — specifically the `connectionProviders` list of the config that owns the operation you intend to call in Phase 2.

**Decision rule:**

- **Multiple configs or multiple providers** → **MUST** use `AskUserQuestion`. The user's choice determines both which `config-name` and which `--connection-provider` you pass to Step 11's `config-detail` call, and which XML structure you write in Step 12.
- **Exactly one config and exactly one provider** → **DO NOT** prompt. State the choice inline in one line ("Using `s3:config[connection]` — the only option provided by the connector") and proceed. Prompts that offer a single "option" look like pointless ceremony and waste a conversation turn.

**Worked examples from live Exchange metadata:**

| Connector | `configs` × providers | Action |
| --- | --- | --- |
| S3 connector | `config[connection]` | Announce, do NOT prompt |
| VM connector | `config[connection]` | Announce, do NOT prompt |
| HTTP connector | `listener-config[listener-connection]`, `request-config[request-connection]` | Configs map 1:1 to listener vs request — determined by the flow shape (trigger vs outbound call), not a user preference. Announce, do NOT prompt. |
| A multi-config connector with stream vs. non-stream configs | `config[basic, role]`, `streams-config[streams]` | **Prompt** — pick the config whose operations match the use case, and if that config has >1 provider, pick a provider. |
| A connector offering basic / OAuth / JWT / client-credentials | `config[basic, oauth-user-pass, jwt, oauth-client-credentials]` | **Prompt** — real alternatives with different credential models. |
| Database connector | `config[my-sql-connection, oracle-connection, data-source-connection, generic-connection, ...]` | **Prompt** for the provider, then resolve the JDBC driver GAV in the same step (see "Step 6b — JDBC driver resolution" below). Step 9 is a mechanical `pom.xml` edit. |

**When you do prompt,** present only the real alternatives (don't pad with "if unsure..." copy). Example:

> This connector offers four connection providers. Which should this integration use?
> - `basic` — username + password + security token
> - `oauth-user-pass` — OAuth with user credentials
> - `jwt` — JWT bearer token (server-to-server)
> - `oauth-client-credentials` — OAuth client credentials

**Do not offer a "recommendation" as one of the options** if it's really the only option. If there is only one choice, do not ask.

Store the selected `(config-name, connection-provider)` pair for each connector, and **persist the `describe-connector` connection-provider output** for Phase 2 so it doesn't have to re-invoke the CLI. **Flag semantics note:** `--name` carries the **connection provider** name, `--config-name` carries the **config** name — easy to get backwards:

```bash
NODE_NO_WARNINGS=1 anypoint-cli-v4 dx mule describe-connector \
  --connector "$(bash scripts/build_gav.sh tmp/connector-choices/sfdc.json)" \
  --type connection-provider \
  --name basic-connection \
  --config-name sfdc-config \
  --output json > tmp/connector-metadata/sfdc-config.json
```

### Step 6b — JDBC driver resolution (only if `mule-db-connector` is in scope)

`mule-db-connector` is the one case where the Step-6 provider choice needs more clarification. The provider choice only handles the XML connection element (`<db:my-sql-connection>`, `<db:generic-connection>`, etc.), but the **JDBC driver JAR** is a separate Maven artifact that must ship alongside the connector via `<sharedLibrary>`. Resolve both in Step 6 so Step 7's design summary can show a full `groupId:artifactId:version` for the driver and Step 9 becomes a mechanical `pom.xml` edit with no further prompting.

**Branch on the Step-6 provider answer:**

| Provider picked | Driver auto-pin from the canonical table (Step 9) | Prompt? |
| --- | --- | --- |
| `my-sql` | `com.mysql:mysql-connector-j:8.4.0` (8.4 LTS; 9.x requires JDK 21) | No — announce inline |
| `oracle` | `com.oracle.database.jdbc:ojdbc11:23.9.0.25.07` (Java 17+) | No — announce inline |
| `mssql` | `com.microsoft.sqlserver:mssql-jdbc:13.4.0.jre11` (Java 17+) | No — announce inline |
| `generic` **and the target database is identifiable as PostgreSQL** (user prompt names Postgres, a prior turn named Postgres, or a JDBC URL placeholder shows `jdbc:postgresql://`) | `org.postgresql:postgresql:42.7.11` — the canonical `generic` pairing | No — announce inline |
| `generic` **and the target is somet

…(truncated)
