Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 22 additions & 26 deletions plugin/skills/aca-sandboxes/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,19 @@
name: azure-connectornamespace-aca-sandbox
description: |
Azure Connector Namespace — ACA sandbox edition. Manage connector namespaces,
connections, and triggers that wire external services (Office 365, Teams, Microsoft
Forms, SharePoint, OneDrive, GitHub, Azure Blob) into Azure Container Apps sandbox
apps via event-driven triggers or direct API calls using connection runtime URLs.
connections, and triggers that wire external services (Office 365, Teams, Forms,
SharePoint, OneDrive, GitHub, Azure Blob) into ACA sandbox apps via event-driven
triggers or direct API calls using connection runtime URLs.
Use when:
- Creating or managing connector namespaces and connections for ACA sandboxes
- Creating or managing trigger configs whose callbacks target sandbox endpoints
- Subscribing to connector events (email, file, webhook, form submission, Teams message)
- Wiring event sources to ACA sandbox callbacks via `gatewayConnections[]`
- Managing trigger lifecycle (enable, disable, delete)
- Building sandbox apps that call connector APIs (send email, upload files, post Teams message, etc.)
- Reacting to events from one service and calling another (e.g., "when a form is submitted, send a Teams message")
- Automating workflows across Microsoft 365 services (Forms, Teams, Outlook, SharePoint, OneDrive) from within an ACA sandbox
Triggers: "create trigger", "trigger config", "webhook trigger",
"connector namespace", "connector", "connection", "email trigger", "send email",
"onedrive", "sharepoint", "teams", "teams message", "post message",
"microsoft forms", "forms", "form response", "form submission", "aca sandbox",
"sandbox group", "container apps sandbox",
"notify", "notification", "automate", "when", "on new"
- Managing connector namespaces and connections for ACA sandboxes
- Creating trigger configs whose callbacks target sandbox endpoints
- Wiring event sources to ACA sandbox callbacks via gatewayConnections
- Building sandbox apps that call connector APIs (send email, post Teams message, etc.)
- Reacting to events from one service and calling another
Triggers: "create trigger", "trigger config", "webhook trigger", "connector namespace",
"connector", "connection", "email trigger", "send email", "onedrive", "sharepoint",
"teams", "teams message", "microsoft forms", "form response", "aca sandbox",
"sandbox group", "container apps sandbox", "notify", "automate", "when", "on new"
---

# Azure Connector Namespace — ACA sandbox edition
Expand Down Expand Up @@ -53,6 +48,9 @@ to sandbox apps via direct API calls or event-driven triggers.
| **SSL/stderr** | `REQUESTS_CA_BUNDLE` preferred. `verify=False` needs `disable_warnings()`. stderr = trigger failure. See [handler-guide.md](references/handler-guide.md). |
| **Parallel execution** | Run independent ops (connections, ACLs, gatewayConnections wiring, dynamic values) as parallel tool calls. |
| **Sandbox ↔ connection wiring** | Use the declarative **gatewayConnections** pattern (SG-level PATCH + per-sandbox PUT body). See [gateway-connections.md](references/gateway-connections.md). |
| **Swagger / operation discovery** | Always fetch the connector Swagger via the connection's runtime metadata URL — see [swagger-discovery.md](references/swagger-discovery.md) for the full pattern (auth, ACL idempotency, parsing, picking the right operation). |
| **ACL idempotency** | When creating a **user-ACL** on a connection (for swagger discovery from outside a sandbox), ALWAYS GET the policy first and only PUT if it doesn't exist. Never blindly recreate a user-ACL. (This does not apply to `gateway-acl` / `sandbox-acl` — those are typically PUT directly during setup.) |
| **Narrate progress** | The user must know what's happening at each step **before** you run the shell. Print a short chat message (NOT inside the shell block) with: (a) what you're doing in one line, (b) the exact URL or resource ID being touched, and (c) why. Never run a shell command silently or with only a collapsed shell block. |

**When to STOP and ask the user:** Any parameter with dynamic values (teams, channels, folders, sites, lists), choosing integration pattern, OAuth consent. **You must NEVER skip this — always fetch the list and present it.**

Expand Down Expand Up @@ -191,9 +189,9 @@ Ask the user:

### Step 5A: Direct API calls via dynamicInvoke

→ **Full details:** [direct-api.md](references/direct-api.md) | **Dynamic values:** [dynamic-values.md](references/dynamic-values.md)
→ **Full details:** [direct-api.md](references/direct-api.md) | **Swagger discovery:** [swagger-discovery.md](references/swagger-discovery.md) | **Dynamic values:** [dynamic-values.md](references/dynamic-values.md)

1. Get the connector Swagger (`managedApis/{connector}?export=true`) → extract operationId-to-path table
1. Fetch the connector Swagger and pick an operation — see [swagger-discovery.md](references/swagger-discovery.md).
2. Call `dynamicInvoke` on the connection with the resolved `method` + `path`
3. If parameters have `x-ms-dynamic-*` → resolve via dynamicInvoke, show display names to user, store values

Expand All @@ -205,9 +203,9 @@ Ask the user:

### Step 5B: Event-driven triggers

→ **Full trigger setup (Steps 5B–9B):** [trigger-setup.md](references/trigger-setup.md) | **Dynamic values:** [dynamic-values.md](references/dynamic-values.md)
→ **Full trigger setup (Steps 5B–9B):** [trigger-setup.md](references/trigger-setup.md) | **Swagger discovery:** [swagger-discovery.md](references/swagger-discovery.md) | **Dynamic values:** [dynamic-values.md](references/dynamic-values.md)

1. Discover trigger operations: `GET .../managedApis/{connector}/apiOperations?api-version=2016-06-01` → filter for `properties.trigger`
1. Discover trigger operations — fetch the Swagger and filter paths whose operation has `x-ms-trigger` set. See [swagger-discovery.md](references/swagger-discovery.md).
2. If trigger type is `batch` (polling): inform user it polls every ~3 minutes by default. Ask if they want a different interval.
3. Collect parameters (resolve `x-ms-dynamic-*` via Swagger + dynamicInvoke)
4. Ask user: sandbox (existing/new) + callback type (ShellCommand / ExecuteCommand / InvokePort)
Expand Down Expand Up @@ -256,11 +254,9 @@ az rest --method GET --url ".../connectorGateways/{gw}?api-version=2026-05-01-pr
# Connections
az rest --method GET --url ".../connectorGateways/{gw}/connections?api-version=2026-05-01-preview"

# List operations (summaries + trigger types)
az rest --method GET --url ".../providers/Microsoft.Web/locations/{location}/managedApis/{connector}/apiOperations?api-version=2016-06-01"

# Get Swagger (full paths, parameters, x-ms-dynamic-* extensions)
az rest --method GET --url ".../providers/Microsoft.Web/locations/{location}/managedApis/{connector}" --url-parameters "api-version=2016-06-01" "export=true"
# Connector Swagger (paths, parameters, x-ms-dynamic-* extensions) — use to list operations or pick a specific one.
# See references/swagger-discovery.md for the full fetch pattern (metadata URL + user-ACL + API Hub token):
# az rest --method GET --url "<metadata_url>?export=true" --resource "https://apihub.azure.com"

# Dynamic invoke
az rest --method POST --url ".../connectorGateways/{gw}/connections/{conn}/dynamicInvoke?api-version=2026-05-01-preview" --body '{"request":{"method":"GET","path":"/..."}}'
Expand Down
34 changes: 1 addition & 33 deletions plugin/skills/aca-sandboxes/references/direct-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,7 @@ Gateway injects stored OAuth credentials. **Use `request` format (NOT `parameter

## 1. Get the Swagger and select the operation

```powershell
# Get connector Swagger — save to file (ConvertFrom-Json fails on piped output)
az rest --method GET `
--url "https://management.azure.com/subscriptions/{sub}/providers/Microsoft.Web/locations/{location}/managedApis/{connector}" `
--url-parameters "api-version=2016-06-01" "export=true" -o json > $env:TEMP\swagger.json

# Extract operationId → path table
python -c "
import json
with open(r'$env:TEMP\swagger.json') as f:
data = json.load(f)
paths = data.get('properties',{}).get('apiDefinitions',{}).get('value',{}).get('paths',{})
for path, methods in paths.items():
for method, details in methods.items():
if isinstance(details, dict) and 'operationId' in details:
clean_path = path.replace('/{connectionId}', '')
print(f'{details[\"operationId\"]:40s} {method.upper():6s} {clean_path}')
"
```

To list available operations (for presenting choices or matching user intent):
```powershell
# Quick list of operations with summaries (lighter than full swagger)
az rest --method GET `
--url "https://management.azure.com/subscriptions/{sub}/providers/Microsoft.Web/locations/{location}/managedApis/{connector}/apiOperations?api-version=2016-06-01" `
--query "value[].{name:name, summary:properties.summary, trigger:properties.trigger}" -o table
```

Match user's intent to the best operation. If ambiguous, ask with specific choices.
Do NOT dump all operations for the user — choose the right one yourself.

**To find the HTTP path for a chosen operationId:** Search the Swagger `paths` for the matching
`operationId`. Strip the `/{connectionId}` prefix — that's the path you pass to `dynamicInvoke`.
See [swagger-discovery.md](swagger-discovery.md) for the full pattern — auth (user-ACL idempotency + API Hub token), metadata URL derivation, Swagger parsing, and the keyword-matching algorithm for picking the right operation.

## 2. Collect parameter values interactively

Expand Down
26 changes: 2 additions & 24 deletions plugin/skills/aca-sandboxes/references/dynamic-values.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,9 @@ How to resolve connector parameters that require dynamic API calls.

## Step 1: Get the connector's Swagger (REQUIRED FIRST)

Before resolving any dynamic value, fetch the connector's **full Swagger definition**.
This gives you operationId → HTTP method + path mappings for all operations.
Before resolving any dynamic value, fetch the connector's **full Swagger definition** via the connection's runtime metadata URL. This gives you operationId → HTTP method + path mappings for all operations.

```powershell
# Fetch the full Swagger — MUST save to file first (ConvertFrom-Json fails on piped output)
az rest --method GET `
--url "https://management.azure.com/subscriptions/{sub}/providers/Microsoft.Web/locations/{location}/managedApis/{connector}" `
--url-parameters "api-version=2016-06-01" "export=true" -o json > $env:TEMP\swagger.json

# Parse the swagger and extract operationId → path table
python -c "
import json
with open(r'$env:TEMP\swagger.json') as f:
data = json.load(f)
paths = data.get('properties',{}).get('apiDefinitions',{}).get('value',{}).get('paths',{})
for path, methods in paths.items():
for method, details in methods.items():
if isinstance(details, dict) and 'operationId' in details:
clean_path = path.replace('/{connectionId}', '')
print(f'{details[\"operationId\"]:40s} {method.upper():6s} {clean_path}')
"
```

> **⚠️ PowerShell parsing issue:** `az rest` with `export=true` returns raw swagger that
> breaks `ConvertFrom-Json` when piped. Always save to file with `-o json > file.json` first.
→ See [swagger-discovery.md](swagger-discovery.md) for the full fetch pattern (user-ACL idempotency + API Hub token + parsing).

**To find the path for an operationId** (e.g., `GetFolders`):
- Look through `paths` → each path key (e.g., `/{connectionId}/datasets/default/folders`) has method entries (get, post, etc.)
Expand Down
4 changes: 1 addition & 3 deletions plugin/skills/aca-sandboxes/references/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ az rest --method GET \
--url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/triggerConfigs?api-version=2026-05-01-preview" \
--query "value[].{name:name, state:properties.state, connector:properties.connectionDetails.connectorName}" -o table

# 4. Discover operations for a connector (classic locations endpoint)
az rest --method GET \
--url "https://management.azure.com/subscriptions/{sub}/providers/Microsoft.Web/locations/{location}/managedApis/office365/apiOperations?api-version=2016-06-01"
# 4. Discover operations for a connector — see references/swagger-discovery.md.
```

For an end-to-end walkthrough, see [`tutorial-welcome-emailer.md`](tutorial-welcome-emailer.md).
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Content-Type: application/json

## How to map Swagger operations to runtime URL calls

1. **Find the operation** from the connector's Swagger (use `GET .../locations/{location}/managedApis/{connector}/apiOperations?api-version=2016-06-01`)
1. **Find the operation** from the connector's Swagger — see [swagger-discovery.md](swagger-discovery.md).

2. **Build the URL**:
- Base: `connectionRuntimeUrl` (from connection properties)
Expand Down
147 changes: 147 additions & 0 deletions plugin/skills/aca-sandboxes/references/swagger-discovery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# Swagger Discovery — Connector Operations via Metadata URL

How to fetch a connector's full Swagger and find the right operation to use.
This is the canonical reference for operation/trigger discovery in this skill.

## Endpoint

Fetch the Swagger from the **connection's runtime metadata URL**:

- Take the connection's `connectionRuntimeUrl` (e.g., `https://{host}/apim/teams/{id}`)
- Replace `/apim/` with `/metadata/`
- Append `?export=true`

Result: `https://{host}/metadata/teams/{id}?export=true`

**Do NOT** use the legacy ARM endpoint
`https://management.azure.com/.../providers/Microsoft.Web/locations/{location}/managedApis/{connector}?api-version=2016-06-01&export=true`.
The ARM endpoint is a different API surface and should not be used by this skill,
even if it returns Swagger-shaped content.

## Auth (outside-sandbox swagger discovery)

To call the metadata URL from outside a sandbox (the typical design-time path), two things are required:

1. A **user-ACL** on the connection for the signed-in user's objectId
2. An **API Hub token** (`az rest --resource "https://apihub.azure.com"`)

### Step 1 — Ensure the user-ACL exists (idempotent: GET first, PUT only if missing)

```powershell
$USER_OID = (az ad signed-in-user show --query id -o tsv)
$TENANT_ID = (az account show --query tenantId -o tsv)
$CONN_URL = "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/connections/{conn}"
$ACL_URL = "$CONN_URL/accessPolicies/$USER_OID`?api-version=2026-05-01-preview"

# GET first — only create if missing
$existing = az rest --method GET --url $ACL_URL 2>$null
if (-not $existing) {
$connInfo = az rest --method GET --url "$CONN_URL`?api-version=2026-05-01-preview" | ConvertFrom-Json
$aclBody = @{
location = $connInfo.location
properties = @{
principal = @{
type = "ActiveDirectory"
identity = @{ objectId = $USER_OID; tenantId = $TENANT_ID }
}
}
} | ConvertTo-Json -Depth 5 -Compress
$tmp = New-TemporaryFile; Set-Content $tmp $aclBody
az rest --method PUT --url $ACL_URL --body "@$tmp"
Remove-Item $tmp
}
```

> **Never blindly recreate a user-ACL.** Always GET first; PUT only if missing.
> This rule applies specifically to user-ACL creation for swagger discovery.
> `gateway-acl` and `sandbox-acl` are typically PUT directly during setup —
> see [gateway-connections.md](gateway-connections.md) — and may need updates
> when the gateway/sandbox-group principal changes.

### Step 2 — Fetch the Swagger

```powershell
$runtimeUrl = (az rest --method GET --url "$CONN_URL`?api-version=2026-05-01-preview" --query "properties.connectionRuntimeUrl" -o tsv)
$metadataUrl = $runtimeUrl -replace '/apim/', '/metadata/'
az rest --method GET --url "$metadataUrl`?export=true" --resource "https://apihub.azure.com" -o json > $env:TEMP\swagger.json
```

## Parse and find operations

The response is a raw Swagger 2.0 document — `paths` is at the top level (no envelope wrapper). All operations live under `paths[<path>][<method>]`. Each operation entry has:

- `operationId` — string identifier (use this when calling `dynamicInvoke`)
- `summary` / `description` — human-readable text
- `x-ms-visibility` — `important` / `default` / `advanced` / `internal`
- `x-ms-trigger` — object like `{"type": "polling"}` if the operation is a trigger; absent for callable actions

Comment thread
prjhawar marked this conversation as resolved.
### Quick listing

Use `jq` to stream the swagger and print each operation (operationId, method, path). No temp file beyond what the metadata fetch already wrote, no separate parse process. Substitute the path you wrote the swagger to (`%TEMP%\swagger.json` in PowerShell, `/tmp/swagger.json` in bash):

```bash
# Bash
jq -r '.paths | to_entries[] as $p | $p.value | to_entries[]
| select(.value.operationId)
| "\(.value.operationId)\t\(.key | ascii_upcase)\t\($p.key | sub("/\\{connectionId\\}"; ""))"' \
/tmp/swagger.json
```

```powershell
# PowerShell — same jq, just the env var differs
jq -r '.paths | to_entries[] as $p | $p.value | to_entries[]
| select(.value.operationId)
| "\(.value.operationId)\t\(.key | ascii_upcase)\t\($p.key | sub("/\\{connectionId\\}"; ""))"' `
"$env:TEMP\swagger.json"
```

### Find the right operation for the user's intent

Filter the swagger by keyword on `operationId + summary + description`. For **direct API** (callable actions), exclude operations that have `x-ms-trigger` set and skip `x-ms-visibility: internal`. For **trigger discovery**, invert the filter — keep only operations with `x-ms-trigger` set.

```bash
# Bash
jq -r --arg kw 'message|chat|post|send' '
.paths | to_entries[] as $p | $p.value | to_entries[]
| select(.value.operationId)
| select(.value["x-ms-trigger"] | not)
| select(.value["x-ms-visibility"] != "internal")
| select((.value.operationId + " " + (.value.summary // "") + " " + (.value.description // "")) | ascii_downcase | test($kw))
| "\(.value.operationId)\t\(.key | ascii_upcase)\t\($p.key | sub("/\\{connectionId\\}"; ""))\n \(.value.summary // "")"' \
/tmp/swagger.json
```

```powershell
# PowerShell
jq -r --arg kw 'message|chat|post|send' '
.paths | to_entries[] as $p | $p.value | to_entries[]
| select(.value.operationId)
| select(.value[\"x-ms-trigger\"] | not)
| select(.value[\"x-ms-visibility\"] != \"internal\")
| select((.value.operationId + \" \" + (.value.summary // \"\") + \" \" + (.value.description // \"\")) | ascii_downcase | test($kw))
| \"\(.value.operationId)\t\(.key | ascii_upcase)\t\($p.key | sub(\"/\\{connectionId\\}\"; \"\"))\n \(.value.summary // \"\")\"' `
"$env:TEMP\swagger.json"
```

### Picking the best match

- **`x-ms-visibility`** — prefer `important` > `default` > `advanced`. Skip `internal`.
- **Versioned variants** — when V2/V3 exist, prefer the highest-numbered unless the user asks otherwise.
- **Intent match** — "send to user" → 1:1 chat operation; "post to channel" → channel operation; etc.
- **Ambiguity** — if multiple operations are plausible, ask the user with specific choices. Don't dump all of them.

### Resolving operationId → HTTP path

Look up the chosen operationId under the top-level `paths` object (e.g., `$swag.paths` in PowerShell). Strip the `/{connectionId}` prefix — that's the path you pass to `dynamicInvoke`.

## Narrate progress

Before running the shell commands above, print a short chat message naming what
you're doing and the exact URL/objectId being touched. Examples:

- `Ensuring user-ACL exists on connection {conn} for objectId {oid} (GET {ACL_URL})`
- `Fetching Swagger from {metadataUrl}?export=true (--resource https://apihub.azure.com)`
- `Found {N} operations matching '{keywords}'. Picking {chosen_op}.`

Never run a swagger-discovery shell command silently — the user shouldn't have to
expand a collapsed shell block to see what URL you're hitting.
Loading