From 2b4a94e917fa9411daaef893a7048f8f0cf821a1 Mon Sep 17 00:00:00 2001 From: apranaseth Date: Fri, 29 May 2026 13:00:04 -0700 Subject: [PATCH 01/14] Add generic azure-connectorgateway skill New skill under Skills/General/azure-connectorgateway providing a sandbox-agnostic playbook for managing Microsoft.Web connector gateways, connections, trigger configs, and MCP server configs. The customer-owned callback URL and notification authentication are user-provided; the skill never assumes a sandbox runtime. Trigger types covered: connector-event, Recurrence, SlidingWindow. MCP focus is on ManagedMcpServer; HostedMcpServer is referenced briefly. Reuses dynamic-values resolution and consent flow from the existing Skills/Sandbox/azure-connectorgateway skill. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../General/azure-connectorgateway/SKILL.md | 325 ++++++++++++++ .../references/connections.md | 167 +++++++ .../references/consent.md | 54 +++ .../references/direct-api.md | 161 +++++++ .../references/dynamic-values.md | 425 ++++++++++++++++++ .../references/gotchas.md | 30 ++ .../references/mcp-server-config.md | 229 ++++++++++ .../references/notification-authentication.md | 158 +++++++ .../references/prerequisites.md | 44 ++ .../references/quickstart.md | 32 ++ .../references/trigger-flow.md | 104 +++++ .../references/trigger-setup.md | 238 ++++++++++ .../references/tutorial.md | 273 +++++++++++ .../azure-connectorgateway/version.json | 1 + 14 files changed, 2241 insertions(+) create mode 100644 Skills/General/azure-connectorgateway/SKILL.md create mode 100644 Skills/General/azure-connectorgateway/references/connections.md create mode 100644 Skills/General/azure-connectorgateway/references/consent.md create mode 100644 Skills/General/azure-connectorgateway/references/direct-api.md create mode 100644 Skills/General/azure-connectorgateway/references/dynamic-values.md create mode 100644 Skills/General/azure-connectorgateway/references/gotchas.md create mode 100644 Skills/General/azure-connectorgateway/references/mcp-server-config.md create mode 100644 Skills/General/azure-connectorgateway/references/notification-authentication.md create mode 100644 Skills/General/azure-connectorgateway/references/prerequisites.md create mode 100644 Skills/General/azure-connectorgateway/references/quickstart.md create mode 100644 Skills/General/azure-connectorgateway/references/trigger-flow.md create mode 100644 Skills/General/azure-connectorgateway/references/trigger-setup.md create mode 100644 Skills/General/azure-connectorgateway/references/tutorial.md create mode 100644 Skills/General/azure-connectorgateway/version.json diff --git a/Skills/General/azure-connectorgateway/SKILL.md b/Skills/General/azure-connectorgateway/SKILL.md new file mode 100644 index 0000000..1e6ba6c --- /dev/null +++ b/Skills/General/azure-connectorgateway/SKILL.md @@ -0,0 +1,325 @@ +--- +name: azure-connectorgateway +description: | + Azure Connector Gateway — manage gateways, connections, triggers, and MCP server configs. + Connect external SaaS services (Office 365, Teams, SharePoint, OneDrive, Forms, GitHub, + Azure Blob, ...) to any user-provided webhook URL via event-driven triggers, expose + selected connector operations as an MCP server endpoint, or call connector operations + on demand via `dynamicInvoke`. + Use when: + - Creating or managing connector gateways and connections + - Creating or managing trigger configs that POST to an arbitrary callback URL + - Subscribing to connector events (email, file, list-item, form response, Teams message) + - Wiring event sources to a customer-owned webhook, Function App, Logic App, or API + - Recurrence / sliding-window triggers that fire on a schedule + - Exposing connector operations as Model Context Protocol (MCP) tools at a gateway endpoint + - Calling connector APIs (send email, post Teams message, upload files, list items, ...) + Triggers: "connector gateway", "create trigger", "trigger config", "webhook trigger", + "recurrence trigger", "schedule trigger", "on new email", "on new file", + "on new item", "on form response", "callback url", "notification url", + "mcp", "mcp server", "mcp tools", "model context protocol", + "send email", "post teams message", "upload to onedrive", "automate" +--- + +# Azure Connector Gateway (generic) + +Manage Microsoft.Web connector gateways, their connections, trigger configs, and MCP +server configs. This skill is **sandbox-agnostic** — it does not assume your callback +target. Callbacks can be any HTTP(S) URL (Function App, Logic App, App Service, +ngrok, anywhere) and you choose how the gateway authenticates to it. + +> If you specifically need to fan events into an Azure Container Apps sandbox group +> (with declarative `gatewayConnections[]` wiring and sandbox callbacks), use the +> companion skill at `Skills/Sandbox/azure-connectorgateway` instead. + +## Three patterns this skill supports + +| Pattern | When to use | Output | +|---|---|---| +| **A) Trigger config** | "Notify my webhook when X happens" / "Run my workflow every N minutes" | A `triggerConfigs/{name}` resource that POSTs to your `callbackUrl` | +| **B) MCP server config** | "Expose these connector operations as MCP tools to an LLM client" | A `mcpServerConfigs/{name}` resource with a public `mcpEndpointUrl` | +| **C) Direct API call** | "Send an email" / "Post a Teams message" — one-off operation | One `dynamicInvoke` POST against the connection | + +## Rules (MUST follow) + +| Rule | Details | +|------|---------| +| **No hallucination** | Check `references/` for details. Use `az rest --help` for syntax. | +| **No generated notebooks/scripts** | Walk the user through interactively. Do NOT generate a standalone notebook or script. | +| **No guessing dynamic values** | `x-ms-dynamic-*` → call the API, present results, STOP. Never assume a team/channel/folder/site/list. | +| **No guessing the callback URL** | The callback URL is **always** user-provided. Ask for it explicitly. Do NOT invent one. | +| **Execute, don't ask** | Once you have inputs, run the commands. Don't ask "Can I run this?" | +| **`az rest` only** | No `az connectorgateway` or other extensions exist. Use `az rest` for ARM and `az rest --resource` for data-plane. | +| **Always `@$tmpFile`** | For `az rest --body` in PowerShell — inline JSON breaks quoting. See [gotchas.md](references/gotchas.md). | +| **Trigger body schema** | Properties root contains: `type` OR `connectionDetails`+`operationName`+`parameters`, plus `notificationDetails` (`callbackUrl`+`authentication`+`body?`). See [trigger-setup.md](references/trigger-setup.md). | +| **Trigger needs `gateway-acl`** | For connector-event triggers, the gateway MI must have an access policy on the connection. See [trigger-setup.md](references/trigger-setup.md) Step 4. | +| **MCP user params** | Each `userParameters[]` entry is the fixed value for a connector-operation parameter, resolved via `dynamic-values` against the connection at config time. See [mcp-server-config.md](references/mcp-server-config.md). | +| **Parallel execution** | Run independent ops (connections, ACLs, dynamic-value lookups, MCP operations) as parallel tool calls. | + +**When to STOP and ask the user:** subscription/resource group, gateway name, connection name, any parameter with dynamic values (teams/channels/folders/sites/lists), callback URL, callback authentication type, OAuth consent completion. + +**When to EXECUTE immediately:** gateway/connection/trigger/MCP-config/access-policy CRUD, role assignments, dynamic-value lookups. + +--- + +### Step 0: Prerequisites & Azure context + +1. Check `az account show`. If missing, see [prerequisites.md](references/prerequisites.md). +2. **Select subscription** — list, ask user to pick: + ```bash + az account list --query "[].{name:name, id:id, isDefault:isDefault}" -o table + ``` + Set if non-default: `az account set --subscription "{sub}"`. Store `{sub}` for all + subsequent commands. +3. **Select resource group**: + - Existing: `az group list --query "[].{name:name, location:location}" -o table` → user picks. + - New: ask for name + location, then `az group create --name {rg} --location {location}`. + +**Stop and wait for the user's answers before continuing.** + +--- + +### Step 1: Gateway setup + +> **ARM base:** `https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways` +> **API version:** `2026-05-01-preview` + +Ask the user: "Do you have an existing connector gateway, or should I create a new one?" + +- **Existing:** ask for the name and fetch it: + ```bash + az rest --method GET \ + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}?api-version=2026-05-01-preview" \ + --query "{name:name, location:location, principalId:identity.principalId, tenantId:identity.tenantId, identityType:identity.type, userAssigned:identity.userAssignedIdentities}" + ``` +- **New:** ask for `{gw}` name + location. **Create with a SystemAssigned managed identity** + (required for trigger event subscriptions on connector-event triggers AND for + `ManagedServiceIdentity` callback auth): + ```powershell + $gwBody = @{ location = "{location}"; identity = @{ type = "SystemAssigned" } } | ConvertTo-Json -Compress + $tmp = New-TemporaryFile; Set-Content $tmp $gwBody + az rest --method PUT ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}?api-version=2026-05-01-preview" ` + --body "@$tmp" ` + --query "{name:name, principalId:identity.principalId, tenantId:identity.tenantId}" + Remove-Item $tmp + ``` + +**Capture `principalId` and `tenantId`** — needed later for the `gateway-acl` +access policy and (optionally) for `ManagedServiceIdentity` callback authentication. + +> If a user later wants to call their callback URL using a **user-assigned** identity, +> they'll need to add that identity to the gateway separately. See +> [notification-authentication.md](references/notification-authentication.md). + +--- + +### Step 2: Connection(s) + OAuth consent + +Required for **trigger configs**, **MCP server configs**, and **direct API calls**. +Recurrence / sliding-window triggers with no connector-event source can skip this. + +Create connections in parallel: + +```powershell +$connBody = @{ properties = @{ connectorName = "office365" }; location = "{location}" } | ConvertTo-Json -Compress +$tmp = New-TemporaryFile; Set-Content $tmp $connBody +az rest --method PUT ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/connections/o365-conn?api-version=2026-05-01-preview" ` + --body "@$tmp" +Remove-Item $tmp +``` + +Then generate consent links and open in the browser — **see [consent.md](references/consent.md)** for the exact body format and `Start-Process` pattern. Verify status `Connected` before continuing. See also [connections.md](references/connections.md) for the full CRUD reference. + +> **For trigger configs:** also create a `gateway-acl` access policy granting the +> gateway MI access to the connection (required for the gateway to subscribe to +> connector events). See [trigger-setup.md](references/trigger-setup.md) Step 4. + +--- + +### Step 3: Choose the pattern + +Ask the user: +- **A) Trigger config** — push notifications to your callback URL when events happen, or on a schedule. +- **B) MCP server config** — expose selected connector operations as MCP tools at a gateway endpoint, callable by any MCP client (Claude Desktop, VS Code, etc.). +- **C) Direct API call** — one-off `dynamicInvoke` (send an email now, list items now). + +**Stop and wait for the user's answer.** + +- A → **Step 4A** +- B → **Step 4B** +- C → **Step 4C** + +--- + +### Step 4A: Trigger config + +→ **Full details:** [trigger-setup.md](references/trigger-setup.md) | **Auth options:** [notification-authentication.md](references/notification-authentication.md) | **Dynamic params:** [dynamic-values.md](references/dynamic-values.md) + +1. **Pick the trigger source.** Ask the user: + - **Connector event** (e.g., new email, new file, new form response) — needs the connection from Step 2. + - **Recurrence** — fires every N seconds / minutes / hours / days. + - **Sliding window** — fires on time intervals with `startTime`/`endTime` window state. + +2. **Connector event:** discover trigger operations on the connector: + ```bash + 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[?properties.trigger != null].{name:name, summary:properties.summary, trigger:properties.trigger}" -o table + ``` + Pick one with the user. Resolve any `x-ms-dynamic-*` parameters via [dynamic-values.md](references/dynamic-values.md). **STOP at every dynamic param.** + +3. **Recurrence / sliding window:** ask for `frequency` (Second/Minute/Hour/Day) and `interval`. + +4. **Callback URL — ask the user explicitly.** Do NOT invent one. Format examples: + - Function App: `https://{app}.azurewebsites.net/api/{fn}?code={key}` + - Logic App HTTP trigger: full SAS URL from the Logic App's "When a HTTP request is received" + - Custom API: any `https://...` endpoint + +5. **Callback authentication — ask the user.** Options (full reference in [notification-authentication.md](references/notification-authentication.md)): + | Type | Use when | + |---|---| + | *(none)* | Callback URL already contains its own auth token (e.g., Logic App SAS in querystring) | + | `QueryString` | Add a `?key=value` automatically (e.g., Function App key) | + | `Raw` | Send a literal `Authorization: ` header | + | `Basic` | HTTP Basic with `username`/`password` | + | `ManagedServiceIdentity` | Gateway acquires a token for `audience` using its own MI (system- or user-assigned) | + | `ActiveDirectoryOAuth` | Gateway authenticates as an Entra app (tenant/clientId/secret/audience) | + | `ClientCertificate` | Mutual TLS with a `pfx`/`password` | + +6. **Create the trigger config** (one PUT). See [trigger-setup.md](references/trigger-setup.md) Step 3 for the canonical body templates for each source × auth combination. + +7. **Create the `gateway-acl`** (only for connector-event triggers). See [trigger-setup.md](references/trigger-setup.md) Step 4. + +8. **Verify** the trigger state is `Enabled`: + ```bash + az rest --method GET \ + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/triggerConfigs/{name}?api-version=2026-05-01-preview" \ + --query "properties.state" -o tsv + ``` + +--- + +### Step 4B: MCP server config + +→ **Full details:** [mcp-server-config.md](references/mcp-server-config.md) | **Dynamic params:** [dynamic-values.md](references/dynamic-values.md) + +1. **Pick connector(s) + operation(s).** Discover them: + ```bash + 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[?properties.trigger == null].{name:name, summary:properties.summary, description:properties.description}" -o table + ``` + Each MCP "tool" maps to one connector operation under one connection. + +2. **For each operation, collect `userParameters`.** These are the **fixed inputs** + baked into the MCP config (e.g., "always send to channel X"). LLM-supplied + inputs are the operation's other parameters. Resolve `x-ms-dynamic-*` exactly + like trigger setup — see [dynamic-values.md](references/dynamic-values.md). **STOP at every dynamic param.** + +3. **PUT the MCP server config.** Body shape: + ```json + { + "properties": { + "description": "...", + "connectors": [ + { + "name": "office365", + "displayName": "Office 365", + "connectionName": "o365-conn", + "operations": [ + { + "name": "Send_Email_(V2)", + "displayName": "Send Email", + "description": "Send an email via Office 365.", + "userParameters": [ + { "name": "from", "displayName": "From", "value": "alice@contoso.com", "displayValue": "Alice" } + ] + } + ] + } + ] + } + } + ``` + PUT to: `.../connectorGateways/{gw}/mcpServerConfigs/{name}?api-version=2026-05-01-preview` + +4. **GET the config** and return `properties.mcpEndpointUrl` — that's the URL the MCP + client points at. See [mcp-server-config.md](references/mcp-server-config.md) for the auth-mode + access-policy details. + +--- + +### Step 4C: Direct API call via `dynamicInvoke` + +→ **Full details:** [direct-api.md](references/direct-api.md) | **Dynamic params:** [dynamic-values.md](references/dynamic-values.md) + +1. Get the connector Swagger (`managedApis/{connector}?export=true`) → operationId → path table +2. Resolve any `x-ms-dynamic-*` parameters with [dynamic-values.md](references/dynamic-values.md). **STOP at every dynamic param.** +3. POST to `dynamicInvoke` with the resolved `method` + `path` (+ optional `queries`, `body`, non-`Content-*` headers). + +--- + +### Final verification checklist + +**For trigger configs (path A):** +- ✅ Gateway exists; for `ManagedServiceIdentity` callback auth, gateway has the requested identity +- ✅ For connector-event triggers: connection `Connected`, `gateway-acl` exists on the connection +- ✅ Trigger `properties.state` is `Enabled` +- ✅ User-provided callback URL is reachable + accepts the chosen auth + +**For MCP server configs (path B):** +- ✅ All referenced connections are `Connected` +- ✅ `properties.mcpEndpointUrl` is populated in the GET response +- ✅ For Hosted MCP servers with `AuthenticationMode != NotSpecified`: `resourceAuth` is set (see [mcp-server-config.md](references/mcp-server-config.md)) +- ✅ Access policies on the MCP server config grant the consuming clients access + +**For direct API (path C):** +- ✅ Connection `Connected` +- ✅ `dynamicInvoke` returns `response.statusCode` in 2xx + +## Quick reference + +```bash +# ARM base: https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways +# API ver: api-version=2026-05-01-preview + +# Gateway +az rest --method GET --url ".../connectorGateways/{gw}?api-version=2026-05-01-preview" + +# Connections +az rest --method GET --url ".../connectorGateways/{gw}/connections?api-version=2026-05-01-preview" + +# List trigger operations + summaries +az rest --method GET --url ".../locations/{location}/managedApis/{connector}/apiOperations?api-version=2016-06-01" + +# Get Swagger (paths, parameters, x-ms-dynamic-*) +az rest --method GET --url ".../locations/{location}/managedApis/{connector}" --url-parameters "api-version=2016-06-01" "export=true" + +# Dynamic invoke +az rest --method POST --url ".../connectorGateways/{gw}/connections/{conn}/dynamicInvoke?api-version=2026-05-01-preview" --body '{"request":{"method":"GET","path":"/..."}}' + +# Trigger configs +az rest --method GET --url ".../connectorGateways/{gw}/triggerConfigs?api-version=2026-05-01-preview" +az rest --method POST --url ".../connectorGateways/{gw}/triggerConfigs/{name}/disable?api-version=2026-05-01-preview" +az rest --method POST --url ".../connectorGateways/{gw}/triggerConfigs/{name}/enable?api-version=2026-05-01-preview" + +# MCP server configs +az rest --method GET --url ".../connectorGateways/{gw}/mcpServerConfigs?api-version=2026-05-01-preview" +az rest --method GET --url ".../connectorGateways/{gw}/mcpServerConfigs/{name}?api-version=2026-05-01-preview" --query "properties.mcpEndpointUrl" -o tsv +``` + +## References + +- [prerequisites.md](references/prerequisites.md) — required tooling and Azure setup +- [quickstart.md](references/quickstart.md) — minimal commands to list everything +- [connections.md](references/connections.md) — connection CRUD + ACLs +- [consent.md](references/consent.md) — OAuth consent link generation (exact body format) +- [dynamic-values.md](references/dynamic-values.md) — `x-ms-dynamic-{values,list,tree,schema}` resolution algorithms +- [trigger-setup.md](references/trigger-setup.md) — trigger config creation (all source types, all auth types) +- [notification-authentication.md](references/notification-authentication.md) — callback authentication reference +- [mcp-server-config.md](references/mcp-server-config.md) — MCP server config CRUD +- [direct-api.md](references/direct-api.md) — `dynamicInvoke` recipes +- [trigger-flow.md](references/trigger-flow.md) — end-to-end architecture diagram +- [tutorial.md](references/tutorial.md) — walkthrough: recurrence trigger → user webhook +- [gotchas.md](references/gotchas.md) — common issues diff --git a/Skills/General/azure-connectorgateway/references/connections.md b/Skills/General/azure-connectorgateway/references/connections.md new file mode 100644 index 0000000..29c0ea7 --- /dev/null +++ b/Skills/General/azure-connectorgateway/references/connections.md @@ -0,0 +1,167 @@ +# Connections + +A **connection** is a stored credential for one connector (e.g., one Office 365 +mailbox, one Teams tenant, one GitHub user) attached to one gateway. Triggers, +MCP server configs, and direct-API calls all reference connections by name. + +This doc covers connection CRUD; for the **consent flow** that turns a freshly +created `Unauthenticated` / `Error` connection into a working `Connected` one, +see [consent.md](consent.md). + +## Discover available connectors + +```bash +# All connectors in a region +az rest --method GET \ + --url "https://management.azure.com/subscriptions/{sub}/providers/Microsoft.Web/locations/{location}/managedApis?api-version=2016-06-01" \ + --query "value[].{name:name, displayName:properties.displayName}" -o table +``` + +Use the `name` value (lowercase, e.g., `office365`, `sharepointonline`, `teams`, +`outlook`, `github`, `salesforce`, ...) as the `connectorName` everywhere. + +## Create a connection + +PUT to `.../connectorGateways/{gw}/connections/{connection_name}?api-version=2026-05-01-preview`. + +```powershell +$connBody = @{ + location = "{location}" # must match the gateway's location + properties = @{ connectorName = "{connector}" } # e.g. "office365" +} | ConvertTo-Json -Compress + +$tmp = New-TemporaryFile; Set-Content $tmp $connBody +az rest --method PUT ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/connections/{conn_name}?api-version=2026-05-01-preview" ` + --body "@$tmp" +Remove-Item $tmp +``` + +The PUT returns immediately with `properties.statuses[].status = "Error"` or +`"Unauthenticated"` — this is the **pre-consent** state. The connection isn't +broken; it just needs the user to complete OAuth. Run [consent.md](consent.md). + +## Verify a connection is connected + +```bash +az rest --method GET \ + --url ".../connectorGateways/{gw}/connections/{conn}?api-version=2026-05-01-preview" \ + --query "{name:name, status:properties.statuses[0].status, createdBy:properties.createdBy.objectId}" +``` + +A connection is ready when `status` is `Connected`. + +## List connections + +```bash +az rest --method GET \ + --url ".../connectorGateways/{gw}/connections?api-version=2026-05-01-preview" \ + --query "value[].{name:name, connector:properties.connectorName, status:properties.statuses[0].status}" -o table +``` + +## Delete a connection + +```bash +# Delete access policies first (PUT-overwriting any leftover ones is fine too) +az rest --method DELETE --url ".../connections/{conn}/accessPolicies/{policy}?api-version=2026-05-01-preview" + +# Then the connection +az rest --method DELETE --url ".../connections/{conn}?api-version=2026-05-01-preview" +``` + +> Deleting a connection that is still referenced by a trigger config or MCP +> server config will fail. Delete those first. + +## Access policies — who can use the connection + +A connection's access policies control which Azure AD principals can **use** it +(invoke operations via `dynamicInvoke`, subscribe to events for triggers, drive +MCP tool calls, etc.). + +The gateway's own MI needs an `gateway-acl` policy on every connection it must +subscribe to for connector-event triggers, or invoke for MCP tool calls. + +### Get the gateway's MI + +```bash +az rest --method GET \ + --url ".../connectorGateways/{gw}?api-version=2026-05-01-preview" \ + --query "{principalId:identity.principalId, tenantId:identity.tenantId}" +``` + +> If `identity` is null, enable a system-assigned MI on the gateway: +> ```bash +> az rest --method PATCH \ +> --url ".../connectorGateways/{gw}?api-version=2026-05-01-preview" \ +> --body '{\"identity\":{\"type\":\"SystemAssigned\"}}' +> ``` + +### Create the gateway ACL + +```powershell +$aclBody = @{ + location = "{location}" + properties = @{ + principal = @{ + type = "ActiveDirectory" + identity = @{ objectId = "{gw_principal_id}"; tenantId = "{gw_tenant_id}" } + } + } +} | ConvertTo-Json -Depth 5 -Compress + +$tmp = New-TemporaryFile; Set-Content $tmp $aclBody +az rest --method PUT ` + --url ".../connections/{conn}/accessPolicies/gateway-acl?api-version=2026-05-01-preview" ` + --body "@$tmp" +Remove-Item $tmp +``` + +### Grant other principals (users / apps / external MIs) + +Same call, different `objectId` and a different policy name: + +```powershell +# e.g., a user +$aclBody = @{ + location = "{location}" + properties = @{ + principal = @{ + type = "ActiveDirectory" + identity = @{ objectId = "{user_object_id}"; tenantId = "{tenant_id}" } + } + } +} | ConvertTo-Json -Depth 5 -Compress +# PUT to .../accessPolicies/{policy_name} +``` + +Policy names are arbitrary but should be descriptive (`gateway-acl`, +`developer-jane`, `agent-prod-mi`, ...). + +### List policies + +```bash +az rest --method GET \ + --url ".../connections/{conn}/accessPolicies?api-version=2026-05-01-preview" \ + --query "value[].{name:name, type:properties.principal.type, objectId:properties.principal.identity.objectId}" -o table +``` + +## Common connector names + +| Connector | `connectorName` | +|---|---| +| Office 365 (Outlook) | `office365` | +| Outlook.com | `outlook` | +| SharePoint Online | `sharepointonline` | +| Microsoft Teams | `teams` | +| OneDrive for Business | `onedriveforbusiness` | +| OneDrive (consumer) | `onedrive` | +| Microsoft Forms | `microsoftforms` | +| Excel Online (Business) | `excelonlinebusiness` | +| Planner | `planner` | +| GitHub | `github` | +| Salesforce | `salesforce` | +| ServiceNow | `service-now` | +| Dropbox | `dropbox` | +| Slack | `slack` | + +For anything not in this list, run the discovery command at the top of this doc. diff --git a/Skills/General/azure-connectorgateway/references/consent.md b/Skills/General/azure-connectorgateway/references/consent.md new file mode 100644 index 0000000..974994e --- /dev/null +++ b/Skills/General/azure-connectorgateway/references/consent.md @@ -0,0 +1,54 @@ +# OAuth Consent Flow + +How to generate consent links and authenticate connections. + +## Generate consent link + +```powershell +# Get the connection's objectId and tenantId first +$conn = az rest --method GET ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/connections/{conn}?api-version=2026-05-01-preview" | ConvertFrom-Json +$objectId = $conn.properties.createdBy.objectId +$tenantId = $conn.properties.createdBy.tenantId + +# Build consent body — EXACT format required (parameters array) +$body = @{ + parameters = @(@{ + objectId = $objectId + tenantId = $tenantId + redirectUrl = "https://microsoft.com" + parameterName = "token" + }) +} | ConvertTo-Json -Depth 3 -Compress + +# Post and open in browser +$tmpFile = New-TemporaryFile +Set-Content $tmpFile $body +$link = az rest --method POST ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/connections/{conn}/listConsentLinks?api-version=2026-05-01-preview" ` + --body "@$tmpFile" --query "value[0].link" -o tsv +Remove-Item $tmpFile +Start-Process $link +``` + +## Critical rules + +- **ALWAYS use `Start-Process`** to open consent links — URLs are too long to copy +- **Use `"redirectUrl":"https://microsoft.com"`** — default redirect is broken +- **Do NOT retry with different body formats** — if consent fails, it's a service issue +- **Body format is exact:** `{"parameters":[{"objectId":"...","tenantId":"...","redirectUrl":"https://microsoft.com","parameterName":"token"}]}` +- Get `objectId` and `tenantId` from the connection's `properties.createdBy` (fields are `objectId` and `tenantId` — there is no `name` field) + +## Verify connection status + +After user authenticates, verify: +```bash +az rest --method GET \ + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/connections?api-version=2026-05-01-preview" \ + --query "value[].{name:name, status:properties.statuses[0].status}" +# All should show: Connected. If not, re-consent. +``` + +> A freshly created connection shows `Error` / `Unauthenticated` until the user +> completes the consent flow in the browser — that's normal pre-consent state, +> not a service issue. Re-check after consent. diff --git a/Skills/General/azure-connectorgateway/references/direct-api.md b/Skills/General/azure-connectorgateway/references/direct-api.md new file mode 100644 index 0000000..76f7cd8 --- /dev/null +++ b/Skills/General/azure-connectorgateway/references/direct-api.md @@ -0,0 +1,161 @@ +# Direct API Calls via `dynamicInvoke` + +Call connector operations on demand via `az rest` against the ARM `dynamicInvoke` endpoint. +The gateway injects the stored OAuth credential automatically. **Use `request` format (NOT `parameters`).** + +> **⚠️ Do NOT include `Content-*` headers in the request object** — `Content-Type` +> is set by the platform. + +## 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`. + +## 2. Collect parameter values interactively + +For each required parameter, check its Swagger extension. Use `@$tmpFile` for `az rest --body` with special chars. + +> **🚫 NEVER guess or infer dynamic parameter values. NEVER use placeholder IDs.** +> If a parameter has `x-ms-dynamic-values`, `x-ms-dynamic-list`, `x-ms-dynamic-tree`, +> or `x-ms-dynamic-schema`, you MUST: +> 1. Call the specified operationId via `dynamicInvoke` to fetch the actual values +> 2. Present the results to the user with `ask_user` +> 3. STOP and wait for the user to select — do NOT proceed until they answer +> +> **Wrong:** "I'll use the Inbox folder" (guessed without calling the API) +> **Wrong:** Using a teamId/channelId/siteUrl without fetching the list first +> **Right:** Call the dynamic operation → show user the list → wait for selection + +**Parameter resolution by extension type:** + +| Extension | What it does | How to handle | +|-----------|-------------|---------------| +| `x-ms-dynamic-values` | Flat list of options | Call operationId → present choices → **STOP** | +| `x-ms-dynamic-list` | Same as above (nested variant) | Same as dynamic-values → **STOP** | +| `x-ms-dynamic-tree` | Hierarchical folder browsing | Call open → present → browse deeper → **STOP at each level** | +| `x-ms-dynamic-schema` | Fields depend on prior selection | Collect dependencies first → call schema op → **STOP** | +| Static enum | Fixed choices in Swagger | Present choices → **STOP** | +| Free-form | User provides value | Ask user (or use obvious default + inform) | + +→ **Full algorithms with code examples:** See [dynamic-values.md](dynamic-values.md) + +**Key rules:** +- **STOP at every dynamic parameter** — even if you think you know the answer. + The user's Teams, channels, SharePoint sites, and folders are NOT predictable. +- **Always resolve `operationId` to HTTP path** — search the Swagger `paths` (from Step 1) for the matching + `operationId`. Strip `/{connectionId}` prefix. That's the path for `dynamicInvoke`. +- **Always URL-encode IDs** with `[System.Uri]::EscapeDataString()` +- **Use `@file` pattern** for `az rest --body` when IDs contain `!` or special chars +- **Skip optional parameters** unless user mentioned them +- **Large lists (>10 items):** Do NOT pass all items as `ask_user` choices. + Instead, print the numbered list in your response text and use `ask_user` + with `allow_freeform: true` (no `choices` array): "Type the name or number." + Only use the `choices` array when there are ≤10 items. +- **Very large lists (50+):** Fetch with `$top=20`, show results, ask + "Do you see yours, or should I load more?" + +**Do NOT proceed to step 3 until ALL required parameters are confirmed by the user.** + +## 3. Build and call `dynamicInvoke` + +The request object supports: +- `method` — HTTP method (GET, POST, PUT, DELETE) +- `path` — operation path from Swagger (strip `/{connectionId}` prefix) +- `queries` — query parameters as key-value dict +- `body` — request body (string or object) +- `headers` — HTTP headers (**except** `Content-*` headers) + +Map Swagger `in` field: `path` → URL path, `query` → queries dict, +`body` → body field, `header` → headers dict (except `Content-*`). + +```bash +# Example: Create a file in OneDrive +az rest --method POST \ + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/connections/{conn}/dynamicInvoke?api-version=2026-05-01-preview" \ + --body '{ + "request": { + "method": "POST", + "path": "/datasets/default/files", + "queries": {"folderPath": "/", "name": "hello.txt"}, + "body": "Hello from Connector Gateway!" + } + }' +``` + +In PowerShell, prefer the `@$tmpFile` pattern for the body: + +```powershell +$body = @{ + request = @{ + method = "POST" + path = "/v2/Mail" + body = @{ + To = "user@contoso.com" + Subject = "Hello" + Body = "

Sent via dynamicInvoke

" + } + } +} | ConvertTo-Json -Depth 6 -Compress + +$tmp = New-TemporaryFile; Set-Content $tmp $body +az rest --method POST ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/connections/{conn}/dynamicInvoke?api-version=2026-05-01-preview" ` + --body "@$tmp" +Remove-Item $tmp +``` + +## 4. Unwrap the response + +`dynamicInvoke` always wraps the connector response: + +```json +{ "response": { "statusCode": "OK", "body": { ...actual data... }, "headers": {...} } } +``` + +Always extract from `response.body`. Use `--query "response.body"` with `az rest`. + +| `response.statusCode` | Meaning | +|---|---| +| `OK` / `Created` / `Accepted` | Success — use `response.body` | +| `BadRequest` (400) | Check parameter names + body shape against the Swagger | +| `Unauthorized` (401) | Connection not consented — re-run [consent.md](consent.md) | +| `Forbidden` (403) | OAuth scope insufficient — re-consent with broader scopes | +| `NotFound` (404) | Wrong path (did you strip `/{connectionId}`?) or the resource doesn't exist | + +## Common pitfalls + +- **Inline JSON in PowerShell breaks** — always use `@$tmpFile` for `--body` +- **`Content-Type` in the inner `headers` returns 400** — omit it +- **Forgetting `/{connectionId}` strip** — Swagger path `/{connectionId}/datasets/...` becomes `dynamicInvoke` path `/datasets/...` +- **Using `parameters: { operationId: ... }` body** — that's an older shape; use `request: { method, path, ... }` diff --git a/Skills/General/azure-connectorgateway/references/dynamic-values.md b/Skills/General/azure-connectorgateway/references/dynamic-values.md new file mode 100644 index 0000000..cf5dcd2 --- /dev/null +++ b/Skills/General/azure-connectorgateway/references/dynamic-values.md @@ -0,0 +1,425 @@ +# Dynamic Values & Schema Resolution + +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. + +```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. + +**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.) +- Each method entry has an `operationId` field +- When you find the matching `operationId`, the path key (minus `/{connectionId}`) is what you pass to `dynamicInvoke` + +> **Strip `/{connectionId}` from the path.** The Swagger paths start with `/{connectionId}/...` +> but when calling `dynamicInvoke`, use only the part after `/{connectionId}`. +> Example: Swagger path `/{connectionId}/datasets/default/folders` → dynamicInvoke path `/datasets/default/folders` + +**⚠️ Literal path segments gotcha:** Some paths look like variables but are literal strings. +Example: `/notebooks/notebookKey/sections` — `notebookKey` is a **literal** path segment, NOT a +variable to substitute. The actual notebook key goes as a **query parameter** named `notebookKey`. +Always check the Swagger parameter definitions (`in: query` vs `in: path`) to know which is which. + +### Common connector paths (quick reference) + +| Connector | operationId | Path | Key params | +|-----------|-------------|------|------------| +| office365 | SendMailV2 | `/v2/Mail` | body: To, Subject, Body | +| office365 | GetEmails | `/datasets/default/messages` | folderPath (query) | +| office365 | OnNewEmailV3 | `/trigger1/datasets/default/messages` | folderPath (query) | +| onedriveforbusiness | ListFolder | `/datasets/default/folders/{id}/listchildren` | id (path) | +| onedriveforbusiness | CreateFile | `/datasets/default/files` | folderPath (query), name (query) | +| sharepointonline | GetItems | `/datasets/{dataset}/tables/{table}/items` | dataset, table (path) | +| sharepointonline | PostItem | `/datasets/{dataset}/tables/{table}/items` | dataset, table (path) | +| teams | GetAllTeams | `/beta/me/joinedTeams` | — | +| teams | GetChannels | `/beta/groups/{groupId}/channels` | groupId (path) | +| onenote | GetNotebooks | `/notebooks` | — | +| onenote | GetSectionsInNotebook | `/notebooks/notebookKey/sections` | notebookKey (query, NOT path) | +| onenote | OnNewPageInSection | `/trigger3/sections/Dynamic/pages` | notebookKey, sectionId (query) | + +## Step 2: Understand value vs display name + +When resolving dynamic values, every item in the response has TWO fields: +- **value** (from `value-path`) — the ID/key to pass to APIs. Example: `"b!oBRIcPVy5ke..."` +- **display name** (from `value-title`) — what to show the user. Example: `"Documents"` + +**CRITICAL rules:** +1. Show the **display name** to the user for selection +2. Store the **value** internally +3. When a subsequent parameter depends on this one, pass the **value** (NEVER the display name) + +Example dependency chain: +``` +Parameter 1: "notebookKey" (x-ms-dynamic-values → operationId: "GetNotebooks") + → API returns: [{Name: "Work Notebook", Key: "Aprana @ Microsoft|$|https://..."}] + → Show user: ["Work Notebook", "Personal Notes"] + → User picks "Work Notebook" → STORE value = "Aprana @ Microsoft|$|https://..." + +Parameter 2: "sectionId" (x-ms-dynamic-values → operationId: "GetSectionsInNotebook", parameters: {"notebookKey": {"parameter": "notebookKey"}}) + → Call GetSectionsInNotebook with notebookKey = STORED VALUE (the long key, NOT "Work Notebook") + → API returns: [{Name: "Meeting Notes", Id: "section-id-123"}] + → Show user: ["Meeting Notes", "Ideas"] + → User picks "Meeting Notes" → STORE value = "section-id-123" +``` + +> **⚠️ The most common agent mistake:** Using the display name ("Work Notebook") instead of the +> stored value ("Aprana @ Microsoft|$|https://...") when calling the next operation. +> This causes 404s or empty results because the API expects the ID/key, not the human-readable name. + +## `x-ms-dynamic-values` — Flat list of options + +The Swagger extension specifies an `operationId` to call and how to extract items: +```json +"x-ms-dynamic-values": { + "operationId": "GetFolders", + "value-path": "Id", + "value-title": "DisplayName", + "value-collection": "value", + "parameters": { "dataset": { "parameter": "dataset" } } +} +``` + +**How to handle:** +1. Resolve `operationId` → find the matching path in the Swagger (fetched in Step 1): + - Search through `paths` for an entry whose method has `operationId` matching the one in the extension + - Extract the path key and strip `/{connectionId}` prefix + - Extract the HTTP method (get, post, etc.) + ``` + Example: operationId "GetFolders" found at path "/{connectionId}/datasets/default/folders" with method "get" + → dynamicInvoke path = "/datasets/default/folders", method = "GET" + ``` +2. Resolve `parameters` — substitute **values** (NOT display names) from previously collected params: + - `{"parameter": "dataset"}` → use the **stored value** the user selected for `dataset` (the `value-path` field, NOT the display name) + - `{"value": "default"}` → use literal `"default"` + - Plain string → use as-is +3. Call `dynamicInvoke`: + ```powershell + az rest --method POST ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/connections/{conn}/dynamicInvoke?api-version=2026-05-01-preview" ` + --body '{\"request\":{\"method\":\"GET\",\"path\":\"{resolved_path}\"}}' ` + --headers "Content-Type=application/json" -o json + ``` + > **PowerShell JSON quoting tips:** + > - Simple static: `'{\"request\":{\"method\":\"GET\",\"path\":\"/datasets\"}}'` + > - Dynamic values with special chars (`!`, `'`, spaces): write to temp file, use `--body @$tmpFile` + > ```powershell + > $body = @{request=@{method="GET";path="/datasets/default/files/$encodedId"}} | ConvertTo-Json -Depth 5 -Compress + > $tmpFile = New-TemporaryFile; Set-Content $tmpFile $body + > az rest --method POST --url "..." --body "@$tmpFile" --headers "Content-Type=application/json" -o json + > Remove-Item $tmpFile + > ``` + +4. Unwrap response: extract `response.body` +5. If `value-collection` is set (e.g., `"value"`), navigate to that array: `response.body.value` +6. For each item: extract TWO things: + - `value-path` field (e.g., `Id`) → **this is the VALUE to store and pass to subsequent API calls** + - `value-title` field (e.g., `DisplayName`) → **this is what you show the user** +7. Present `value-title` items as choices via `ask_user` +8. **STOP and wait for user selection** +9. **Store the selected item's `value-path` field** — you will need it if any subsequent parameter depends on this one + +> **⚠️ NEVER use the display name in API calls.** If the next parameter depends on this one +> (via `{"parameter": "thisParam"}`), pass the stored VALUE, not what the user saw. + +**Common examples:** +| Connector | Parameter | Path | value-path | value-title | +|-----------|-----------|------|-----------|-------------| +| OneDrive | `folderPath` | `/datasets/default/folders` | `Path` | `DisplayName` | +| Teams | `groupId` | `/beta/me/joinedTeams` | `id` | `displayName` | +| SharePoint | `dataset` | `/datasets` | `Url` | `Name` | +| Office365 | `folderPath` | `/datasets/default/folders` | `Path` | `DisplayName` | + +## `x-ms-dynamic-list` — Same as dynamic-values with nesting + +Identical to `x-ms-dynamic-values` except: +- The `operationId` may be nested: check `dynamicState.operationId` or + `dynamicState.extension.operationId` if direct `operationId` is missing +- Supports both `value-path` and `valuePath` (camelCase variant) +- Handle exactly the same way as `x-ms-dynamic-values` + +## `x-ms-dynamic-tree` — Hierarchical browsing (folder tree) + +Used for folder pickers where the user navigates level-by-level. The extension defines: +- `open` — fetches root-level items +- `browse` — fetches children of a selected item +- `settings` — controls what can be selected + +```json +"x-ms-dynamic-tree": { + "open": { + "operationId": "ListRootFolders", + "itemValuePath": "Id", + "itemTitlePath": "DisplayName", + "itemsPath": "value", + "itemIsParent": "IsFolder eq true" + }, + "browse": { + "operationId": "ListChildFolders", + "itemValuePath": "Id", + "itemTitlePath": "DisplayName", + "itemsPath": "value", + "itemIsParent": "IsFolder eq true", + "parameters": { "id": { "selectedItemValuePath": "Id" } } + }, + "settings": { "canSelectParentNodes": true, "canSelectLeafNodes": false } +} +``` + +### Algorithm + +**Step T1: Get the `open` operation path from the Swagger.** +``` +open.operationId = "ListRootFolders" +→ Find in Swagger paths → "/{connectionId}/datasets/default/folders" (GET) +→ dynamicInvoke path = "/datasets/default/folders" +``` + +**Step T2: Call `open` via dynamicInvoke.** +```powershell +$body = @{request=@{method="GET";path="/datasets/default/folders"}} | ConvertTo-Json -Compress +$tmp = New-TemporaryFile; Set-Content $tmp $body +az rest --method POST ` + --url ".../{gw}/connections/{conn}/dynamicInvoke?api-version=2026-05-01-preview" ` + --body "@$tmp" -o json > $env:TEMP\tree-response.json +Remove-Item $tmp +``` + +**Step T3: Parse the response and present items.** +- Navigate to `response.body` → then follow `itemsPath` (e.g., `"value"` → `response.body.value`) +- For each item, extract: + - `itemTitlePath` field (e.g., `DisplayName`) → show to user + - `itemValuePath` field (e.g., `Id`) → store internally + - Check `itemIsParent` condition → mark with 📁 if true (can browse deeper) + +Present to user: +``` +📁 Apps +📁 Documents +📁 Desktop +📁 EmailAttachments +✅ Select root (/) ← only show if canSelectParentNodes is true +``` +**STOP and wait for user selection.** + +**Step T4: User selects a folder → BROWSE deeper.** + +1. Get the `browse` operation path from the Swagger: + ``` + browse.operationId = "ListChildFolders" + → Found in Swagger → "/{connectionId}/datasets/default/folders/{id}" (GET) + → dynamicInvoke path template = "/datasets/default/folders/{id}" + ``` + +2. Substitute the selected item's value into the path using `browse.parameters`: + ``` + browse.parameters = { "id": { "selectedItemValuePath": "Id" } } + + This means: take the "Id" field from the item the user selected, + and substitute it for {id} in the path. + ``` + +3. **URL-encode the value** — IDs often contain `!`, `/`, spaces, and other special chars: + ```powershell + $selectedId = "b!oBRIcPVy5ke..." # The stored VALUE from user's selection + $encodedId = [System.Uri]::EscapeDataString($selectedId) + $browsePath = "/datasets/default/folders/$encodedId" + ``` + +4. Call dynamicInvoke with the constructed browse path: + ```powershell + $body = @{request=@{method="GET";path=$browsePath}} | ConvertTo-Json -Compress + $tmp = New-TemporaryFile; Set-Content $tmp $body + az rest --method POST ` + --url ".../{gw}/connections/{conn}/dynamicInvoke?api-version=2026-05-01-preview" ` + --body "@$tmp" -o json > $env:TEMP\tree-response.json + Remove-Item $tmp + ``` + +5. Parse response same as Step T3 — present child items to user. + +**Step T5: Present children. STOP and wait.** +``` +📁 Project Files +📁 Templates +📄 README.md +✅ Select "Documents" ← show if canSelectParentNodes is true +``` + +**Step T6: Repeat T4-T5** until user selects (clicks ✅) or picks a leaf item. + +### Final value to use + +When the user makes their final selection: +- Use the `itemValuePath` field (e.g., `Id` or `Path`) as the parameter value +- Some connectors expect `Path` (e.g., `/Documents/Copilot`), others expect `Id` +- Check the Swagger parameter definition for the original parameter to see what type it expects + +### Key rules for dynamic tree + +- **Always use `@file` pattern** for browse calls (IDs have special chars that break inline JSON) +- **Always URL-encode** values with `[System.Uri]::EscapeDataString()` +- **Always STOP at each level** — let the user choose to go deeper or select current +- **Use VALUE not display name** when constructing browse paths +- If `browse` is not defined in the extension, reuse `open` with the selected item's value as a parameter +- Check `itemIsParent` to know which items can be browsed deeper (📁) vs are leaf items (📄) + +## `x-ms-dynamic-schema` — Schema depends on prior selection + +The parameter's available fields/columns change based on another parameter's value. +```json +"x-ms-dynamic-schema": { + "operationId": "GetTable", + "parameters": { + "dataset": { "parameter": "dataset" }, + "table": { "parameter": "table" } + }, + "value-path": "Schema/Items" +} +``` + +### Step-by-step algorithm: + +**Step S1: Identify the dependency chain.** +Each entry like `"dataset": { "parameter": "dataset" }` means the schema operation +needs the value the user already selected for `dataset`. + +Example dependency chain for SharePoint `PostItem`: +``` +dataset (site) ← x-ms-dynamic-values via GetDataSets + ↓ +table (list) ← x-ms-dynamic-values via GetTables (depends on dataset) + ↓ +item (body) ← x-ms-dynamic-schema via GetTable (depends on dataset + table) +``` + +**Step S2: Collect all dependent parameters first.** +Follow the `x-ms-dynamic-values` flow for each dependency: +```powershell +# Step S2a: Get SharePoint sites (dataset parameter) +az rest --method POST ` + --url ".../connections/{sp_conn}/dynamicInvoke?api-version=2026-05-01-preview" ` + --body '{\"request\":{\"method\":\"GET\",\"path\":\"/datasets\"}}' ` + --headers "Content-Type=application/json" ` + --query "response.body.value[].{Name:Name, Display:DisplayName}" -o table +# → Present sites as choices. STOP and wait. + +# Step S2b: Get lists for the selected site (table parameter) +$siteEncoded = [System.Uri]::EscapeDataString("https://contoso.sharepoint.com/sites/HR") +$bodyJson = '{"request":{"method":"GET","path":"/datasets/' + $siteEncoded + '/tables"}}' +$tmpBody = [System.IO.Path]::GetTempFileName() +$bodyJson | Out-File -FilePath $tmpBody -Encoding ascii -NoNewline + +az rest --method POST ` + --url ".../connections/{sp_conn}/dynamicInvoke?api-version=2026-05-01-preview" ` + --body "@$tmpBody" ` + --headers "Content-Type=application/json" ` + --query "response.body.value[].{Name:Name, Display:DisplayName}" -o table + +Remove-Item $tmpBody -ErrorAction SilentlyContinue +# → Present lists as choices. STOP and wait. +``` + +**Step S3: Resolve the schema operation's path.** +Find the operation whose `operationId` matches. Substitute collected param values. +``` +GetTable → GET /$metadata.json/datasets/{dataset}/tables/{table} +``` + +**Step S4: Call the schema operation via `dynamicInvoke`.** +```powershell +$siteEncoded = [System.Uri]::EscapeDataString($selectedSite) +$listEncoded = [System.Uri]::EscapeDataString($selectedList) +$bodyJson = '{"request":{"method":"GET","path":"/$metadata.json/datasets/' + $siteEncoded + '/tables/' + $listEncoded + '"}}' +$tmpBody = [System.IO.Path]::GetTempFileName() +$bodyJson | Out-File -FilePath $tmpBody -Encoding ascii -NoNewline + +az rest --method POST ` + --url ".../connections/{sp_conn}/dynamicInvoke?api-version=2026-05-01-preview" ` + --body "@$tmpBody" ` + --headers "Content-Type=application/json" ` + --query "response.body" -o json + +Remove-Item $tmpBody -ErrorAction SilentlyContinue +``` + +**Step S5: Navigate the response using `value-path`.** +Split on `/` and walk each key: `Schema/Items` → `response.body.Schema.Items` + +Example result: +```json +{ + "type": "object", + "properties": { + "Title": { "type": "string", "x-ms-display": "Title" }, + "StartDate": { "type": "string", "format": "date", "x-ms-display": "Start Date" }, + "Manager": { "type": "string", "x-ms-display": "Manager" } + }, + "required": ["Title"] +} +``` + +**Step S6: Present fields to user. STOP and wait.** +``` +Available columns in "New Hires" list: +• Title (string) — REQUIRED +• StartDate (date) +• Manager (string) +Which fields do you want to populate, and what values? +``` + +**Step S7: Build the body with user-provided values.** + +**Key rules for dynamic schema:** +- **Collect dependencies in order** — schema operation needs values from prior selections +- **Always resolve `operationId` to path** from the Swagger +- **Navigate `value-path`** by splitting on `/` and walking each key +- **Present ALL fields** with types and required markers +- **Do NOT assume field values** — always ask the user +- **URL-encode** all path parameters + +## No extension — static enum or free-form + +- If the parameter has a **static enum**, present values as choices. **STOP and wait.** +- If the parameter is **free-form**, ask the user directly. **STOP and wait.** + +## Response unwrapping + +The `dynamicInvoke` response is always double-wrapped: +```json +{"response": {"statusCode": "OK", "body": { ...actual data... }, "headers": {...}}} +``` +Always extract from `response.body`. Use `--query "response.body"` with `az rest`. + +## When to STOP vs. use defaults + +| Parameter type | Action | +|---------------|--------| +| Any `x-ms-dynamic-*` extension | **Always STOP** — fetch, present choices, wait for user | +| Static enum from Swagger | **Always STOP** — present choices, wait for user | +| Free-form with obvious default (e.g., `folderPath=Inbox`) | Use default BUT tell user | +| Free-form with no obvious default | **Always STOP** — ask the user | +| Optional parameters | **Skip** unless user mentioned them | diff --git a/Skills/General/azure-connectorgateway/references/gotchas.md b/Skills/General/azure-connectorgateway/references/gotchas.md new file mode 100644 index 0000000..756cdbe --- /dev/null +++ b/Skills/General/azure-connectorgateway/references/gotchas.md @@ -0,0 +1,30 @@ +# Gotchas & Troubleshooting + +Common issues for the generic connector-gateway skill and their fixes. + +| Issue | Solution | +|-------|----------| +| **Trigger not firing** (connector event) | Make sure `gateway-acl` exists on the connection (gateway MI → connection). Without it the subscription silently fails. See [trigger-setup.md](trigger-setup.md) Step 4. | +| **Trigger state is `Enabled` but no runs** | Check `triggerConfigs/{name}/runs` for errors. Most commonly: the gateway couldn't reach `callbackUrl` (4xx/5xx from your endpoint) or authentication mismatch. | +| **Trigger run shows `Unauthorized` from callback** | Your callback URL's auth doesn't match `notificationDetails.authentication`. Re-check the type/audience/secret. See [notification-authentication.md](notification-authentication.md). | +| **`ManagedServiceIdentity` callback fails with "audience required"** | `audience` is mandatory on MSI auth. Use the API's resource/audience URI. | +| **`ManagedServiceIdentity` callback fails with "identity not configured"** | The requested MI isn't attached to the gateway. Either omit `identity` (uses SystemAssigned) or attach the UAMI to the gateway. See [notification-authentication.md](notification-authentication.md). | +| **Connection stuck in `Error` / `Unauthenticated`** | This is the normal pre-consent state. Run [consent.md](consent.md) and have the user complete the browser flow. If still stuck after consent, regenerate the consent link — do NOT retry with different body formats. | +| **Consent redirect shows error** | Body MUST be `{"parameters":[{"objectId":"...","tenantId":"...","redirectUrl":"https://microsoft.com","parameterName":"token"}]}`. Get `objectId`/`tenantId` from the connection's `properties.createdBy`. Always open with `Start-Process`. | +| **`dynamicInvoke` 400: `parameters` not valid** | Use `{"request": {"method":..., "path":...}}` format. The older `{"parameters": {"operationId":...}}` format is not supported. | +| **`dynamicInvoke` 400: `Content-*` headers** | Do NOT include `Content-Type` (or any other `Content-*` header) inside the inner `request.headers`. | +| **`dynamicInvoke` returns `NotFound`** | Wrong path: did you strip `/{connectionId}` from the Swagger path? Or the operation expects path-segment parameters that you put in `queries`. Re-check the Swagger `in:` markers. | +| **`dynamicInvoke` browse calls fail with mangled JSON** | IDs often contain `!`, `'`, spaces. Always use `@$tmpFile` for the body and `[System.Uri]::EscapeDataString()` for URL path segments. | +| **`az rest --body` "Unsupported Media Type"** | Inline JSON strings get mangled by PowerShell quoting. Always use `@$tmpFile`: write the body to a temp file and pass `--body "@$tmpFile"`. | +| **Swagger paths include `/{connectionId}/...`** | Strip the prefix when calling `dynamicInvoke` — the connection context is already set by the endpoint. | +| **`x-ms-dynamic-*` resolution returns empty** | The display value you used for the prior parameter wasn't the actual value. Always pass the **stored value** (from `value-path`), not the display name (from `value-title`). See [dynamic-values.md](dynamic-values.md). | +| **MCP tool calls 403 from MCP client** | The caller's objectId isn't in the MCP server config's access policies. Add an access policy on `mcpServerConfigs/{name}/accessPolicies/{policy}`. See [mcp-server-config.md](mcp-server-config.md). | +| **MCP tool calls 401** | Underlying connection isn't consented or the consent expired. Re-run [consent.md](consent.md). | +| **`resourceAuth` rejected on `ManagedMcpServer`** | `resourceAuth` is only valid on `HostedMcpServer`. Remove it. | +| **MCP `userParameters[].value` is the display name** | The connector rejects it because it expects the underlying ID/key. Re-resolve via `dynamicInvoke` and store the `value-path` field. | +| **`callbackTarget` rejected** | That field does not exist in the schema. Use `notificationDetails.callbackUrl`. | +| **PowerShell `ConvertFrom-Json` fails on Swagger** | `az rest ... export=true` returns content that piping breaks. Always `-o json > $env:TEMP\swagger.json` first, then read the file. | +| **Cleanup order** | Delete trigger configs and MCP server configs → delete access policies on connections → delete connections → delete gateway. Trigger configs hold subscriptions, so delete them first to avoid orphan webhooks. | +| **403 from `az rest` against ARM** | Your Azure CLI identity doesn't have RBAC on the resource group / gateway. You need at least `Microsoft.Web/connectorGateways/*` (Contributor or a custom role). | +| **PUT to gateway fails with "region not supported"** | Try another region. Common supported regions: `eastus`, `eastus2`, `westus`, `westus2`, `westus3`, `northeurope`, `westeurope`, `australiaeast`, `southeastasia`, `brazilsouth`. | +| **Provider not registered** | `az provider register --namespace Microsoft.Web` and wait for `Registered` state. | diff --git a/Skills/General/azure-connectorgateway/references/mcp-server-config.md b/Skills/General/azure-connectorgateway/references/mcp-server-config.md new file mode 100644 index 0000000..19fafc2 --- /dev/null +++ b/Skills/General/azure-connectorgateway/references/mcp-server-config.md @@ -0,0 +1,229 @@ +# MCP Server Config + +Expose selected connector operations as Model Context Protocol (MCP) tools at a +gateway-hosted endpoint. Any MCP client (Claude Desktop, VS Code, an Agent SDK) +can connect to the resulting `mcpEndpointUrl` and call the tools — the gateway +forwards each tool call to the underlying connector using the stored OAuth +credential. + +## Kinds + +| `kind` | What it is | Use this when | +|---|---|---| +| `ManagedMcpServer` *(default)* | A **connector-backed** MCP server — you map MCP tools to connector operations. **This is the main pattern for this skill.** | You want an LLM to use Office 365 / Teams / SharePoint / GitHub / ... operations as tools. | +| `HostedMcpServer` | A **container-image-backed** MCP server (e.g., `mcp-sql`). The gateway runs the image. | You want one of the curated containerized MCP servers (currently a small registry). | + +> The rest of this doc focuses on `ManagedMcpServer`. For `HostedMcpServer`, the +> shape is the same except `properties.hostedMcpServer.hostedMcpServerId` (e.g., +> `"mcp-sql"`) replaces the `connectors[]` array, and `properties.resourceAuth` +> is required when `authenticationMode` is set. See "Hosted MCP server" at the end. + +## Schema (ManagedMcpServer) + +```json +{ + "properties": { + "description": "Office 365 productivity tools for the agent", + "connectors": [ + { + "name": "office365", + "displayName": "Office 365", + "connectionName": "o365-conn", + "operations": [ + { + "name": "Send_Email_(V2)", + "displayName": "Send email", + "description": "Send an email via Office 365.", + "userParameters": [ + { "name": "from", "displayName": "From", "value": "alice@contoso.com", "displayValue": "Alice" } + ] + } + ] + } + ] + } +} +``` + +### Field reference + +| Path | Required | Meaning | +|---|---|---| +| `properties.description` | optional | Free-form description | +| `properties.connectors[]` | required | One entry per backing connector | +| `connectors[].name` | required | Connector key (e.g., `office365`, `sharepointonline`, `teams`) | +| `connectors[].displayName` | required | Human-readable name shown to MCP clients | +| `connectors[].connectionName` | required | Name of the **already-created** connection on the same gateway | +| `connectors[].operations[]` | required | Operations to expose as MCP tools | +| `operations[].name` | required | The operation `name` from the connector's `apiOperations` (e.g., `Send_Email_(V2)`) | +| `operations[].displayName` | required | Tool name shown to the LLM | +| `operations[].description` | optional | Tool description shown to the LLM — write it for the LLM, not for humans | +| `operations[].userParameters[]` | optional | **Pre-bound** values for operation parameters — see below | + +### `userParameters` — pre-bound parameter values + +Each entry in `userParameters[]` is a parameter whose value is **fixed at config +time** rather than supplied by the LLM at call time. Typical examples: + +- "Always post to this Teams channel" +- "Always send from this mailbox" +- "Always upload to this folder" + +```json +{ + "name": "channelId", + "displayName": "Channel", + "value": "19:abc...@thread.tacv2", + "displayValue": "Logic Apps / general" +} +``` + +- `value` is what's sent to the connector (the ID/key) +- `displayValue` is human-readable +- Parameters **not** listed in `userParameters[]` remain LLM-supplied inputs at tool-call time + +> Resolve dynamic IDs by running the parameter's `x-ms-dynamic-*` lookups +> against the connection — exactly like [trigger-setup.md](trigger-setup.md) Step 2. +> See [dynamic-values.md](dynamic-values.md). **STOP** at each dynamic param for user selection. + +## PUT the config + +```powershell +$body = @{ + properties = @{ + description = "Office 365 productivity tools" + connectors = @( + @{ + name = "office365" + displayName = "Office 365" + connectionName = "o365-conn" + operations = @( + @{ + name = "Send_Email_(V2)" + displayName = "Send email" + description = "Send an email via Office 365." + userParameters = @( + @{ name = "from"; displayName = "From"; value = "alice@contoso.com"; displayValue = "Alice" } + ) + } + ) + } + ) + } +} | ConvertTo-Json -Depth 10 -Compress + +$tmp = New-TemporaryFile; Set-Content $tmp $body +az rest --method PUT ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/mcpServerConfigs/{mcp_name}?api-version=2026-05-01-preview" ` + --body "@$tmp" +Remove-Item $tmp +``` + +## Get the endpoint URL + +```bash +az rest --method GET \ + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/mcpServerConfigs/{mcp_name}?api-version=2026-05-01-preview" \ + --query "properties.mcpEndpointUrl" -o tsv +``` + +Format looks like: +`https:///subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/mcpServerConfigs/{mcp_name}/mcp` + +Point your MCP client at this URL. + +## Listing operations to expose + +Use the connector's `apiOperations` endpoint and filter out trigger operations +(they're not tools): + +```bash +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[?properties.trigger == null].{name:name, summary:properties.summary}" -o table +``` + +The `name` value from this list is exactly what goes into `operations[].name`. + +## Access policies on the MCP server config + +The MCP endpoint accepts callers based on access policies on the config itself. +Grant access to whoever (user, MI, app) needs to call the MCP tools: + +```powershell +$aclBody = @{ + location = "{location}" + properties = @{ + principal = @{ + type = "ActiveDirectory" + identity = @{ objectId = "{caller_object_id}"; tenantId = "{tenant_id}" } + } + } +} | ConvertTo-Json -Depth 5 -Compress +$tmp = New-TemporaryFile; Set-Content $tmp $aclBody +az rest --method PUT ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/mcpServerConfigs/{mcp_name}/accessPolicies/{policy_name}?api-version=2026-05-01-preview" ` + --body "@$tmp" +Remove-Item $tmp +``` + +List existing policies: + +```bash +az rest --method GET \ + --url ".../mcpServerConfigs/{mcp_name}/accessPolicies?api-version=2026-05-01-preview" \ + --query "value[].{name:name, objectId:properties.principal.identity.objectId}" -o table +``` + +## Update / delete + +```bash +# Update (re-PUT with the new full body) +az rest --method PUT --url ".../mcpServerConfigs/{name}?api-version=2026-05-01-preview" --body "@$tmp" + +# Delete +az rest --method DELETE --url ".../mcpServerConfigs/{name}?api-version=2026-05-01-preview" +``` + +## Hosted MCP server (briefly) + +For curated containerized MCP servers (e.g., `mcp-sql`): + +```json +{ + "kind": "HostedMcpServer", + "properties": { + "description": "Hosted SQL MCP server", + "hostedMcpServer": { + "hostedMcpServerId": "mcp-sql" + }, + "authenticationMode": "OnBehalfOfUserWithApp", + "resourceAuth": { + "adminAppRegistration": { + "clientId": "{aad-app-client-id}", + "tenantId": "{tenant-id}", + "name": "my-mcp-admin-app" + }, + "targetResource": "https://database.windows.net/", + "identity": { "type": "SystemAssigned" } + } + } +} +``` + +Validation rules (from BPM tests): + +- `resourceAuth` is required when `authenticationMode` ∈ {`OnBehalfOfUserWithApp`, `AppOnly`}, and forbidden when `authenticationMode` is `NotSpecified` or null. +- `resourceAuth` is **only** valid on `HostedMcpServer` — providing it on `ManagedMcpServer` fails validation. +- For `identity.type = SystemAssigned`, the gateway must have a SystemAssigned MI; for user-assigned, the resource ID must be attached. + +## Common mistakes + +| Mistake | Fix | +|---|---| +| Putting `connectors[]` for a Hosted MCP server | Hosted servers use `hostedMcpServer.hostedMcpServerId` instead. | +| Putting `resourceAuth` on a `ManagedMcpServer` | Remove it — only valid on `HostedMcpServer`. | +| `operations[].name` not matching the connector's apiOperations | Re-fetch with `apiOperations?api-version=2016-06-01` and copy the `name` verbatim — case + parens matter. | +| Guessing dynamic-value IDs in `userParameters[].value` | Always resolve via `dynamicInvoke` first ([dynamic-values.md](dynamic-values.md)). | +| Forgetting access policies | The MCP endpoint will 403 your MCP client. Add the caller's objectId. | +| Skipping consent on the underlying connection | Tool calls fail with 401 — go through [consent.md](consent.md) first. | diff --git a/Skills/General/azure-connectorgateway/references/notification-authentication.md b/Skills/General/azure-connectorgateway/references/notification-authentication.md new file mode 100644 index 0000000..76a47ba --- /dev/null +++ b/Skills/General/azure-connectorgateway/references/notification-authentication.md @@ -0,0 +1,158 @@ +# Notification (callback) Authentication + +Reference for `properties.notificationDetails.authentication` on a trigger config. +Pick one type per trigger. If you omit `authentication` entirely, the gateway just +POSTs to `callbackUrl` with no extra credentials — fine for callback URLs that +already embed their own auth (e.g., Logic App SAS in the querystring or a Function +App key embedded in the URL). + +## Shapes + +### 1) `QueryString` — append `?name=value` + +Useful for Function App keys, ngrok tokens, etc. + +```json +"authentication": { + "type": "QueryString", + "name": "code", + "value": "{function_key}" +} +``` + +### 2) `Raw` — literal `Authorization` header + +You provide the full ` `. + +```json +"authentication": { + "type": "Raw", + "scheme": "Bearer", + "parameter": "{static_token}" +} +``` + +> Use this when you have a long-lived static bearer token. For dynamic tokens, +> prefer `ManagedServiceIdentity` or `ActiveDirectoryOAuth`. + +### 3) `Basic` — HTTP Basic auth + +```json +"authentication": { + "type": "Basic", + "username": "{user}", + "password": "{pass}" +} +``` + +### 4) `ManagedServiceIdentity` — gateway uses its own MI + +The gateway acquires an Entra token for `audience` using its managed identity +and sends it as `Authorization: Bearer {token}`. + +**System-assigned** (gateway must have `identity.type = SystemAssigned`): + +```json +"authentication": { + "type": "ManagedServiceIdentity", + "audience": "https://your-api.example.com/" +} +``` + +**User-assigned** (gateway must have the user-assigned identity attached): + +```json +"authentication": { + "type": "ManagedServiceIdentity", + "audience": "api://{aad-app-id}", + "identity": "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{name}" +} +``` + +> Validation rules (from BPM `AIGatewayApiTests_NotificationAuthValidation_*`): +> - `audience` is required (non-empty) +> - If `identity` is omitted → gateway must have a SystemAssigned identity +> - If `identity` is set → it must be a valid `/subscriptions/.../userAssignedIdentities/{name}` resource ID AND that identity must already be attached to the gateway + +To attach a user-assigned identity to an existing gateway: + +```powershell +$body = @{ + identity = @{ + type = "SystemAssigned,UserAssigned" # or just "UserAssigned" + userAssignedIdentities = @{ + "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{name}" = @{} + } + } +} | ConvertTo-Json -Depth 5 -Compress +$tmp = New-TemporaryFile; Set-Content $tmp $body +az rest --method PATCH ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}?api-version=2026-05-01-preview" ` + --body "@$tmp" +Remove-Item $tmp +``` + +### 5) `ActiveDirectoryOAuth` — Entra app (client credentials) + +```json +"authentication": { + "type": "ActiveDirectoryOAuth", + "tenant": "{tenant-id}", + "audience": "api://{aad-app-id}", + "clientId": "{client-id}", + "secret": "{client-secret}" +} +``` + +> Store the secret in Key Vault and reference it via Key Vault links if your +> deployment pipeline supports it. Plain-text secrets in trigger configs are +> persisted as-is. + +### 6) `ClientCertificate` — mutual TLS + +```json +"authentication": { + "type": "ClientCertificate", + "pfx": "{base64-pfx}", + "password": "{pfx-password}" +} +``` + +`pfx` is the base64-encoded PKCS#12 file. + +## Picking an auth type — decision tree + +1. **Callback URL already contains a secret/token** (e.g. Logic App SAS, Function App `?code=`) + → omit `authentication`. The URL is self-authenticating. +2. **Function App with a key** but you want to keep the key out of the URL + → `QueryString` with `name: "code"`. +3. **Your own API, accepts Entra tokens, gateway is in the same tenant** + → `ManagedServiceIdentity` (system-assigned is simplest). +4. **Your own API, accepts Entra tokens, you want fine-grained control / different tenant** + → `ActiveDirectoryOAuth` with a dedicated app registration. +5. **Legacy API with HTTP Basic** + → `Basic`. +6. **mTLS endpoint** + → `ClientCertificate`. +7. **Static long-lived bearer token** + → `Raw` (last resort — rotates are manual). + +## Where this fits + +In the trigger config PUT body: + +```json +{ + "properties": { + "type": "Recurrence", + "recurrence": { "frequency": "Minute", "interval": 5 }, + "notificationDetails": { + "callbackUrl": "https://my-api.contoso.com/hook", + "httpMethod": "Post", + "authentication": { "type": "ManagedServiceIdentity", "audience": "api://my-api" } + } + } +} +``` + +See [trigger-setup.md](trigger-setup.md) for the full PUT body templates per trigger source. diff --git a/Skills/General/azure-connectorgateway/references/prerequisites.md b/Skills/General/azure-connectorgateway/references/prerequisites.md new file mode 100644 index 0000000..029f2bc --- /dev/null +++ b/Skills/General/azure-connectorgateway/references/prerequisites.md @@ -0,0 +1,44 @@ +# Prerequisites + +## Required + +| Requirement | Check | Install | +|-------------|-------|---------| +| Azure CLI 2.55+ | `az --version` | [Install](https://learn.microsoft.com/cli/azure/install-azure-cli) | +| Azure login | `az account show` | `az login` | +| Python 3.8+ *(only if parsing Swagger locally)* | `python --version` | [python.org](https://python.org) | + +> **No extensions required.** There are no `az connectorgateway` or `az aigateway` +> commands. Everything goes through `az rest` against ARM. Do not try to install +> a connector-gateway-specific extension. + +## Azure resource providers + +Register once per subscription: + +```bash +az provider register --namespace Microsoft.Web +# Wait until "registrationState": "Registered" +az provider show --namespace Microsoft.Web --query "registrationState" -o tsv +``` + +## Resource group + +Pick or create one. The gateway, connections, trigger configs, and MCP server +configs all live inside it: + +```bash +az group create --name {rg} --location {location} +``` + +Common locations where connector gateways are available: `eastus`, `eastus2`, +`westus`, `westus2`, `westus3`, `northeurope`, `westeurope`, `australiaeast`, +`southeastasia`, `brazilsouth`. If a PUT fails with a region error, try a different +location. + +## Optional: identity for `ManagedServiceIdentity` callback auth + +If you want the trigger gateway to authenticate to your callback URL with a +**user-assigned** managed identity, create it ahead of time and attach it to the +gateway when creating the gateway resource. See +[notification-authentication.md](notification-authentication.md) for the `identity` block shape. diff --git a/Skills/General/azure-connectorgateway/references/quickstart.md b/Skills/General/azure-connectorgateway/references/quickstart.md new file mode 100644 index 0000000..d3a88d3 --- /dev/null +++ b/Skills/General/azure-connectorgateway/references/quickstart.md @@ -0,0 +1,32 @@ +# Quick Start + +```bash +# 1. Login +az login +az account show --query "{subscription:id, tenant:tenantId}" -o table + +# 2. List connector gateways in a resource group +az rest --method GET \ + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways?api-version=2026-05-01-preview" \ + --query "value[].{name:name, location:location, identityType:identity.type, principalId:identity.principalId}" -o table + +# 3. List connections on a gateway +az rest --method GET \ + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/connections?api-version=2026-05-01-preview" \ + --query "value[].{name:name, connector:properties.connectorName, status:properties.statuses[0].status}" -o table + +# 4. List trigger configs on a gateway +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, type:properties.type, connector:properties.connectionDetails.connectorName, op:properties.operationName, callbackUrl:properties.notificationDetails.callbackUrl}" -o table + +# 5. List MCP server configs on a gateway +az rest --method GET \ + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/mcpServerConfigs?api-version=2026-05-01-preview" \ + --query "value[].{name:name, kind:kind, endpoint:properties.mcpEndpointUrl}" -o table + +# 6. Discover operations for a connector +az rest --method GET \ + --url "https://management.azure.com/subscriptions/{sub}/providers/Microsoft.Web/locations/{location}/managedApis/office365/apiOperations?api-version=2016-06-01" \ + --query "value[].{name:name, summary:properties.summary, trigger:properties.trigger}" -o table +``` diff --git a/Skills/General/azure-connectorgateway/references/trigger-flow.md b/Skills/General/azure-connectorgateway/references/trigger-flow.md new file mode 100644 index 0000000..10b97db --- /dev/null +++ b/Skills/General/azure-connectorgateway/references/trigger-flow.md @@ -0,0 +1,104 @@ +# Trigger Flow & Lifecycle + +How a connector-gateway trigger actually works end-to-end, and the ARM operations +you use to manage its lifecycle. + +## Architecture + +### Connector-event trigger + +``` +┌─────────────────┐ 1. Subscribe ┌──────────────────────┐ +│ Connector SaaS │ ◀──────────────────── │ Connector Gateway │ +│ (Office 365, │ │ (Microsoft.Web/ │ +│ SharePoint, │ 2. Webhook event │ connectorGateways)│ +│ Teams, ...) │ ────────────────────▶ │ │ +└─────────────────┘ │ │ + │ 3. POST to │ + │ callbackUrl │ + └───────────┬──────────┘ + │ + ▼ + ┌────────────────────────┐ + │ Your callback URL │ + │ (any HTTPS endpoint: │ + │ Function App, │ + │ App Service, Logic │ + │ App, custom API ...) │ + └────────────────────────┘ +``` + +1. When you `PUT` a connector-event trigger config, the gateway uses the + underlying **connection** to subscribe to the connector (via the operation's + `x-ms-notification-url`). +2. When the SaaS detects the event, it POSTs a webhook to the gateway. +3. The gateway forwards the payload to your `callbackUrl`, applying any + configured `authentication`. + +### Recurrence / SlidingWindow trigger + +``` +┌──────────────────────┐ 1. Timer fires ┌────────────────────────┐ +│ Connector Gateway │ ──────────────────▶ │ Your callback URL │ +│ (scheduler) │ │ (any HTTPS endpoint) │ +└──────────────────────┘ └────────────────────────┘ +``` + +No external SaaS, no subscription, no connection needed. Just a timer inside the +gateway that POSTs to your `callbackUrl` per the `recurrence` schedule. + +## Required pieces + +| Pattern | Connection? | `gateway-acl` ACL? | Notification auth? | +|---|---|---|---| +| Connector event | Yes | Yes (gateway MI → connection) | Recommended | +| Recurrence | No | No | Recommended | +| SlidingWindow | No | No | Recommended | + +## Lifecycle + +```bash +# Create / update +az rest --method PUT --url ".../triggerConfigs/{name}?api-version=2026-05-01-preview" --body "@$tmp" + +# Get +az rest --method GET --url ".../triggerConfigs/{name}?api-version=2026-05-01-preview" + +# List +az rest --method GET --url ".../triggerConfigs?api-version=2026-05-01-preview" \ + --query "value[].{name:name, state:properties.state, type:properties.type}" -o table + +# Disable (subscription/timer paused; state -> Disabled) +az rest --method POST --url ".../triggerConfigs/{name}/disable?api-version=2026-05-01-preview" + +# Enable (state -> Enabled) +az rest --method POST --url ".../triggerConfigs/{name}/enable?api-version=2026-05-01-preview" + +# Delete (removes subscription on SaaS side too — for event triggers) +az rest --method DELETE --url ".../triggerConfigs/{name}?api-version=2026-05-01-preview" +``` + +## Run history + +Each invocation produces a `run` resource: + +```bash +az rest --method GET --url ".../triggerConfigs/{name}/runs?api-version=2026-05-01-preview" \ + --query "value[].{name:name, status:properties.status, start:properties.startTime, end:properties.endTime, statusCode:properties.outputs.statusCode, error:properties.error.message}" -o table +``` + +| `status` | Meaning | +|---|---| +| `Succeeded` | Callback returned 2xx | +| `Failed` | Callback returned 4xx/5xx, or the gateway couldn't reach it. See `error.message`. | +| `Skipped` | Trigger fired but a downstream rule (e.g., dedup) suppressed the callback. | +| `Cancelled` | Trigger was disabled while a run was in flight. | + +## Updates + +To change anything (callback URL, auth, parameters, schedule), re-PUT the full +config. The platform diffs against the existing config and: + +- Re-subscribes if `connectionDetails` / `operationName` / `parameters` change +- Updates the schedule if `recurrence` changes +- Updates the callback URL / auth without re-subscribing for everything else diff --git a/Skills/General/azure-connectorgateway/references/trigger-setup.md b/Skills/General/azure-connectorgateway/references/trigger-setup.md new file mode 100644 index 0000000..d7bc80d --- /dev/null +++ b/Skills/General/azure-connectorgateway/references/trigger-setup.md @@ -0,0 +1,238 @@ +# Trigger Setup + +Detailed commands for creating trigger configs on a connector gateway. Triggers +fire on either a connector event (new email, new file, ...) or on a schedule +(Recurrence / SlidingWindow), and POST a notification to your `callbackUrl`. + +## Step 1: Pick the trigger source + +| Source | When fires | Requires connection? | +|---|---|---| +| **Connector event** | When the connector reports an event (e.g., `OnNewEmailV3`) | Yes — plus `gateway-acl` | +| **Recurrence** | Every N `Second`/`Minute`/`Hour`/`Day` | No | +| **SlidingWindow** | Every N units with a `startTime`/`endTime` window state | No | + +## Step 2: Connector-event triggers — discover + collect parameters + +```bash +# Discover trigger operations for the connector +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[?properties.trigger != null].{name:name, summary:properties.summary, trigger:properties.trigger}" -o table +``` + +Present operations to the user. After selection, fetch the full Swagger (see +[direct-api.md](direct-api.md) §1) to find the operation's parameter list. For each +parameter: + +- **Has `x-ms-dynamic-*`** → resolve via [dynamic-values.md](dynamic-values.md) and **STOP** for user selection +- **Static enum** → present choices and **STOP** +- **Free-form with obvious default** (e.g., `folderPath=Inbox`) → use default, inform user +- **Free-form, no default** → ask the user + +> **Polling cadence:** if the operation has neither `x-ms-notification` nor +> `x-ms-notification-content` in its Swagger, it polls (default ~3 min). Inform +> the user; if they want a different cadence, omit `type` and add a `recurrence` +> property on the trigger config: +> `recurrence: { frequency: "Minute", interval: 5 }`. + +## Step 3: PUT the trigger config + +PUT to `.../connectorGateways/{gw}/triggerConfigs/{name}?api-version=2026-05-01-preview`. + +### 3A. Connector-event trigger + +```powershell +$triggerBody = @{ + properties = @{ + description = "Notify webhook on new email" + connectionDetails = @{ + connectorName = "office365" + connectionName = "o365-conn" + } + operationName = "OnNewEmailV3" + parameters = @( + @{ name = "folderPath"; value = "Inbox" } + ) + notificationDetails = @{ + callbackUrl = "https://my-api.contoso.com/email-hook" + httpMethod = "Post" + authentication = @{ + type = "ManagedServiceIdentity" + audience = "api://my-api" + } + } + state = "Enabled" + } +} | ConvertTo-Json -Depth 8 -Compress + +$tmp = New-TemporaryFile; Set-Content $tmp $triggerBody +az rest --method PUT ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/triggerConfigs/{trigger_name}?api-version=2026-05-01-preview" ` + --body "@$tmp" +Remove-Item $tmp +``` + +> **`parameters`** must match the connector operation's input schema (resolved +> against the Swagger). See [direct-api.md](direct-api.md) §2 for parameter resolution. + +### 3B. Recurrence trigger + +```powershell +$triggerBody = @{ + properties = @{ + description = "Ping webhook every 5 minutes" + type = "Recurrence" + recurrence = @{ + frequency = "Minute" + interval = 5 + } + notificationDetails = @{ + callbackUrl = "https://my-api.contoso.com/tick" + httpMethod = "Post" + # Omit `authentication` if the URL is self-authenticating + } + state = "Enabled" + } +} | ConvertTo-Json -Depth 6 -Compress + +$tmp = New-TemporaryFile; Set-Content $tmp $triggerBody +az rest --method PUT ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/triggerConfigs/{trigger_name}?api-version=2026-05-01-preview" ` + --body "@$tmp" +Remove-Item $tmp +``` + +| `frequency` | Allowed values | +|---|---| +| `Second` | interval ≥ 1 | +| `Minute` | interval ≥ 1 | +| `Hour` | interval ≥ 1 | +| `Day` | interval ≥ 1 | + +### 3C. Sliding-window trigger + +Same as Recurrence but with `type = "SlidingWindow"`. Use it when each invocation +needs a `windowStart`/`windowEnd` time range (e.g., "process records added in +the last hour, every hour"). The window state is tracked by the platform. + +```powershell +$triggerBody = @{ + properties = @{ + description = "Hourly sliding window" + type = "SlidingWindow" + recurrence = @{ + frequency = "Hour" + interval = 1 + } + notificationDetails = @{ + callbackUrl = "https://my-api.contoso.com/process-window" + httpMethod = "Post" + } + state = "Enabled" + } +} | ConvertTo-Json -Depth 6 -Compress +# (same PUT as 3B) +``` + +### 3D. Custom callback body + +Add `notificationDetails.body` if you want to override the default payload sent +to your callback: + +```powershell +notificationDetails = @{ + callbackUrl = "..." + httpMethod = "Post" + body = @{ + eventSource = "my-trigger" + extra = "context" + } +} +``` + +If `body` is omitted, the connector-event payload (for event triggers) or an +empty body (for recurrence) is sent. + +### Auth options + +See [notification-authentication.md](notification-authentication.md) for all 6 auth types +(`QueryString`, `Raw`, `Basic`, `ManagedServiceIdentity`, `ActiveDirectoryOAuth`, `ClientCertificate`) +and their exact JSON shapes. + +## Step 4: `gateway-acl` (connector-event triggers only) + +The gateway MI must have an access policy on the connection so the gateway can +subscribe to connector events. Skip this for Recurrence / SlidingWindow. + +```powershell +# Get gateway's principalId and tenantId first (from Step 1 of SKILL.md) +$aclBody = @{ + location = "{location}" + properties = @{ + principal = @{ + type = "ActiveDirectory" + identity = @{ objectId = "{gw_principal_id}"; tenantId = "{gw_tenant_id}" } + } + } +} | ConvertTo-Json -Depth 5 -Compress +$tmp = New-TemporaryFile; Set-Content $tmp $aclBody +az rest --method PUT ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/connections/{conn}/accessPolicies/gateway-acl?api-version=2026-05-01-preview" ` + --body "@$tmp" +Remove-Item $tmp +``` + +This is independent of the trigger PUT — **run them in parallel**. + +## Step 5: Verify + +```bash +az rest --method GET \ + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/triggerConfigs/{trigger}?api-version=2026-05-01-preview" \ + --query "{state:properties.state, type:properties.type, callback:properties.notificationDetails.callbackUrl}" +# state should be: Enabled +``` + +For connector-event triggers, also wait for the subscription to be created on +the SaaS side. Most providers take seconds; some (notably SharePoint and Forms) +can take 1-2 minutes. + +## Manage lifecycle + +```bash +# Disable +az rest --method POST \ + --url ".../connectorGateways/{gw}/triggerConfigs/{name}/disable?api-version=2026-05-01-preview" + +# Enable +az rest --method POST \ + --url ".../connectorGateways/{gw}/triggerConfigs/{name}/enable?api-version=2026-05-01-preview" + +# Delete +az rest --method DELETE \ + --url ".../connectorGateways/{gw}/triggerConfigs/{name}?api-version=2026-05-01-preview" +``` + +## List recent runs (for debugging) + +```bash +az rest --method GET \ + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/triggerConfigs/{name}/runs?api-version=2026-05-01-preview" \ + --query "value[].{name:name, status:properties.status, start:properties.startTime, end:properties.endTime, error:properties.error.message}" -o table +``` + +Each run shows whether the gateway succeeded in POSTing to your `callbackUrl` +and what HTTP status came back. + +## Common mistakes + +| Mistake | Fix | +|---|---| +| Putting `callbackUrl` at `properties.callbackUrl` | Goes under `properties.notificationDetails.callbackUrl` | +| Putting `parameters` inside `connectionDetails` | Goes at `properties.parameters` (sibling of `connectionDetails`) | +| Using `callbackTarget` | That field does not exist. Use `notificationDetails`. | +| Forgetting `gateway-acl` on a connector-event trigger | Subscription fails silently — trigger state may show `Enabled` but never fires. Create the ACL. | +| Inline JSON `--body '...'` in PowerShell | "Unsupported Media Type" — always `@$tmpFile`. See [gotchas.md](gotchas.md). | +| `ManagedServiceIdentity` auth without an audience | Validation rejects it — `audience` is required. | +| `ManagedServiceIdentity` referencing a UAMI not on the gateway | Attach it to the gateway first (see [notification-authentication.md](notification-authentication.md)). | diff --git a/Skills/General/azure-connectorgateway/references/tutorial.md b/Skills/General/azure-connectorgateway/references/tutorial.md new file mode 100644 index 0000000..4d48e84 --- /dev/null +++ b/Skills/General/azure-connectorgateway/references/tutorial.md @@ -0,0 +1,273 @@ +# Tutorial: Two end-to-end walkthroughs + +Two minimal real-world scenarios you can run to verify a gateway, a connection, +and a trigger config all work together. + +- **Tutorial A** (no connection) — recurrence trigger pinging your webhook + every 5 minutes +- **Tutorial B** (connector-event) — email me whenever a new file appears in a + SharePoint folder + +Set these variables once at the top of your terminal session: + +```powershell +$sub = "" +$rg = "tutorial-cg-rg" +$location = "eastus" +$gw = "tutorial-gw" +$preview = "2026-05-01-preview" +``` + +--- + +## One-time setup (both tutorials) + +### 1. Resource group + provider + +```bash +az group create --name $rg --location $location +az provider register --namespace Microsoft.Web +``` + +### 2. Connector gateway with system-assigned MI + +```powershell +$gwBody = @{ + location = $location + identity = @{ type = "SystemAssigned" } + properties = @{} +} | ConvertTo-Json -Depth 4 -Compress + +$tmp = New-TemporaryFile; Set-Content $tmp $gwBody +az rest --method PUT ` + --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$gw?api-version=$preview" ` + --body "@$tmp" +Remove-Item $tmp + +# Wait for provisioningState = Succeeded +az rest --method GET ` + --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$gw?api-version=$preview" ` + --query "{state:properties.provisioningState, mi:identity.principalId}" +``` + +Save the `mi` value as `$gwMi`. Also capture `$gwTenant = (az account show --query tenantId -o tsv)`. + +--- + +## Tutorial A — Recurrence trigger to your webhook + +**Goal:** every 5 minutes, the gateway POSTs to `https://your-webhook.example.com/ping`. + +### A.1 Create the trigger config + +```powershell +$callback = "https://your-webhook.example.com/ping" # replace with your real URL + +$triggerBody = @{ + properties = @{ + description = "Tutorial recurrence trigger" + type = "Recurrence" + recurrence = @{ frequency = "Minute"; interval = 5 } + notificationDetails = @{ + callbackUrl = $callback + httpMethod = "Post" + } + state = "Enabled" + } +} | ConvertTo-Json -Depth 6 -Compress + +$tmp = New-TemporaryFile; Set-Content $tmp $triggerBody +az rest --method PUT ` + --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$gw/triggerConfigs/tut-recur?api-version=$preview" ` + --body "@$tmp" +Remove-Item $tmp +``` + +### A.2 Verify + +```bash +az rest --method GET \ + --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$gw/triggerConfigs/tut-recur?api-version=$preview" \ + --query "{state:properties.state, callback:properties.notificationDetails.callbackUrl}" +``` + +Within 5 minutes, check `triggerConfigs/tut-recur/runs`: + +```bash +az rest --method GET \ + --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$gw/triggerConfigs/tut-recur/runs?api-version=$preview" \ + --query "value[].{status:properties.status, start:properties.startTime, end:properties.endTime}" -o table +``` + +You should see `Succeeded` runs as long as your webhook returns 2xx. + +### A.3 Add MSI auth on the callback (optional) + +If your webhook is an Azure resource that accepts AAD tokens: + +```powershell +$triggerBody = @{ + properties = @{ + description = "Tutorial recurrence trigger (MSI)" + type = "Recurrence" + recurrence = @{ frequency = "Minute"; interval = 5 } + notificationDetails = @{ + callbackUrl = $callback + httpMethod = "Post" + authentication = @{ + type = "ManagedServiceIdentity" + audience = "api://your-aad-app" + } + } + state = "Enabled" + } +} | ConvertTo-Json -Depth 8 -Compress +# Re-PUT with the same URL +``` + +The gateway will mint a token with its system-assigned MI for `audience` and +attach it as `Authorization: Bearer ...`. + +### A.4 Cleanup (Tutorial A only) + +```bash +az rest --method DELETE \ + --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$gw/triggerConfigs/tut-recur?api-version=$preview" +``` + +--- + +## Tutorial B — Connector event trigger from SharePoint + +**Goal:** when a new file appears in `Shared Documents` of a SharePoint site, the +gateway POSTs the file metadata to your webhook. + +### B.1 Create a SharePoint connection + +```powershell +$connBody = @{ + location = $location + properties = @{ connectorName = "sharepointonline" } +} | ConvertTo-Json -Compress + +$tmp = New-TemporaryFile; Set-Content $tmp $connBody +az rest --method PUT ` + --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$gw/connections/sp-conn?api-version=$preview" ` + --body "@$tmp" +Remove-Item $tmp +``` + +### B.2 Complete consent + +Follow [consent.md](consent.md): fetch `properties.createdBy.{objectId,tenantId}`, +PUT a body with `parameters[]` containing `{objectId, tenantId, redirectUrl, parameterName:"token"}`, +then open the returned link in a browser and sign in. + +Confirm `Connected`: + +```bash +az rest --method GET \ + --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$gw/connections/sp-conn?api-version=$preview" \ + --query "properties.statuses[0].status" +``` + +### B.3 Grant the gateway MI access to the connection + +```powershell +$aclBody = @{ + location = $location + properties = @{ + principal = @{ + type = "ActiveDirectory" + identity = @{ objectId = $gwMi; tenantId = $gwTenant } + } + } +} | ConvertTo-Json -Depth 5 -Compress + +$tmp = New-TemporaryFile; Set-Content $tmp $aclBody +az rest --method PUT ` + --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$gw/connections/sp-conn/accessPolicies/gateway-acl?api-version=$preview" ` + --body "@$tmp" +Remove-Item $tmp +``` + +### B.4 Resolve `dataset` and `folder_id` via dynamic values + +Per [dynamic-values.md](dynamic-values.md). The SharePoint operation +`OnNewFile` needs: + +- `dataset` (site URL) — comes from `GetDataSets` +- `folder_id` (library + path) — comes from `GetAllTables` once `dataset` is set + +Use `dynamicInvoke` to list each, **STOP** for the user to pick, and pass the +chosen `value-path` field forward. + +Suppose the user picks: + +- `dataset = "https://contoso.sharepoint.com/sites/team"` +- `folder_id = "%252fShared%2520Documents"` (URL-encoded) + +### B.5 PUT the trigger config + +```powershell +$callback = "https://your-webhook.example.com/sp-newfile" + +$triggerBody = @{ + properties = @{ + description = "New file in Shared Documents" + connectionDetails = @{ connectorName = "sharepointonline"; connectionName = "sp-conn" } + operationName = "OnNewFile" + parameters = @( + @{ name = "dataset"; value = "https://contoso.sharepoint.com/sites/team" } + @{ name = "folder_id"; value = "%252fShared%2520Documents" } + ) + notificationDetails = @{ + callbackUrl = $callback + httpMethod = "Post" + authentication = @{ + type = "QueryString" + name = "code" + value = "" + } + } + state = "Enabled" + } +} | ConvertTo-Json -Depth 8 -Compress + +$tmp = New-TemporaryFile; Set-Content $tmp $triggerBody +az rest --method PUT ` + --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$gw/triggerConfigs/tut-spnewfile?api-version=$preview" ` + --body "@$tmp" +Remove-Item $tmp +``` + +### B.6 Verify + +```bash +# Trigger config state +az rest --method GET --url ".../triggerConfigs/tut-spnewfile?api-version=$preview" \ + --query "{state:properties.state}" + +# Upload a file to the SharePoint folder, then within ~30s: +az rest --method GET --url ".../triggerConfigs/tut-spnewfile/runs?api-version=$preview" \ + --query "value[0:5].{status:properties.status, start:properties.startTime, statusCode:properties.outputs.statusCode}" -o table +``` + +### B.7 Cleanup (Tutorial B) + +```bash +az rest --method DELETE --url ".../triggerConfigs/tut-spnewfile?api-version=$preview" +az rest --method DELETE --url ".../connections/sp-conn/accessPolicies/gateway-acl?api-version=$preview" +az rest --method DELETE --url ".../connections/sp-conn?api-version=$preview" +``` + +--- + +## Final cleanup + +```bash +az rest --method DELETE \ + --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$gw?api-version=$preview" + +az group delete --name $rg --yes --no-wait +``` diff --git a/Skills/General/azure-connectorgateway/version.json b/Skills/General/azure-connectorgateway/version.json new file mode 100644 index 0000000..ec1f093 --- /dev/null +++ b/Skills/General/azure-connectorgateway/version.json @@ -0,0 +1 @@ +{"version": "0.1.0"} From 41c35ce09f76b0062a3fb853ae163dabd5bccae9 Mon Sep 17 00:00:00 2001 From: apranaseth Date: Fri, 29 May 2026 13:40:18 -0700 Subject: [PATCH 02/14] MCP config: explicit dynamic-values/list/tree/schema guidance Expand mcp-server-config.md with a full "Resolving dynamic parameters for MCP config" section that covers all four x-ms-dynamic-* kinds, the cascading-dependency pattern (e.g., channel depends on team), dynamic schema for body shapes, and a rule of thumb for what to bake into userParameters vs leave LLM-supplied. Mirror the same triage in SKILL.md Step 4B. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../General/azure-connectorgateway/SKILL.md | 12 +- .../references/mcp-server-config.md | 117 ++++++++++++++++++ 2 files changed, 125 insertions(+), 4 deletions(-) diff --git a/Skills/General/azure-connectorgateway/SKILL.md b/Skills/General/azure-connectorgateway/SKILL.md index 1e6ba6c..ea409ec 100644 --- a/Skills/General/azure-connectorgateway/SKILL.md +++ b/Skills/General/azure-connectorgateway/SKILL.md @@ -213,10 +213,14 @@ Ask the user: ``` Each MCP "tool" maps to one connector operation under one connection. -2. **For each operation, collect `userParameters`.** These are the **fixed inputs** - baked into the MCP config (e.g., "always send to channel X"). LLM-supplied - inputs are the operation's other parameters. Resolve `x-ms-dynamic-*` exactly - like trigger setup — see [dynamic-values.md](references/dynamic-values.md). **STOP at every dynamic param.** +2. **For each operation, triage every parameter** through dynamic resolution before deciding what to bake. The four extension kinds (`x-ms-dynamic-values`, `-list`, `-tree`, `-schema`) **all apply** here, exactly like trigger setup. For each: + - `x-ms-dynamic-values` / `-list` → `dynamicInvoke` the lookup operation, **STOP** for user pick, store the `value-path` value. + - `x-ms-dynamic-tree` → walk the tree (root → children), **STOP** at each level, store the final opaque token. + - `x-ms-dynamic-schema` / `-properties` → resolve schema after parents are picked (the body shape comes from the connector). Bake the parents so the LLM gets a stable tool shape. + - Cascading params (e.g., channel depends on team): always resolve parents first; pass their `value` (not display name) to child lookups. + - See [mcp-server-config.md](references/mcp-server-config.md) §"Resolving dynamic parameters for MCP config" and [dynamic-values.md](references/dynamic-values.md). + + Decide for each: bake into `userParameters[]` (fixed at config time) or leave for the LLM (free-form fields like subject/body). **Rule of thumb:** if the value space is enumerable connector data (team, channel, site, list, folder, file, db, table), **bake it**; if it's "anything the user might imagine" (subject, message body, email address), leave it for the LLM. **STOP at every dynamic param.** 3. **PUT the MCP server config.** Body shape: ```json diff --git a/Skills/General/azure-connectorgateway/references/mcp-server-config.md b/Skills/General/azure-connectorgateway/references/mcp-server-config.md index 19fafc2..990b31f 100644 --- a/Skills/General/azure-connectorgateway/references/mcp-server-config.md +++ b/Skills/General/azure-connectorgateway/references/mcp-server-config.md @@ -86,6 +86,123 @@ time** rather than supplied by the LLM at call time. Typical examples: > against the connection — exactly like [trigger-setup.md](trigger-setup.md) Step 2. > See [dynamic-values.md](dynamic-values.md). **STOP** at each dynamic param for user selection. +## Resolving dynamic parameters for MCP config + +**Every connector operation parameter goes through this triage before the PUT.** +This is the same logic as trigger setup, but applied to *all* operation +parameters, not just the ones in `notificationDetails`. + +### Step 1: Get the connector's Swagger once + +```bash +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 +``` + +Find the operation by `operationId` and read its `parameters[]`. For each +parameter, identify which extension is present. + +### Step 2: Triage every parameter + +| Parameter has | What to do | Bake into `userParameters[]`? | +|---|---|---| +| `x-ms-dynamic-values` | Resolve via [dynamic-values.md](dynamic-values.md) §`x-ms-dynamic-values`. **STOP** for user pick. | Usually yes — IDs are not LLM-discoverable | +| `x-ms-dynamic-list` | Same as `x-ms-dynamic-values` — resolve, **STOP** for user pick. | Usually yes | +| `x-ms-dynamic-tree` | Walk the tree via [dynamic-values.md](dynamic-values.md) §`x-ms-dynamic-tree`. **STOP** at each level. | Usually yes — final `value` is opaque (e.g., `%252fShared%2520Documents`) | +| `x-ms-dynamic-schema` | The parameter's **body shape** depends on a parent param. See §"Dynamic schema" below. | Bake the parent, then decide for the body | +| `x-ms-dynamic-properties` | Same as `x-ms-dynamic-schema` (older variant). Handle identically. | Same as `x-ms-dynamic-schema` | +| Static enum | Show choices, **STOP** for user pick. | Bake if the user wants it fixed; leave for LLM otherwise | +| Free-form with obvious default | Use default, inform user. | Optional | +| Free-form, no default | Ask the user whether to bake it or let the LLM supply it. | User's choice | + +> **The "STOP for user pick" rule applies even for MCP config.** Never invent a +> team / channel / site / folder / list / mailbox / database ID. + +### Step 3: Handle cascading dependencies + +Many parameters' `x-ms-dynamic-*` lookups **depend on prior parameters**. You +must resolve the parents first and use the chosen `value` as input to the +child lookup. This is identical to the algorithm in +[dynamic-values.md](dynamic-values.md) §"Step 2: Understand value vs display name" +and §"x-ms-dynamic-schema". + +**Worked example: "Post message to Teams channel"** + +The `PostMessageToChannelV3` operation has: + +```text +groupId ← x-ms-dynamic-values via GetAllTeams +channelId ← x-ms-dynamic-values via GetChannelsForGroup(groupId) +message ← LLM-supplied free-form +``` + +To bake `groupId` + `channelId` into `userParameters[]`: + +1. Call `dynamicInvoke` → `GetAllTeams`. **STOP** for the user to pick a team. + Capture the team's `value-path` field (e.g., `value-path = "id"` → `"abc..."`). +2. Call `dynamicInvoke` → `GetChannelsForGroup` with `groupId = "abc..."`. + **STOP** for the user to pick a channel. Capture `id`. +3. Build the MCP config: + ```json + "userParameters": [ + { "name": "groupId", "displayName": "Team", "value": "abc...", "displayValue": "Engineering" }, + { "name": "channelId", "displayName": "Channel", "value": "19:def...@thread.tacv2", "displayValue": "general" } + ] + ``` +4. Leave `message` out of `userParameters[]` so the LLM supplies it per tool call. + +> **Always pass the stored `value` (from `value-path`), not the display name +> (from `value-title`)**, as input to child lookups. The connector rejects +> display names with `NotFound` or empty results. + +### Step 4: Dynamic schema (body shape) + +When an operation's body parameter uses `x-ms-dynamic-schema` (e.g., +"Create item in SharePoint list" — the body fields are the list's columns), +the schema can only be resolved after the parent parameters (`dataset`, +`table`) are known. + +**Recommendation:** if a tool uses `x-ms-dynamic-schema` for its body, **always +bake the dependent parents** (`dataset`, `table`, ...) as `userParameters[]` +so the LLM gets a stable, concrete tool shape. Without that, the LLM cannot +know what fields it's allowed to send. + +Resolve the schema once at config time, using +[dynamic-values.md](dynamic-values.md) §"x-ms-dynamic-schema" algorithm: + +```bash +# Once dataset + table are chosen, fetch the schema: +az rest --method POST \ + --url ".../connectorGateways/{gw}/connections/{conn}/dynamicInvoke?api-version=2026-05-01-preview" \ + --body "@$schemaBody" +# (request body uses GetTable's operationId per the operation's +# x-ms-dynamic-schema.operationId, with the resolved dataset + table) +``` + +The returned JSON schema is what the LLM will see as the tool's input shape +for the body — confirm with the user before saving the MCP config. + +### Step 5: LLM-facing input schema + +Parameters **not** in `userParameters[]` become LLM-supplied inputs. The +gateway derives the MCP tool's input schema from those remaining parameters +in the operation's Swagger. So: + +- Static parameters → straightforward type in the MCP tool input schema +- `x-ms-dynamic-values` / `-list` left LLM-supplied → the LLM must guess + valid IDs; **this rarely works**. Prefer baking these as `userParameters[]`. +- `x-ms-dynamic-tree` left LLM-supplied → almost never works (opaque tokens). + Bake it. +- `x-ms-dynamic-schema` body left LLM-supplied with parents un-baked → the + body schema is unknown until tool-call time; LLM tool input becomes generic + `object`. **Bake the parents.** + +**Rule of thumb:** if a parameter's value space is "anything the user might +imagine" (a subject line, a message body, an email address), leave it for the +LLM. If it's "one of an enumerable, connector-supplied set" (team, channel, +site, list, folder, file, database, table), **bake it via dynamic resolution**. + ## PUT the config ```powershell From cd087334048b50f7778c743bc3ee4f651701aba1 Mon Sep 17 00:00:00 2001 From: apranaseth Date: Fri, 29 May 2026 19:15:25 -0700 Subject: [PATCH 03/14] Fix: MSI audience is OPTIONAL, ask the user User correction: 'audience is not a required field' on ManagedServiceIdentity callback auth. Updated the skill to treat it as optional and to always ASK the user whether to set one: - notification-authentication.md sec 4: audience-less example first, audience-with-value second; validation rules say 'optional, omit for non-AAD callbacks'. - SKILL.md auth-type table + STOP-and-ask list updated. - trigger-setup.md example shows audience commented out + gotcha table updated. - gotchas.md row replaced (was 'audience required', now 'fabricated audience -> always ask'). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../General/azure-connectorgateway/SKILL.md | 5 +-- .../references/gotchas.md | 2 +- .../references/notification-authentication.md | 35 ++++++++++++++++--- .../references/trigger-setup.md | 6 ++-- 4 files changed, 38 insertions(+), 10 deletions(-) diff --git a/Skills/General/azure-connectorgateway/SKILL.md b/Skills/General/azure-connectorgateway/SKILL.md index ea409ec..748f37e 100644 --- a/Skills/General/azure-connectorgateway/SKILL.md +++ b/Skills/General/azure-connectorgateway/SKILL.md @@ -48,6 +48,7 @@ ngrok, anywhere) and you choose how the gateway authenticates to it. | **No generated notebooks/scripts** | Walk the user through interactively. Do NOT generate a standalone notebook or script. | | **No guessing dynamic values** | `x-ms-dynamic-*` → call the API, present results, STOP. Never assume a team/channel/folder/site/list. | | **No guessing the callback URL** | The callback URL is **always** user-provided. Ask for it explicitly. Do NOT invent one. | +| **No guessing MSI audience** | For `ManagedServiceIdentity` callback auth, the `audience` field is **optional**. **Always ask the user** whether to set one — only set it if the callback is AAD-protected and they provide a real AAD-protected resource URI (e.g., an AAD app they own, `https://management.azure.com/`, etc.). The callback URL itself is NOT a valid audience. For non-AAD callbacks (e.g., webhook receivers), omit `audience`. Never fabricate. | | **Execute, don't ask** | Once you have inputs, run the commands. Don't ask "Can I run this?" | | **`az rest` only** | No `az connectorgateway` or other extensions exist. Use `az rest` for ARM and `az rest --resource` for data-plane. | | **Always `@$tmpFile`** | For `az rest --body` in PowerShell — inline JSON breaks quoting. See [gotchas.md](references/gotchas.md). | @@ -56,7 +57,7 @@ ngrok, anywhere) and you choose how the gateway authenticates to it. | **MCP user params** | Each `userParameters[]` entry is the fixed value for a connector-operation parameter, resolved via `dynamic-values` against the connection at config time. See [mcp-server-config.md](references/mcp-server-config.md). | | **Parallel execution** | Run independent ops (connections, ACLs, dynamic-value lookups, MCP operations) as parallel tool calls. | -**When to STOP and ask the user:** subscription/resource group, gateway name, connection name, any parameter with dynamic values (teams/channels/folders/sites/lists), callback URL, callback authentication type, OAuth consent completion. +**When to STOP and ask the user:** subscription/resource group, gateway name, connection name, any parameter with dynamic values (teams/channels/folders/sites/lists), callback URL, callback authentication type, **whether to set an MSI `audience` (if MSI auth chosen)**, OAuth consent completion. **When to EXECUTE immediately:** gateway/connection/trigger/MCP-config/access-policy CRUD, role assignments, dynamic-value lookups. @@ -184,7 +185,7 @@ Ask the user: | `QueryString` | Add a `?key=value` automatically (e.g., Function App key) | | `Raw` | Send a literal `Authorization: ` header | | `Basic` | HTTP Basic with `username`/`password` | - | `ManagedServiceIdentity` | Gateway acquires a token for `audience` using its own MI (system- or user-assigned) | + | `ManagedServiceIdentity` | Gateway calls using its own MI (system- or user-assigned). `audience` is **optional** — only set it if the callback is AAD-protected. **Always ask the user whether to set `audience`** — never fabricate one (the callback URL is NOT a valid audience). See [notification-authentication.md](references/notification-authentication.md). | | `ActiveDirectoryOAuth` | Gateway authenticates as an Entra app (tenant/clientId/secret/audience) | | `ClientCertificate` | Mutual TLS with a `pfx`/`password` | diff --git a/Skills/General/azure-connectorgateway/references/gotchas.md b/Skills/General/azure-connectorgateway/references/gotchas.md index 756cdbe..619d923 100644 --- a/Skills/General/azure-connectorgateway/references/gotchas.md +++ b/Skills/General/azure-connectorgateway/references/gotchas.md @@ -7,7 +7,7 @@ Common issues for the generic connector-gateway skill and their fixes. | **Trigger not firing** (connector event) | Make sure `gateway-acl` exists on the connection (gateway MI → connection). Without it the subscription silently fails. See [trigger-setup.md](trigger-setup.md) Step 4. | | **Trigger state is `Enabled` but no runs** | Check `triggerConfigs/{name}/runs` for errors. Most commonly: the gateway couldn't reach `callbackUrl` (4xx/5xx from your endpoint) or authentication mismatch. | | **Trigger run shows `Unauthorized` from callback** | Your callback URL's auth doesn't match `notificationDetails.authentication`. Re-check the type/audience/secret. See [notification-authentication.md](notification-authentication.md). | -| **`ManagedServiceIdentity` callback fails with "audience required"** | `audience` is mandatory on MSI auth. Use the API's resource/audience URI. | +| **`ManagedServiceIdentity` callback fabricated audience** | `audience` is **optional** on MSI auth — only set it when the callback is AAD-protected and the user gives you the resource URI. **Always ask the user** whether to set it; never default to the callback URL. See [notification-authentication.md](notification-authentication.md). | | **`ManagedServiceIdentity` callback fails with "identity not configured"** | The requested MI isn't attached to the gateway. Either omit `identity` (uses SystemAssigned) or attach the UAMI to the gateway. See [notification-authentication.md](notification-authentication.md). | | **Connection stuck in `Error` / `Unauthenticated`** | This is the normal pre-consent state. Run [consent.md](consent.md) and have the user complete the browser flow. If still stuck after consent, regenerate the consent link — do NOT retry with different body formats. | | **Consent redirect shows error** | Body MUST be `{"parameters":[{"objectId":"...","tenantId":"...","redirectUrl":"https://microsoft.com","parameterName":"token"}]}`. Get `objectId`/`tenantId` from the connection's `properties.createdBy`. Always open with `Start-Process`. | diff --git a/Skills/General/azure-connectorgateway/references/notification-authentication.md b/Skills/General/azure-connectorgateway/references/notification-authentication.md index 76a47ba..c96a4b3 100644 --- a/Skills/General/azure-connectorgateway/references/notification-authentication.md +++ b/Skills/General/azure-connectorgateway/references/notification-authentication.md @@ -47,15 +47,40 @@ You provide the full ` `. ### 4) `ManagedServiceIdentity` — gateway uses its own MI -The gateway acquires an Entra token for `audience` using its managed identity -and sends it as `Authorization: Bearer {token}`. +The gateway uses its managed identity to call the callback. If `audience` is +provided, the gateway acquires an Entra token for that audience and sends it +as `Authorization: Bearer {token}`. If `audience` is omitted, the gateway +still calls using its MI (no AAD-protected token is minted for a specific +resource). + +> ⚠️ **`audience` is OPTIONAL — ASK THE USER whether to set one. Never fabricate it.** +> Only set `audience` if the callback endpoint is AAD-protected and the user +> knows the resource URI of the AAD app guarding it (e.g., +> `api://my-app`, `https://management.azure.com/`, `https://graph.microsoft.com/`, +> or the `Application ID URI` of a custom AAD app). The callback URL itself +> is **NOT** a valid audience — do not default to it. If the callback isn't +> AAD-protected (e.g., a generic webhook like Pipedream), omit `audience` and +> proceed with just `type: ManagedServiceIdentity`. +> +> **STOP and ask the user** — offer: +> - Skip the audience (recommended for non-AAD endpoints) +> - Provide the audience of an AAD app they own +> - Set up a new AAD app registration first (then come back with its Application ID URI) + +**System-assigned, no audience** (simplest — gateway must have `identity.type = SystemAssigned`): -**System-assigned** (gateway must have `identity.type = SystemAssigned`): +```json +"authentication": { + "type": "ManagedServiceIdentity" +} +``` + +**System-assigned with audience** (for AAD-protected callbacks): ```json "authentication": { "type": "ManagedServiceIdentity", - "audience": "https://your-api.example.com/" + "audience": "api://my-app" } ``` @@ -70,7 +95,7 @@ and sends it as `Authorization: Bearer {token}`. ``` > Validation rules (from BPM `AIGatewayApiTests_NotificationAuthValidation_*`): -> - `audience` is required (non-empty) +> - `audience` is optional — omit it for non-AAD callbacks > - If `identity` is omitted → gateway must have a SystemAssigned identity > - If `identity` is set → it must be a valid `/subscriptions/.../userAssignedIdentities/{name}` resource ID AND that identity must already be attached to the gateway diff --git a/Skills/General/azure-connectorgateway/references/trigger-setup.md b/Skills/General/azure-connectorgateway/references/trigger-setup.md index d7bc80d..c2ae847 100644 --- a/Skills/General/azure-connectorgateway/references/trigger-setup.md +++ b/Skills/General/azure-connectorgateway/references/trigger-setup.md @@ -59,7 +59,9 @@ $triggerBody = @{ httpMethod = "Post" authentication = @{ type = "ManagedServiceIdentity" - audience = "api://my-api" + # audience is OPTIONAL — only set if callback is AAD-protected. + # ALWAYS ask the user; never default to the callback URL. + # audience = "api://my-api" } } state = "Enabled" @@ -234,5 +236,5 @@ and what HTTP status came back. | Using `callbackTarget` | That field does not exist. Use `notificationDetails`. | | Forgetting `gateway-acl` on a connector-event trigger | Subscription fails silently — trigger state may show `Enabled` but never fires. Create the ACL. | | Inline JSON `--body '...'` in PowerShell | "Unsupported Media Type" — always `@$tmpFile`. See [gotchas.md](gotchas.md). | -| `ManagedServiceIdentity` auth without an audience | Validation rejects it — `audience` is required. | +| `ManagedServiceIdentity` with a fabricated audience | Token is minted for a meaningless audience; if the callback validates AAD tokens it returns `401 invalid audience`, otherwise MSI auth is providing no value. `audience` is **optional** — **always ask the user** whether to set one and what value to use; never default to the callback URL. See [notification-authentication.md](notification-authentication.md). | | `ManagedServiceIdentity` referencing a UAMI not on the gateway | Attach it to the gateway first (see [notification-authentication.md](notification-authentication.md)). | From c0626e877f1780afbfae7a74f59ecb68ced4e0da Mon Sep 17 00:00:00 2001 From: apranaseth Date: Fri, 29 May 2026 19:24:01 -0700 Subject: [PATCH 04/14] MSI auth: default audience to https://management.azure.com/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the user doesn't provide an MSI audience, default to https://management.azure.com/ (a real AAD-protected resource — ARM). Never use the callback URL as the audience. Updated: - notification-authentication.md sec 4: workflow (ask -> use / default to ARM), examples lead with default-audience pattern, validation rule reverted to 'audience required, default if not given'. - SKILL.md auth-type table, rules table, STOP-and-ask list. - trigger-setup.md example shows default audience + comment, gotcha table updated. - gotchas.md row updated to reflect default-audience behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../General/azure-connectorgateway/SKILL.md | 6 ++-- .../references/gotchas.md | 2 +- .../references/notification-authentication.md | 33 ++++++++++--------- .../references/trigger-setup.md | 9 ++--- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/Skills/General/azure-connectorgateway/SKILL.md b/Skills/General/azure-connectorgateway/SKILL.md index 748f37e..b2180b3 100644 --- a/Skills/General/azure-connectorgateway/SKILL.md +++ b/Skills/General/azure-connectorgateway/SKILL.md @@ -48,7 +48,7 @@ ngrok, anywhere) and you choose how the gateway authenticates to it. | **No generated notebooks/scripts** | Walk the user through interactively. Do NOT generate a standalone notebook or script. | | **No guessing dynamic values** | `x-ms-dynamic-*` → call the API, present results, STOP. Never assume a team/channel/folder/site/list. | | **No guessing the callback URL** | The callback URL is **always** user-provided. Ask for it explicitly. Do NOT invent one. | -| **No guessing MSI audience** | For `ManagedServiceIdentity` callback auth, the `audience` field is **optional**. **Always ask the user** whether to set one — only set it if the callback is AAD-protected and they provide a real AAD-protected resource URI (e.g., an AAD app they own, `https://management.azure.com/`, etc.). The callback URL itself is NOT a valid audience. For non-AAD callbacks (e.g., webhook receivers), omit `audience`. Never fabricate. | +| **MSI audience: ask, then default** | For `ManagedServiceIdentity` callback auth, **ask the user for `audience`** (the AAD-protected resource the gateway should acquire a token for, e.g. an AAD app they own, `https://graph.microsoft.com/`, etc.). If the user doesn't provide one, default to `https://management.azure.com/`. **Never** use the callback URL as the audience. | | **Execute, don't ask** | Once you have inputs, run the commands. Don't ask "Can I run this?" | | **`az rest` only** | No `az connectorgateway` or other extensions exist. Use `az rest` for ARM and `az rest --resource` for data-plane. | | **Always `@$tmpFile`** | For `az rest --body` in PowerShell — inline JSON breaks quoting. See [gotchas.md](references/gotchas.md). | @@ -57,7 +57,7 @@ ngrok, anywhere) and you choose how the gateway authenticates to it. | **MCP user params** | Each `userParameters[]` entry is the fixed value for a connector-operation parameter, resolved via `dynamic-values` against the connection at config time. See [mcp-server-config.md](references/mcp-server-config.md). | | **Parallel execution** | Run independent ops (connections, ACLs, dynamic-value lookups, MCP operations) as parallel tool calls. | -**When to STOP and ask the user:** subscription/resource group, gateway name, connection name, any parameter with dynamic values (teams/channels/folders/sites/lists), callback URL, callback authentication type, **whether to set an MSI `audience` (if MSI auth chosen)**, OAuth consent completion. +**When to STOP and ask the user:** subscription/resource group, gateway name, connection name, any parameter with dynamic values (teams/channels/folders/sites/lists), callback URL, callback authentication type, **MSI `audience` (if MSI auth chosen — default to `https://management.azure.com/` if user doesn't provide one)**, OAuth consent completion. **When to EXECUTE immediately:** gateway/connection/trigger/MCP-config/access-policy CRUD, role assignments, dynamic-value lookups. @@ -185,7 +185,7 @@ Ask the user: | `QueryString` | Add a `?key=value` automatically (e.g., Function App key) | | `Raw` | Send a literal `Authorization: ` header | | `Basic` | HTTP Basic with `username`/`password` | - | `ManagedServiceIdentity` | Gateway calls using its own MI (system- or user-assigned). `audience` is **optional** — only set it if the callback is AAD-protected. **Always ask the user whether to set `audience`** — never fabricate one (the callback URL is NOT a valid audience). See [notification-authentication.md](references/notification-authentication.md). | + | `ManagedServiceIdentity` | Gateway acquires a token for `audience` using its own MI (system- or user-assigned). **Ask the user for `audience`** — if they don't provide one, default to `https://management.azure.com/`. Never use the callback URL as the audience. See [notification-authentication.md](references/notification-authentication.md). | | `ActiveDirectoryOAuth` | Gateway authenticates as an Entra app (tenant/clientId/secret/audience) | | `ClientCertificate` | Mutual TLS with a `pfx`/`password` | diff --git a/Skills/General/azure-connectorgateway/references/gotchas.md b/Skills/General/azure-connectorgateway/references/gotchas.md index 619d923..c126007 100644 --- a/Skills/General/azure-connectorgateway/references/gotchas.md +++ b/Skills/General/azure-connectorgateway/references/gotchas.md @@ -7,7 +7,7 @@ Common issues for the generic connector-gateway skill and their fixes. | **Trigger not firing** (connector event) | Make sure `gateway-acl` exists on the connection (gateway MI → connection). Without it the subscription silently fails. See [trigger-setup.md](trigger-setup.md) Step 4. | | **Trigger state is `Enabled` but no runs** | Check `triggerConfigs/{name}/runs` for errors. Most commonly: the gateway couldn't reach `callbackUrl` (4xx/5xx from your endpoint) or authentication mismatch. | | **Trigger run shows `Unauthorized` from callback** | Your callback URL's auth doesn't match `notificationDetails.authentication`. Re-check the type/audience/secret. See [notification-authentication.md](notification-authentication.md). | -| **`ManagedServiceIdentity` callback fabricated audience** | `audience` is **optional** on MSI auth — only set it when the callback is AAD-protected and the user gives you the resource URI. **Always ask the user** whether to set it; never default to the callback URL. See [notification-authentication.md](notification-authentication.md). | +| **`ManagedServiceIdentity` callback with no audience supplied by user** | `audience` is required. **Ask the user**; if they don't provide one, default to `https://management.azure.com/` (real AAD-protected resource). **Never** use the callback URL as the audience. See [notification-authentication.md](notification-authentication.md). | | **`ManagedServiceIdentity` callback fails with "identity not configured"** | The requested MI isn't attached to the gateway. Either omit `identity` (uses SystemAssigned) or attach the UAMI to the gateway. See [notification-authentication.md](notification-authentication.md). | | **Connection stuck in `Error` / `Unauthenticated`** | This is the normal pre-consent state. Run [consent.md](consent.md) and have the user complete the browser flow. If still stuck after consent, regenerate the consent link — do NOT retry with different body formats. | | **Consent redirect shows error** | Body MUST be `{"parameters":[{"objectId":"...","tenantId":"...","redirectUrl":"https://microsoft.com","parameterName":"token"}]}`. Get `objectId`/`tenantId` from the connection's `properties.createdBy`. Always open with `Start-Process`. | diff --git a/Skills/General/azure-connectorgateway/references/notification-authentication.md b/Skills/General/azure-connectorgateway/references/notification-authentication.md index c96a4b3..0fc7fea 100644 --- a/Skills/General/azure-connectorgateway/references/notification-authentication.md +++ b/Skills/General/azure-connectorgateway/references/notification-authentication.md @@ -53,29 +53,30 @@ as `Authorization: Bearer {token}`. If `audience` is omitted, the gateway still calls using its MI (no AAD-protected token is minted for a specific resource). -> ⚠️ **`audience` is OPTIONAL — ASK THE USER whether to set one. Never fabricate it.** -> Only set `audience` if the callback endpoint is AAD-protected and the user -> knows the resource URI of the AAD app guarding it (e.g., -> `api://my-app`, `https://management.azure.com/`, `https://graph.microsoft.com/`, -> or the `Application ID URI` of a custom AAD app). The callback URL itself -> is **NOT** a valid audience — do not default to it. If the callback isn't -> AAD-protected (e.g., a generic webhook like Pipedream), omit `audience` and -> proceed with just `type: ManagedServiceIdentity`. +> ⚠️ **ASK THE USER for `audience`. If they don't provide one, default to `https://management.azure.com/`.** +> The audience is the AAD-protected resource the gateway acquires a token for. +> Set it explicitly if the callback is AAD-protected and the user knows the +> resource URI of the AAD app guarding it (e.g., `api://my-app`, +> `https://graph.microsoft.com/`, `https://vault.azure.net/`, or a custom AAD +> app's `Application ID URI`). The callback URL itself is **NOT** a valid +> audience — do not default to it. > -> **STOP and ask the user** — offer: -> - Skip the audience (recommended for non-AAD endpoints) -> - Provide the audience of an AAD app they own -> - Set up a new AAD app registration first (then come back with its Application ID URI) +> **Workflow:** +> 1. Ask the user: "What AAD resource should the gateway acquire a token for? (e.g., the App ID URI of an AAD-registered API you control, `https://graph.microsoft.com/`, etc.) — leave blank to default to `https://management.azure.com/`." +> 2. If the user provides an audience, use it. +> 3. If the user declines, skips, or doesn't know → use `https://management.azure.com/` as the default. This is a real AAD-protected resource (ARM), so the gateway can mint a valid token for it. The callback may not validate the token, but at least nothing is fabricated. +> 4. If the callback isn't AAD-protected (e.g., a generic webhook), tell the user that MSI auth is providing no real security benefit — they may want `QueryString` or no auth instead. -**System-assigned, no audience** (simplest — gateway must have `identity.type = SystemAssigned`): +**System-assigned with default audience** (simplest — gateway must have `identity.type = SystemAssigned`, user didn't specify): ```json "authentication": { - "type": "ManagedServiceIdentity" + "type": "ManagedServiceIdentity", + "audience": "https://management.azure.com/" } ``` -**System-assigned with audience** (for AAD-protected callbacks): +**System-assigned with user-supplied audience** (for AAD-protected callbacks): ```json "authentication": { @@ -95,7 +96,7 @@ resource). ``` > Validation rules (from BPM `AIGatewayApiTests_NotificationAuthValidation_*`): -> - `audience` is optional — omit it for non-AAD callbacks +> - `audience` is required (non-empty) — if the user doesn't provide one, default to `https://management.azure.com/` > - If `identity` is omitted → gateway must have a SystemAssigned identity > - If `identity` is set → it must be a valid `/subscriptions/.../userAssignedIdentities/{name}` resource ID AND that identity must already be attached to the gateway diff --git a/Skills/General/azure-connectorgateway/references/trigger-setup.md b/Skills/General/azure-connectorgateway/references/trigger-setup.md index c2ae847..083570a 100644 --- a/Skills/General/azure-connectorgateway/references/trigger-setup.md +++ b/Skills/General/azure-connectorgateway/references/trigger-setup.md @@ -59,9 +59,9 @@ $triggerBody = @{ httpMethod = "Post" authentication = @{ type = "ManagedServiceIdentity" - # audience is OPTIONAL — only set if callback is AAD-protected. - # ALWAYS ask the user; never default to the callback URL. - # audience = "api://my-api" + # Ask the user for audience. Default to https://management.azure.com/ + # if they don't provide one. NEVER default to the callback URL. + audience = "https://management.azure.com/" } } state = "Enabled" @@ -236,5 +236,6 @@ and what HTTP status came back. | Using `callbackTarget` | That field does not exist. Use `notificationDetails`. | | Forgetting `gateway-acl` on a connector-event trigger | Subscription fails silently — trigger state may show `Enabled` but never fires. Create the ACL. | | Inline JSON `--body '...'` in PowerShell | "Unsupported Media Type" — always `@$tmpFile`. See [gotchas.md](gotchas.md). | -| `ManagedServiceIdentity` with a fabricated audience | Token is minted for a meaningless audience; if the callback validates AAD tokens it returns `401 invalid audience`, otherwise MSI auth is providing no value. `audience` is **optional** — **always ask the user** whether to set one and what value to use; never default to the callback URL. See [notification-authentication.md](notification-authentication.md). | +| `ManagedServiceIdentity` auth without an audience | `audience` is required (non-empty). Ask the user for it; if they don't provide one, default to `https://management.azure.com/`. | +| `ManagedServiceIdentity` with callback URL as audience | The token will be meaningless (and rejected if the callback validates AAD tokens). Use a real AAD-protected resource URI — when in doubt, default to `https://management.azure.com/`. See [notification-authentication.md](notification-authentication.md). | | `ManagedServiceIdentity` referencing a UAMI not on the gateway | Attach it to the gateway first (see [notification-authentication.md](notification-authentication.md)). | From a8c6be615efa20d5505198b242718ba8c99c8cb4 Mon Sep 17 00:00:00 2001 From: apranaseth Date: Fri, 29 May 2026 19:35:48 -0700 Subject: [PATCH 05/14] Fix MCP access-policy schema docs (User/Group + objectId path) MCP server config access policies require BOTH a principal.type of 'ActiveDirectory' AND a sibling properties.principalType of 'User' or 'Group' (the shared ActiveDirectory enum is insufficient here). The access-policy resource name in the URL must equal the caller's objectId (case-insensitive). MIs/service principals are not supported as MCP principals - only Entra users and groups. Discovered while creating servicebus-send-aigateway MCP config on apseth-msi gateway. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../references/gotchas.md | 2 +- .../references/mcp-server-config.md | 26 +++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/Skills/General/azure-connectorgateway/references/gotchas.md b/Skills/General/azure-connectorgateway/references/gotchas.md index c126007..9093b57 100644 --- a/Skills/General/azure-connectorgateway/references/gotchas.md +++ b/Skills/General/azure-connectorgateway/references/gotchas.md @@ -18,7 +18,7 @@ Common issues for the generic connector-gateway skill and their fixes. | **`az rest --body` "Unsupported Media Type"** | Inline JSON strings get mangled by PowerShell quoting. Always use `@$tmpFile`: write the body to a temp file and pass `--body "@$tmpFile"`. | | **Swagger paths include `/{connectionId}/...`** | Strip the prefix when calling `dynamicInvoke` — the connection context is already set by the endpoint. | | **`x-ms-dynamic-*` resolution returns empty** | The display value you used for the prior parameter wasn't the actual value. Always pass the **stored value** (from `value-path`), not the display name (from `value-title`). See [dynamic-values.md](dynamic-values.md). | -| **MCP tool calls 403 from MCP client** | The caller's objectId isn't in the MCP server config's access policies. Add an access policy on `mcpServerConfigs/{name}/accessPolicies/{policy}`. See [mcp-server-config.md](mcp-server-config.md). | +| **MCP tool calls 403 from MCP client** | The caller's objectId isn't in the MCP server config's access policies. Add an access policy on `mcpServerConfigs/{name}/accessPolicies/{objectId}` — the path segment **must be the caller's objectId** and the body needs BOTH `principal.type="ActiveDirectory"` AND `properties.principalType="User"` (or `"Group"`). MIs/service principals are not supported here. See [mcp-server-config.md](mcp-server-config.md). | | **MCP tool calls 401** | Underlying connection isn't consented or the consent expired. Re-run [consent.md](consent.md). | | **`resourceAuth` rejected on `ManagedMcpServer`** | `resourceAuth` is only valid on `HostedMcpServer`. Remove it. | | **MCP `userParameters[].value` is the display name** | The connector rejects it because it expects the underlying ID/key. Re-resolve via `dynamicInvoke` and store the `value-path` field. | diff --git a/Skills/General/azure-connectorgateway/references/mcp-server-config.md b/Skills/General/azure-connectorgateway/references/mcp-server-config.md index 990b31f..010d654 100644 --- a/Skills/General/azure-connectorgateway/references/mcp-server-config.md +++ b/Skills/General/azure-connectorgateway/references/mcp-server-config.md @@ -265,7 +265,17 @@ The `name` value from this list is exactly what goes into `operations[].name`. ## Access policies on the MCP server config The MCP endpoint accepts callers based on access policies on the config itself. -Grant access to whoever (user, MI, app) needs to call the MCP tools: +Grant access to whoever (user, group) needs to call the MCP tools. + +**Important — MCP access-policy schema differs from connection access policies:** + +1. The body needs BOTH a `principal.type` (must be `"ActiveDirectory"`) AND a + sibling `properties.principalType` (must be `"User"` or `"Group"` — + `"ActiveDirectory"` is NOT accepted here, and there is no MI/ServicePrincipal + option). Sending only one of the two fields will fail validation. +2. The access-policy **name in the URL must equal the principal's `objectId`** + (case-insensitive). The server-side validator rejects any other name. +3. `objectId` and `tenantId` must be well-formed GUIDs. ```powershell $aclBody = @{ @@ -275,15 +285,27 @@ $aclBody = @{ type = "ActiveDirectory" identity = @{ objectId = "{caller_object_id}"; tenantId = "{tenant_id}" } } + principalType = "User" # or "Group" } } | ConvertTo-Json -Depth 5 -Compress $tmp = New-TemporaryFile; Set-Content $tmp $aclBody +# IMPORTANT: the path segment after /accessPolicies/ must equal {caller_object_id}. az rest --method PUT ` - --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/mcpServerConfigs/{mcp_name}/accessPolicies/{policy_name}?api-version=2026-05-01-preview" ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/mcpServerConfigs/{mcp_name}/accessPolicies/{caller_object_id}?api-version=2026-05-01-preview" ` --body "@$tmp" Remove-Item $tmp ``` +For the signed-in user, get `{caller_object_id}` with +`az ad signed-in-user show --query id -o tsv`. For another user, +`az ad user show --id --query id -o tsv`. For a group, +`az ad group show --group --query id -o tsv`. + +> ⚠️ Managed identities and service principals are NOT currently supported as +> MCP access-policy principals — only Entra users and groups. If a non-user +> caller needs access (e.g., another Azure resource's MI), use a group and add +> that principal to the group. + List existing policies: ```bash From 804018635954ef36109c8b007ad4c83664f56d17 Mon Sep 17 00:00:00 2001 From: apranaseth Date: Fri, 29 May 2026 19:59:07 -0700 Subject: [PATCH 06/14] Document agentParameters and per-parameter triage for MCP server configs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The MCP runtime validates that every required Swagger parameter — including required body sub-properties — is declared in either userParameters (fixed value) or agentParameters (caller-supplied JSON Schema). Skipping this triage causes runtime failures like: The API operation 'SendEmailV2' is missing required property 'emailMessage/To'. Changes: - mcp-server-config.md: rewrote the parameter section to cover userParameters vs agentParameters, body decomposition (one agentParameter named after the body root with a nested object schema), and a STOP-and-ask triage workflow per parameter. Added the ConvertTo-Json depth gotcha and updated the PUT example to show both arrays. - SKILL.md: added the per-parameter triage rule + STOP-and-ask entry; updated Step 4B to enumerate body sub-properties and triage each one; updated the PUT example to include agentParameters. - gotchas.md: added rows for the "missing required property" runtime error and the ConvertTo-Json depth truncation symptom. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../General/azure-connectorgateway/SKILL.md | 47 +++- .../references/gotchas.md | 2 + .../references/mcp-server-config.md | 240 +++++++++++++----- 3 files changed, 221 insertions(+), 68 deletions(-) diff --git a/Skills/General/azure-connectorgateway/SKILL.md b/Skills/General/azure-connectorgateway/SKILL.md index b2180b3..48b1280 100644 --- a/Skills/General/azure-connectorgateway/SKILL.md +++ b/Skills/General/azure-connectorgateway/SKILL.md @@ -54,10 +54,11 @@ ngrok, anywhere) and you choose how the gateway authenticates to it. | **Always `@$tmpFile`** | For `az rest --body` in PowerShell — inline JSON breaks quoting. See [gotchas.md](references/gotchas.md). | | **Trigger body schema** | Properties root contains: `type` OR `connectionDetails`+`operationName`+`parameters`, plus `notificationDetails` (`callbackUrl`+`authentication`+`body?`). See [trigger-setup.md](references/trigger-setup.md). | | **Trigger needs `gateway-acl`** | For connector-event triggers, the gateway MI must have an access policy on the connection. See [trigger-setup.md](references/trigger-setup.md) Step 4. | -| **MCP user params** | Each `userParameters[]` entry is the fixed value for a connector-operation parameter, resolved via `dynamic-values` against the connection at config time. See [mcp-server-config.md](references/mcp-server-config.md). | +| **MCP user params** | Each `userParameters[]` entry is a **fixed value** for a connector-operation parameter (resolved via `dynamic-values` against the connection at config time). Each `agentParameters[]` entry declares a JSON-Schema input the **caller (LLM) supplies** at tool-call time. **Every required parameter (including required body sub-properties) MUST appear in one of the two arrays** — the runtime rejects the call with `missing required property ''` otherwise. See [mcp-server-config.md](references/mcp-server-config.md). | +| **MCP per-parameter triage** | **STOP and ask the user, for each operation parameter (path/query/body field, including each required body sub-property), whether it should be a fixed `userParameter`, a caller-supplied `agentParameter`, or skipped (optional only).** Do this **before** the `mcpServerConfigs` PUT, even when no `x-ms-dynamic-*` markers are present. Decompose the body's object schema and triage each leaf. See [mcp-server-config.md](references/mcp-server-config.md) §"Per-parameter triage workflow". | | **Parallel execution** | Run independent ops (connections, ACLs, dynamic-value lookups, MCP operations) as parallel tool calls. | -**When to STOP and ask the user:** subscription/resource group, gateway name, connection name, any parameter with dynamic values (teams/channels/folders/sites/lists), callback URL, callback authentication type, **MSI `audience` (if MSI auth chosen — default to `https://management.azure.com/` if user doesn't provide one)**, OAuth consent completion. +**When to STOP and ask the user:** subscription/resource group, gateway name, connection name, any parameter with dynamic values (teams/channels/folders/sites/lists), callback URL, callback authentication type, **MSI `audience` (if MSI auth chosen — default to `https://management.azure.com/` if user doesn't provide one)**, OAuth consent completion, **MCP per-parameter triage (`userParameter` vs `agentParameter` vs skip — for every path/query/body field, including each body sub-property)**. **When to EXECUTE immediately:** gateway/connection/trigger/MCP-config/access-policy CRUD, role assignments, dynamic-value lookups. @@ -214,14 +215,36 @@ Ask the user: ``` Each MCP "tool" maps to one connector operation under one connection. -2. **For each operation, triage every parameter** through dynamic resolution before deciding what to bake. The four extension kinds (`x-ms-dynamic-values`, `-list`, `-tree`, `-schema`) **all apply** here, exactly like trigger setup. For each: +2. **For each operation, triage every parameter — including each body sub-property.** + + First enumerate every parameter from the Swagger: + - All `parameters[]` entries (`path` / `query` / `body` / `header`). + - For each `body` parameter whose schema is an object (resolve `$ref`), + enumerate its `properties` and the inner `required` array. Recurse into + nested objects and `items` of arrays. + + Then resolve dynamic markers — the four extension kinds + (`x-ms-dynamic-values`, `-list`, `-tree`, `-schema`) **all apply** here, + exactly like trigger setup: - `x-ms-dynamic-values` / `-list` → `dynamicInvoke` the lookup operation, **STOP** for user pick, store the `value-path` value. - `x-ms-dynamic-tree` → walk the tree (root → children), **STOP** at each level, store the final opaque token. - - `x-ms-dynamic-schema` / `-properties` → resolve schema after parents are picked (the body shape comes from the connector). Bake the parents so the LLM gets a stable tool shape. + - `x-ms-dynamic-schema` / `-properties` → resolve schema after parents are picked. Bake the parents so the LLM gets a stable tool shape. - Cascading params (e.g., channel depends on team): always resolve parents first; pass their `value` (not display name) to child lookups. - - See [mcp-server-config.md](references/mcp-server-config.md) §"Resolving dynamic parameters for MCP config" and [dynamic-values.md](references/dynamic-values.md). - - Decide for each: bake into `userParameters[]` (fixed at config time) or leave for the LLM (free-form fields like subject/body). **Rule of thumb:** if the value space is enumerable connector data (team, channel, site, list, folder, file, db, table), **bake it**; if it's "anything the user might imagine" (subject, message body, email address), leave it for the LLM. **STOP at every dynamic param.** + + **STOP and ask the user, for every parameter (and every required body + sub-property)**, whether it should be: + + | Choice | What it means | + |---|---| + | **`userParameter`** | Bake a fixed value now (`{name, value}`). LLM cannot change it. Use this for enumerable connector IDs (team/channel/site/list/folder/file/db/table) and for org-default fields. | + | **`agentParameter`** | LLM supplies on each call (`{name, schema}`). Use this for free-form content (subject/body/recipient) and for required fields with no fixed value. | + | **skip** | Only valid if the field is optional (not in the Swagger `required` array). | + + **Required fields cannot be skipped.** Body sub-properties are nested under + one `agentParameter` whose `schema.type` is `"object"` and whose + `schema.properties` mirrors the body. See + [mcp-server-config.md](references/mcp-server-config.md) §"Per-parameter + triage workflow" and [dynamic-values.md](references/dynamic-values.md). 3. **PUT the MCP server config.** Body shape: ```json @@ -239,7 +262,10 @@ Ask the user: "displayName": "Send Email", "description": "Send an email via Office 365.", "userParameters": [ - { "name": "from", "displayName": "From", "value": "alice@contoso.com", "displayValue": "Alice" } + { "name": "from", "value": "alerts@contoso.com" } + ], + "agentParameters": [ + { "name": "emailMessage", "schema": { "type": "object", "required": true, "properties": { "To": { "type": "string", "required": true }, "Subject": { "type": "string" }, "Body": { "type": "string" } } } } ] } ] @@ -250,6 +276,11 @@ Ask the user: ``` PUT to: `.../connectorGateways/{gw}/mcpServerConfigs/{name}?api-version=2026-05-01-preview` + > **Always serialize with `ConvertTo-Json -Depth 20`** (or higher). The + > default depth (2) silently flattens nested body schemas to the literal + > string `"System.Collections.Hashtable"`. GET the config after the PUT and + > verify the nested properties are real objects. + 4. **GET the config** and return `properties.mcpEndpointUrl` — that's the URL the MCP client points at. See [mcp-server-config.md](references/mcp-server-config.md) for the auth-mode + access-policy details. diff --git a/Skills/General/azure-connectorgateway/references/gotchas.md b/Skills/General/azure-connectorgateway/references/gotchas.md index 9093b57..01f6198 100644 --- a/Skills/General/azure-connectorgateway/references/gotchas.md +++ b/Skills/General/azure-connectorgateway/references/gotchas.md @@ -22,6 +22,8 @@ Common issues for the generic connector-gateway skill and their fixes. | **MCP tool calls 401** | Underlying connection isn't consented or the consent expired. Re-run [consent.md](consent.md). | | **`resourceAuth` rejected on `ManagedMcpServer`** | `resourceAuth` is only valid on `HostedMcpServer`. Remove it. | | **MCP `userParameters[].value` is the display name** | The connector rejects it because it expects the underlying ID/key. Re-resolve via `dynamicInvoke` and store the `value-path` field. | +| **MCP endpoint fails with `missing required property '/'`** | The operation's required body sub-property (e.g., `emailMessage/To`) wasn't declared in `userParameters` or `agentParameters`. Re-PUT with the body as a single `agentParameter` whose `schema.type` is `"object"` and whose `schema.properties` mirrors the Swagger body — see [mcp-server-config.md](mcp-server-config.md) §"Body parameters". | +| **MCP config GET shows `"System.Collections.Hashtable"` inside `agentParameters[].schema.properties`** | `ConvertTo-Json -Depth` was too shallow when serializing the PUT body — nested objects got coerced to strings. Re-PUT with `-Depth 20` (or higher) and verify with GET. | | **`callbackTarget` rejected** | That field does not exist in the schema. Use `notificationDetails.callbackUrl`. | | **PowerShell `ConvertFrom-Json` fails on Swagger** | `az rest ... export=true` returns content that piping breaks. Always `-o json > $env:TEMP\swagger.json` first, then read the file. | | **Cleanup order** | Delete trigger configs and MCP server configs → delete access policies on connections → delete connections → delete gateway. Trigger configs hold subscriptions, so delete them first to avoid orphan webhooks. | diff --git a/Skills/General/azure-connectorgateway/references/mcp-server-config.md b/Skills/General/azure-connectorgateway/references/mcp-server-config.md index 010d654..e1d465e 100644 --- a/Skills/General/azure-connectorgateway/references/mcp-server-config.md +++ b/Skills/General/azure-connectorgateway/references/mcp-server-config.md @@ -35,7 +35,10 @@ credential. "displayName": "Send email", "description": "Send an email via Office 365.", "userParameters": [ - { "name": "from", "displayName": "From", "value": "alice@contoso.com", "displayValue": "Alice" } + { "name": "from", "value": "alice@contoso.com" } + ], + "agentParameters": [ + { "name": "emailMessage", "schema": { "type": "object", "properties": { "To": { "type": "string", "required": true }, "Subject": { "type": "string" }, "Body": { "type": "string" } } } } ] } ] @@ -58,39 +61,104 @@ credential. | `operations[].name` | required | The operation `name` from the connector's `apiOperations` (e.g., `Send_Email_(V2)`) | | `operations[].displayName` | required | Tool name shown to the LLM | | `operations[].description` | optional | Tool description shown to the LLM — write it for the LLM, not for humans | -| `operations[].userParameters[]` | optional | **Pre-bound** values for operation parameters — see below | +| `operations[].userParameters[]` | optional | **Pre-bound (fixed)** values for operation parameters — see "User vs agent" below | +| `operations[].agentParameters[]` | optional | **Caller-supplied (LLM-at-call-time)** parameters, declared as JSON Schema — see "User vs agent" below | -### `userParameters` — pre-bound parameter values +### `userParameters` vs `agentParameters` — every required field must appear in one of them -Each entry in `userParameters[]` is a parameter whose value is **fixed at config -time** rather than supplied by the LLM at call time. Typical examples: +This is the **single most important rule** for MCP server configs. The runtime +validates every required parameter of the operation (path, query, body — +including required body sub-properties) against the union of `userParameters` +and `agentParameters`. If a required field is missing from both, the endpoint +fails at invoke time with errors like: -- "Always post to this Teams channel" -- "Always send from this mailbox" -- "Always upload to this folder" +``` +The API operation 'SendEmailV2' is missing required property 'emailMessage/To'. +``` + +| Array | Shape | Semantics | +|---|---|---| +| `userParameters[]` | `{ "name": "", "value": }` | The value is **baked into the config** and sent on every tool call. The LLM cannot change it. | +| `agentParameters[]` | `{ "name": "", "schema": { "type": ..., "description": ..., "required": ..., "enum": ..., "properties": {...} } }` | The LLM **supplies the value on each tool call**. The schema becomes part of the MCP tool's input contract. | + +Decision rule for each operation parameter: + +- **Enumerable connector-supplied identifier** (team, channel, site, list, folder, file, database, table, mailbox) → bake as `userParameter` (use `dynamic-values` resolution — see below). +- **Free-form caller-controlled content** (subject, message body, recipient email, search query) → declare as `agentParameter`. +- **Required field with no obvious fixed value** → declare as `agentParameter` (so the LLM is forced to supply it). +- **Optional field that should be hidden from the LLM** → omit entirely. + +> **STOP and ask the user for every operation parameter** before the PUT: +> "Should `` be a fixed value you set now, supplied by the LLM at +> call time, or skipped (if optional)?" — do not guess. The LLM tool contract +> is exactly what's declared here. + +### Body parameters — nested object decomposition + +When an operation has a `body` parameter whose schema is a complex object +(e.g., Gmail `SendEmailV2`'s `emailMessage` body wraps `To`, `Subject`, `Body`, +…), the body becomes **one entry** in `agentParameters` whose `schema.type` is +`"object"` and whose `schema.properties` lists each inner field: ```json { - "name": "channelId", - "displayName": "Channel", - "value": "19:abc...@thread.tacv2", - "displayValue": "Logic Apps / general" + "name": "emailMessage", + "schema": { + "type": "object", + "required": true, + "properties": { + "To": { "type": "string", "required": true, "description": "Recipient address(es), semicolon- or comma-separated." }, + "Subject": { "type": "string", "description": "Subject of the outgoing email." }, + "Body": { "type": "string", "description": "HTML body of the outgoing email." }, + "Cc": { "type": "string", "description": "Cc addresses." }, + "Bcc": { "type": "string", "description": "Bcc addresses." }, + "Importance": { "type": "string", "enum": ["Normal","Low","High"], "description": "Importance." }, + "Attachments": { + "type": "array", + "description": "Attachments to send with the email.", + "items": { + "type": "object", + "properties": { + "Name": { "type": "string", "description": "File name." }, + "ContentBytes": { "type": "string", "format": "byte", "description": "Base64 content." }, + "ContentType": { "type": "string", "description": "MIME type." } + } + } + } + } + } } ``` -- `value` is what's sent to the connector (the ID/key) -- `displayValue` is human-readable -- Parameters **not** listed in `userParameters[]` remain LLM-supplied inputs at tool-call time +Mirror Swagger faithfully: `type`, `format`, `description`, `enum`, and +`required` (as a boolean on each sub-property — not the JSON Schema array form, +this is the Connector-Gateway convention). For nested arrays use `items` with +its own object schema. -> Resolve dynamic IDs by running the parameter's `x-ms-dynamic-*` lookups -> against the connection — exactly like [trigger-setup.md](trigger-setup.md) Step 2. -> See [dynamic-values.md](dynamic-values.md). **STOP** at each dynamic param for user selection. +To bake parts of the body as fixed values, use the **same wrapper name** in +`userParameters` with `value` as an object literal: -## Resolving dynamic parameters for MCP config +```json +{ "name": "emailMessage", "value": { "From": "alerts@contoso.com" } } +``` + +You can have **both** a `userParameters` entry and an `agentParameters` entry +for the same body root — `userParameters` provides fixed sub-fields, and +`agentParameters` declares the caller-supplied sub-fields. + +> **Heads up — `ConvertTo-Json` depth.** PowerShell defaults to depth 2 and the +> nested body schemas above are easily 4–6 levels deep. Always serialize with +> `-Depth 20` (or higher), otherwise inner properties will be silently flattened +> into the string `"System.Collections.Hashtable"` and the PUT will succeed but +> the runtime schema will be wrong. -**Every connector operation parameter goes through this triage before the PUT.** -This is the same logic as trigger setup, but applied to *all* operation -parameters, not just the ones in `notificationDetails`. +## Per-parameter triage workflow + +**Every connector operation parameter goes through this triage before the PUT — +no exceptions, even when there are no `x-ms-dynamic-*` markers anywhere on the +operation.** The body schema is part of the triage: a `body` parameter with a +nested object schema must be decomposed into its sub-properties so each one is +classified. ### Step 1: Get the connector's Swagger once @@ -103,23 +171,45 @@ az rest --method GET \ Find the operation by `operationId` and read its `parameters[]`. For each parameter, identify which extension is present. -### Step 2: Triage every parameter +### Step 2: Enumerate every parameter — including body sub-properties + +For each operation: + +1. List all top-level parameters from the Swagger (`parameters[]`): each has `in` + ∈ `{path, query, body, header}` and a `name`. +2. For any `body` parameter whose schema is an object (resolve `$ref` to + `definitions/`), enumerate its properties **and** its inner `required` + array. Recurse into nested objects and `items` of arrays. +3. Note which fields are required at each level. Required fields MUST be + classified — they cannot be skipped. -| Parameter has | What to do | Bake into `userParameters[]`? | +### Step 3: Triage every parameter — STOP and ASK per parameter + +**Bring the full parameter list to the user and ask, for each one:** + +> "Should this be a fixed value I bake now (`userParameter`), supplied by the +> caller / LLM on each tool call (`agentParameter`), or skipped (if optional)?" + +| Parameter shape | Default recommendation to surface to the user | If `userParameter` chosen | |---|---|---| -| `x-ms-dynamic-values` | Resolve via [dynamic-values.md](dynamic-values.md) §`x-ms-dynamic-values`. **STOP** for user pick. | Usually yes — IDs are not LLM-discoverable | -| `x-ms-dynamic-list` | Same as `x-ms-dynamic-values` — resolve, **STOP** for user pick. | Usually yes | -| `x-ms-dynamic-tree` | Walk the tree via [dynamic-values.md](dynamic-values.md) §`x-ms-dynamic-tree`. **STOP** at each level. | Usually yes — final `value` is opaque (e.g., `%252fShared%2520Documents`) | -| `x-ms-dynamic-schema` | The parameter's **body shape** depends on a parent param. See §"Dynamic schema" below. | Bake the parent, then decide for the body | -| `x-ms-dynamic-properties` | Same as `x-ms-dynamic-schema` (older variant). Handle identically. | Same as `x-ms-dynamic-schema` | -| Static enum | Show choices, **STOP** for user pick. | Bake if the user wants it fixed; leave for LLM otherwise | -| Free-form with obvious default | Use default, inform user. | Optional | -| Free-form, no default | Ask the user whether to bake it or let the LLM supply it. | User's choice | +| `x-ms-dynamic-values` | `userParameter` — IDs are not LLM-discoverable. Resolve via [dynamic-values.md](dynamic-values.md) §`x-ms-dynamic-values`. **STOP** for the user's pick. | Store the `value-path` field, not the display name | +| `x-ms-dynamic-list` | `userParameter` — same as above. | Same | +| `x-ms-dynamic-tree` | `userParameter` — final `value` is opaque (e.g., `%252fShared%2520Documents`). Walk the tree (§`x-ms-dynamic-tree`), **STOP** at each level. | Store the opaque token | +| `x-ms-dynamic-schema` body | Bake the parent(s) as `userParameter`, then triage the resolved body fields the same way. See §"Dynamic schema" below. | The parent's `value-path` value | +| `x-ms-dynamic-properties` | Same as `x-ms-dynamic-schema` (older variant). | Same | +| Static enum on a fixed-meaning field | Either — `userParameter` if there's an obvious org default, otherwise `agentParameter` with `schema.enum`. | The exact enum value | +| Required free-form caller content (`To`, `subject`, `body`, `query`) | `agentParameter` — declare the JSON Schema. | n/a | +| Optional free-form | `agentParameter` (omit `required`) if the LLM should be able to use it, otherwise skip. | n/a | -> **The "STOP for user pick" rule applies even for MCP config.** Never invent a -> team / channel / site / folder / list / mailbox / database ID. +> **Never invent a team / channel / site / folder / list / mailbox / database ID.** +> Resolve dynamic values via `dynamicInvoke` and STOP at every dynamic param. -### Step 3: Handle cascading dependencies +> **Never silently skip a required field.** Every Swagger `required` (including +> required sub-properties of a body object) must appear in `userParameters` or +> `agentParameters`, or the runtime will fail with +> `missing required property ''` at the first tool invocation. + +### Step 4: Handle cascading dependencies Many parameters' `x-ms-dynamic-*` lookups **depend on prior parameters**. You must resolve the parents first and use the chosen `value` as input to the @@ -146,17 +236,20 @@ To bake `groupId` + `channelId` into `userParameters[]`: 3. Build the MCP config: ```json "userParameters": [ - { "name": "groupId", "displayName": "Team", "value": "abc...", "displayValue": "Engineering" }, - { "name": "channelId", "displayName": "Channel", "value": "19:def...@thread.tacv2", "displayValue": "general" } + { "name": "groupId", "value": "abc..." }, + { "name": "channelId", "value": "19:def...@thread.tacv2" } + ], + "agentParameters": [ + { "name": "message", "schema": { "type": "object", "required": true, "properties": { "body": { "type": "object", "properties": { "content": { "type": "string", "required": true, "description": "Markdown / HTML message content." } } } } } } ] ``` -4. Leave `message` out of `userParameters[]` so the LLM supplies it per tool call. +4. Declare `message` as an `agentParameter` so the LLM supplies it per tool call. > **Always pass the stored `value` (from `value-path`), not the display name > (from `value-title`)**, as input to child lookups. The connector rejects > display names with `NotFound` or empty results. -### Step 4: Dynamic schema (body shape) +### Step 5: Dynamic schema (body shape) When an operation's body parameter uses `x-ms-dynamic-schema` (e.g., "Create item in SharePoint list" — the body fields are the list's columns), @@ -180,35 +273,39 @@ az rest --method POST \ # x-ms-dynamic-schema.operationId, with the resolved dataset + table) ``` -The returned JSON schema is what the LLM will see as the tool's input shape -for the body — confirm with the user before saving the MCP config. +Decompose the returned JSON schema into one `agentParameter` per resolved +field (or a single `agentParameter` whose `schema` mirrors the whole body +object), and STOP-and-ask the user per the Step 3 triage. Confirm with the +user before saving the MCP config. -### Step 5: LLM-facing input schema +### Step 6: LLM-facing input schema -Parameters **not** in `userParameters[]` become LLM-supplied inputs. The -gateway derives the MCP tool's input schema from those remaining parameters -in the operation's Swagger. So: +The MCP tool's input contract is **exactly** the union of `agentParameters[]` +entries you declared in Step 3. `userParameters[]` are invisible to the LLM — +they're baked into the outbound call. So: -- Static parameters → straightforward type in the MCP tool input schema -- `x-ms-dynamic-values` / `-list` left LLM-supplied → the LLM must guess - valid IDs; **this rarely works**. Prefer baking these as `userParameters[]`. -- `x-ms-dynamic-tree` left LLM-supplied → almost never works (opaque tokens). - Bake it. -- `x-ms-dynamic-schema` body left LLM-supplied with parents un-baked → the - body schema is unknown until tool-call time; LLM tool input becomes generic - `object`. **Bake the parents.** +- Required body sub-property → must be an `agentParameter` (or baked as a + `userParameter`). Never left implicit. +- `x-ms-dynamic-values` / `-list` left as `agentParameter` → the LLM must + guess valid IDs; **this rarely works**. Prefer baking these as `userParameters[]`. +- `x-ms-dynamic-tree` left as `agentParameter` → almost never works (opaque + tokens). Bake it. +- `x-ms-dynamic-schema` body left as `agentParameter` with parents un-baked → + the body schema is unknown until tool-call time; the MCP tool input becomes + generic `object`. **Bake the parents** as `userParameters`. **Rule of thumb:** if a parameter's value space is "anything the user might -imagine" (a subject line, a message body, an email address), leave it for the -LLM. If it's "one of an enumerable, connector-supplied set" (team, channel, -site, list, folder, file, database, table), **bake it via dynamic resolution**. +imagine" (a subject line, a message body, an email address), declare it as an +`agentParameter`. If it's "one of an enumerable, connector-supplied set" +(team, channel, site, list, folder, file, database, table), **bake it as a +`userParameter` via dynamic resolution**. ## PUT the config ```powershell $body = @{ properties = @{ - description = "Office 365 productivity tools" + description = "Send emails via Office 365." connectors = @( @{ name = "office365" @@ -218,16 +315,30 @@ $body = @{ @{ name = "Send_Email_(V2)" displayName = "Send email" - description = "Send an email via Office 365." + description = "Send an email through Office 365. Caller must supply 'To'." userParameters = @( - @{ name = "from"; displayName = "From"; value = "alice@contoso.com"; displayValue = "Alice" } + @{ name = "from"; value = "alerts@contoso.com" } + ) + agentParameters = @( + @{ + name = "emailMessage" + schema = @{ + type = "object" + required = $true + properties = @{ + To = @{ type = "string"; required = $true; description = "Recipient address(es), semicolon- or comma-separated." } + Subject = @{ type = "string"; description = "Subject line." } + Body = @{ type = "string"; description = "HTML body." } + } + } + } ) } ) } ) } -} | ConvertTo-Json -Depth 10 -Compress +} | ConvertTo-Json -Depth 20 -Compress # IMPORTANT: -Depth 20+ for nested body schemas $tmp = New-TemporaryFile; Set-Content $tmp $body az rest --method PUT ` @@ -236,6 +347,12 @@ az rest --method PUT ` Remove-Item $tmp ``` +> **Verify after the PUT** — `GET` the config and check that the nested +> `agentParameters[].schema.properties.` are present as objects, NOT as +> the literal string `"System.Collections.Hashtable"`. That string means +> `ConvertTo-Json -Depth` was too shallow — re-serialize with `-Depth 20` (or +> higher) and re-PUT. + ## Get the endpoint URL ```bash @@ -366,3 +483,6 @@ Validation rules (from BPM tests): | Guessing dynamic-value IDs in `userParameters[].value` | Always resolve via `dynamicInvoke` first ([dynamic-values.md](dynamic-values.md)). | | Forgetting access policies | The MCP endpoint will 403 your MCP client. Add the caller's objectId. | | Skipping consent on the underlying connection | Tool calls fail with 401 — go through [consent.md](consent.md) first. | +| **Skipping required body sub-properties** | Endpoint fails at invoke time with `missing required property '/'`. Every Swagger `required` field — including nested body properties — MUST be declared in `userParameters` or `agentParameters`. Triage them per §"Per-parameter triage workflow". | +| **Body params not nested under their root name** | The body becomes ONE `agentParameter` whose `name` is the body parameter's name (e.g., `emailMessage`) and whose `schema` is `{type:"object", properties: {...}}`. Don't flatten the inner fields to top-level entries. | +| **`ConvertTo-Json -Depth` too shallow → `"System.Collections.Hashtable"` in the saved schema** | Use `-Depth 20` (or higher) when serializing the PUT body. After the PUT, GET the config and confirm nested properties are objects, not the truncation string. | From 8bd0ef8887aac70ce666f6b3356a1adbd84ea4ce Mon Sep 17 00:00:00 2001 From: apranaseth Date: Fri, 29 May 2026 20:07:33 -0700 Subject: [PATCH 07/14] Cover nested-object construction for trigger configs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The trigger wizard (Cascade `serializeTriggerParams`) aggregates every body- sourced leaf — including dotted nested paths — into ONE `parameters[]` entry whose name is literally `"body"` (independent of the Swagger body parameter's own name, e.g. Teams declares it as `requestBody`) and whose `value` is the nested object. Non-body dotted leaves group under their root segment via `groupDottedEntriesToNestedObjects`. The skill previously only showed flat scalar examples and would have produced broken `body.` dotted entries. Also calls out the wrapper-name asymmetry between MCP and triggers: - MCP: body wrapper name = Swagger body param's own name (e.g. `emailMessage`). - Trigger: body wrapper name = literal `"body"`, always. Changes: - trigger-setup.md: added §2a (per-parameter enumeration + STOP-and-ask) and §2b (wire-shape rules with a nested-body example and an anti-pattern). Bumped the PUT example to ConvertTo-Json -Depth 20. Added Common-Mistakes rows for flat dotted body names, wrong wrapper name, and depth truncation. - SKILL.md: rewrote Step 4A item 2 to spell out body decomposition and the literal `"body"` wrapper. Updated the rules table entry for "Trigger body schema" to document the wrapper convention. Clarified MCP triage rule to highlight the wrapper-name difference. - gotchas.md: added rows for "body leaves visible as flat dotted params" and "trigger body wrapper named after the Swagger param". Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../General/azure-connectorgateway/SKILL.md | 20 ++++- .../references/gotchas.md | 2 + .../references/trigger-setup.md | 78 +++++++++++++++++-- 3 files changed, 90 insertions(+), 10 deletions(-) diff --git a/Skills/General/azure-connectorgateway/SKILL.md b/Skills/General/azure-connectorgateway/SKILL.md index 48b1280..88ad2f5 100644 --- a/Skills/General/azure-connectorgateway/SKILL.md +++ b/Skills/General/azure-connectorgateway/SKILL.md @@ -52,10 +52,10 @@ ngrok, anywhere) and you choose how the gateway authenticates to it. | **Execute, don't ask** | Once you have inputs, run the commands. Don't ask "Can I run this?" | | **`az rest` only** | No `az connectorgateway` or other extensions exist. Use `az rest` for ARM and `az rest --resource` for data-plane. | | **Always `@$tmpFile`** | For `az rest --body` in PowerShell — inline JSON breaks quoting. See [gotchas.md](references/gotchas.md). | -| **Trigger body schema** | Properties root contains: `type` OR `connectionDetails`+`operationName`+`parameters`, plus `notificationDetails` (`callbackUrl`+`authentication`+`body?`). See [trigger-setup.md](references/trigger-setup.md). | +| **Trigger body schema** | Properties root contains: `type` OR `connectionDetails`+`operationName`+`parameters`, plus `notificationDetails` (`callbackUrl`+`authentication`+`body?`). **Body-sourced leaves (top-level body param + every nested sub-property) are aggregated into ONE `parameters[]` entry whose `name` is literally `"body"` and whose `value` is the nested object** (dotted leaf names like `filter.labels` get rolled up via `setNestedValue`). The wrapper name `"body"` is independent of the Swagger body parameter's own name. Non-body dotted leaves are grouped under their root segment. See [trigger-setup.md](references/trigger-setup.md) §2b. | | **Trigger needs `gateway-acl`** | For connector-event triggers, the gateway MI must have an access policy on the connection. See [trigger-setup.md](references/trigger-setup.md) Step 4. | | **MCP user params** | Each `userParameters[]` entry is a **fixed value** for a connector-operation parameter (resolved via `dynamic-values` against the connection at config time). Each `agentParameters[]` entry declares a JSON-Schema input the **caller (LLM) supplies** at tool-call time. **Every required parameter (including required body sub-properties) MUST appear in one of the two arrays** — the runtime rejects the call with `missing required property ''` otherwise. See [mcp-server-config.md](references/mcp-server-config.md). | -| **MCP per-parameter triage** | **STOP and ask the user, for each operation parameter (path/query/body field, including each required body sub-property), whether it should be a fixed `userParameter`, a caller-supplied `agentParameter`, or skipped (optional only).** Do this **before** the `mcpServerConfigs` PUT, even when no `x-ms-dynamic-*` markers are present. Decompose the body's object schema and triage each leaf. See [mcp-server-config.md](references/mcp-server-config.md) §"Per-parameter triage workflow". | +| **MCP per-parameter triage** | **STOP and ask the user, for each operation parameter (path/query/body field, including each required body sub-property), whether it should be a fixed `userParameter`, a caller-supplied `agentParameter`, or skipped (optional only).** Do this **before** the `mcpServerConfigs` PUT, even when no `x-ms-dynamic-*` markers are present. Decompose the body's object schema and triage each leaf. **For MCP, body wrapper name = the Swagger body param's own name (e.g. `emailMessage`).** See [mcp-server-config.md](references/mcp-server-config.md) §"Per-parameter triage workflow". | | **Parallel execution** | Run independent ops (connections, ACLs, dynamic-value lookups, MCP operations) as parallel tool calls. | **When to STOP and ask the user:** subscription/resource group, gateway name, connection name, any parameter with dynamic values (teams/channels/folders/sites/lists), callback URL, callback authentication type, **MSI `audience` (if MSI auth chosen — default to `https://management.azure.com/` if user doesn't provide one)**, OAuth consent completion, **MCP per-parameter triage (`userParameter` vs `agentParameter` vs skip — for every path/query/body field, including each body sub-property)**. @@ -170,7 +170,21 @@ Ask the user: --url "https://management.azure.com/subscriptions/{sub}/providers/Microsoft.Web/locations/{location}/managedApis/{connector}/apiOperations?api-version=2016-06-01" \ --query "value[?properties.trigger != null].{name:name, summary:properties.summary, trigger:properties.trigger}" -o table ``` - Pick one with the user. Resolve any `x-ms-dynamic-*` parameters via [dynamic-values.md](references/dynamic-values.md). **STOP at every dynamic param.** + Pick one with the user. **Enumerate every parameter — including each + sub-property of a `body` object** (resolve `$ref`, recurse into nested + objects and array `items`). Triage each leaf: + - `x-ms-dynamic-*` → resolve via [dynamic-values.md](references/dynamic-values.md). **STOP at every dynamic param.** + - Static enum → present choices and **STOP** for user pick. + - Free-form with obvious default (e.g., `folderPath=Inbox`) → use default, inform user. + - Free-form with no default → **STOP and ask the user.** + + **When assembling `parameters[]`, build a nested object — do NOT emit dotted + leaf names.** All body-sourced leaves (top-level body + every nested + sub-property) collapse into ONE entry whose `name` is **literally the string + `"body"`** (NOT the Swagger body parameter's name) and whose `value` is the + nested object. Non-body dotted leaves group under their root segment. See + [trigger-setup.md](references/trigger-setup.md) §2b for the exact pattern + and worked examples. 3. **Recurrence / sliding window:** ask for `frequency` (Second/Minute/Hour/Day) and `interval`. diff --git a/Skills/General/azure-connectorgateway/references/gotchas.md b/Skills/General/azure-connectorgateway/references/gotchas.md index 01f6198..0e05cd9 100644 --- a/Skills/General/azure-connectorgateway/references/gotchas.md +++ b/Skills/General/azure-connectorgateway/references/gotchas.md @@ -24,6 +24,8 @@ Common issues for the generic connector-gateway skill and their fixes. | **MCP `userParameters[].value` is the display name** | The connector rejects it because it expects the underlying ID/key. Re-resolve via `dynamicInvoke` and store the `value-path` field. | | **MCP endpoint fails with `missing required property '/'`** | The operation's required body sub-property (e.g., `emailMessage/To`) wasn't declared in `userParameters` or `agentParameters`. Re-PUT with the body as a single `agentParameter` whose `schema.type` is `"object"` and whose `schema.properties` mirrors the Swagger body — see [mcp-server-config.md](mcp-server-config.md) §"Body parameters". | | **MCP config GET shows `"System.Collections.Hashtable"` inside `agentParameters[].schema.properties`** | `ConvertTo-Json -Depth` was too shallow when serializing the PUT body — nested objects got coerced to strings. Re-PUT with `-Depth 20` (or higher) and verify with GET. | +| **Trigger fails to subscribe / fires with wrong shape — body leaves visible as flat `body.` params in the GET** | The trigger config was PUT with dotted-name parameter entries instead of a single nested `body` object. Re-build `parameters[]` so all body-sourced leaves live under ONE entry named literally `"body"` with a nested object `value`. See [trigger-setup.md](trigger-setup.md) §2b. | +| **Trigger body wrapper named after the Swagger param (e.g. `requestBody`)** | Triggers always use the literal string `"body"` as the wrapper name, regardless of the Swagger body parameter's own name. (MCP is opposite — it uses the Swagger name.) See [trigger-setup.md](trigger-setup.md) §2b. | | **`callbackTarget` rejected** | That field does not exist in the schema. Use `notificationDetails.callbackUrl`. | | **PowerShell `ConvertFrom-Json` fails on Swagger** | `az rest ... export=true` returns content that piping breaks. Always `-o json > $env:TEMP\swagger.json` first, then read the file. | | **Cleanup order** | Delete trigger configs and MCP server configs → delete access policies on connections → delete connections → delete gateway. Trigger configs hold subscriptions, so delete them first to avoid orphan webhooks. | diff --git a/Skills/General/azure-connectorgateway/references/trigger-setup.md b/Skills/General/azure-connectorgateway/references/trigger-setup.md index 083570a..914ddf4 100644 --- a/Skills/General/azure-connectorgateway/references/trigger-setup.md +++ b/Skills/General/azure-connectorgateway/references/trigger-setup.md @@ -22,13 +22,74 @@ az rest --method GET \ ``` Present operations to the user. After selection, fetch the full Swagger (see -[direct-api.md](direct-api.md) §1) to find the operation's parameter list. For each -parameter: +[direct-api.md](direct-api.md) §1) to find the operation's parameter list. -- **Has `x-ms-dynamic-*`** → resolve via [dynamic-values.md](dynamic-values.md) and **STOP** for user selection -- **Static enum** → present choices and **STOP** -- **Free-form with obvious default** (e.g., `folderPath=Inbox`) → use default, inform user -- **Free-form, no default** → ask the user +### 2a. Enumerate every parameter — including body sub-properties + +Trigger parameters are baked at config time (there's no `agentParameters` for +triggers — it's `parameters[]` and every entry has a fixed `value`). But the +enumeration step is the same as for MCP: + +1. List all top-level `parameters[]` entries (`in: path | query | body | header`). +2. For any `body` parameter whose schema is an object (resolve `$ref` to + `definitions/`), enumerate its `properties` and required fields. + Recurse into nested objects and `items` of arrays. +3. For each leaf (top-level OR nested body sub-property), classify: + - **Has `x-ms-dynamic-*`** → resolve via [dynamic-values.md](dynamic-values.md) and **STOP** for user selection + - **Static enum** → present choices and **STOP** + - **Free-form with obvious default** (e.g., `folderPath=Inbox`) → use default, inform user + - **Free-form, no default** → **STOP** and ask the user + +> **STOP-and-ask applies to every parameter — including required body +> sub-properties.** Required fields cannot be skipped or the trigger will fail +> at subscribe time or first fire. + +### 2b. Wire shape — nested objects for body / dotted-path leaves + +Triggers use a single `parameters[]` array (no user vs agent split). Each entry +is `{name, value}` where `value` may be a scalar OR a complex object. There are +three rules for assembling it (mirroring Cascade's `serializeTriggerParams`): + +| Param shape (from Swagger) | Wire entry | +|---|---| +| Top-level scalar (`folderPath`, `groupId`) | `{ "name": "folderPath", "value": "Inbox" }` | +| Top-level object (non-body), with dotted leaf names like `filter.from` | `{ "name": "filter", "value": { "from": "..." } }` — group dotted entries under their root | +| **Any body-sourced leaf** (the Swagger `body` parameter or any of its nested sub-properties) | **All body leaves are collected into ONE entry named literally `"body"`** whose `value` is the nested object. Dotted leaf names like `repository.owner` are placed at their nested path inside `body`. | + +> **Critical:** The wrapper name for body params is **literally the string `"body"`**, +> NOT the Swagger body parameter's own name. (Teams' Swagger declares the body +> param as `requestBody`, but the runtime expects the entry to be named `body`.) +> This is a trigger-ARM-config convention — different from MCP, where the body +> wrapper preserves the Swagger name (e.g. `emailMessage`). See +> [mcp-server-config.md](mcp-server-config.md) §"Body parameters" for the MCP convention. + +**Example — single-body trigger** (e.g. a connector with `body.filter.labels`): + +```powershell +parameters = @( + @{ name = "groupId"; value = "abc..." } # top-level scalar + @{ + name = "body" # literal "body" wrapper + value = @{ + filter = @{ # nested object built from + labels = @("bug","release") # what the user supplied + } + includeAttachments = $true + } + } +) +``` + +**Anti-pattern (do NOT do this)** — keeping the body leaves as flat dotted +parameter names. The runtime won't route them: + +```powershell +# WRONG — flat dotted names won't be unwrapped server-side +parameters = @( + @{ name = "body.filter.labels"; value = "bug,release" } + @{ name = "body.includeAttachments"; value = $true } +) +``` > **Polling cadence:** if the operation has neither `x-ms-notification` nor > `x-ms-notification-content` in its Swagger, it polls (default ~3 min). Inform @@ -66,7 +127,7 @@ $triggerBody = @{ } state = "Enabled" } -} | ConvertTo-Json -Depth 8 -Compress +} | ConvertTo-Json -Depth 20 -Compress # -Depth 20+ for nested body objects $tmp = New-TemporaryFile; Set-Content $tmp $triggerBody az rest --method PUT ` @@ -234,6 +295,9 @@ and what HTTP status came back. | Putting `callbackUrl` at `properties.callbackUrl` | Goes under `properties.notificationDetails.callbackUrl` | | Putting `parameters` inside `connectionDetails` | Goes at `properties.parameters` (sibling of `connectionDetails`) | | Using `callbackTarget` | That field does not exist. Use `notificationDetails`. | +| **Body sub-properties emitted as flat dotted-name params** (e.g. `body.filter.labels`) | The runtime won't unwrap them. Build a nested object and emit it as `{ name = "body"; value = @{ filter = @{ labels = ... } } }`. See §2b. | +| **Body wrapper named after the Swagger param** (e.g. `requestBody` for Teams) | Triggers always use the literal string `"body"`. The Swagger body param's own name is irrelevant. (This is opposite to MCP, where the body wrapper preserves the Swagger name.) | +| **`ConvertTo-Json -Depth` too shallow** → nested objects coerced to `"System.Collections.Hashtable"` strings | Use `-Depth 20` (or higher) when serializing trigger configs that contain nested body objects. Verify with a GET after the PUT. | | Forgetting `gateway-acl` on a connector-event trigger | Subscription fails silently — trigger state may show `Enabled` but never fires. Create the ACL. | | Inline JSON `--body '...'` in PowerShell | "Unsupported Media Type" — always `@$tmpFile`. See [gotchas.md](gotchas.md). | | `ManagedServiceIdentity` auth without an audience | `audience` is required (non-empty). Ask the user for it; if they don't provide one, default to `https://management.azure.com/`. | From a78471c66b952af379d972a6effaaf0686c63f39 Mon Sep 17 00:00:00 2001 From: apranaseth Date: Fri, 29 May 2026 20:09:23 -0700 Subject: [PATCH 08/14] Add end-to-end worked examples for complex body assembly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both MCP and trigger configs need to build nested-object payloads from per-leaf user answers. The previous commit covered the rules and shapes but didn't walk through the Swagger → user answers → assembled wire entry flow. Add concrete worked examples in both docs: - trigger-setup.md §2c: Teams PostMessageToChannelV3 → user picks team/channel, types message content, picks contentType → assembled as ONE entry named "body" (literal) with the recipient/messageBody sub-objects. - mcp-server-config.md "Worked example": SharePoint PostItem → user splits Title (agent), Status (user="Open"), Assignee.Email (agent), Tags (agent) → assembled as paired userParameters/agentParameters entries with the same root name "item" (Swagger body param's own name). The two examples make the user-vs-agent / fixed-vs-dynamic distinction and the wrapper-name asymmetry (literal "body" for triggers vs Swagger name for MCP) concrete. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../references/mcp-server-config.md | 81 +++++++++++++++++++ .../references/trigger-setup.md | 74 +++++++++++++++++ 2 files changed, 155 insertions(+) diff --git a/Skills/General/azure-connectorgateway/references/mcp-server-config.md b/Skills/General/azure-connectorgateway/references/mcp-server-config.md index e1d465e..ade68df 100644 --- a/Skills/General/azure-connectorgateway/references/mcp-server-config.md +++ b/Skills/General/azure-connectorgateway/references/mcp-server-config.md @@ -146,6 +146,87 @@ You can have **both** a `userParameters` entry and an `agentParameters` entry for the same body root — `userParameters` provides fixed sub-fields, and `agentParameters` declares the caller-supplied sub-fields. +### Worked example — Swagger → user answers → assembled MCP config + +For SharePoint `PostItem` the Swagger body is: + +```json +"item": { // ← Swagger body param's own name + "in": "body", + "schema": { + "type": "object", + "required": ["Title"], + "properties": { + "Title": { "type": "string" }, + "Status": { "type": "string", "enum": ["Open","Closed"] }, + "Assignee": { "type": "object", "properties": { + "Email": { "type": "string" } + } }, + "Tags": { "type": "array", "items": { "type": "string" } } + } + } +} +``` + +**Step A — enumerate every leaf:** `Title`, `Status`, `Assignee.Email`, `Tags`. + +**Step B — STOP and ask per leaf** (record the user's answers): + +| Leaf | Type | User's choice | +|---|---|---| +| `Title` | Free-form, required | `agentParameter` (LLM supplies per call) | +| `Status` | Static enum, required-ish | `userParameter`, fixed to `"Open"` (org default) | +| `Assignee.Email` | Free-form | `agentParameter` | +| `Tags` | Free-form array | `agentParameter` | + +**Step C — assemble the MCP operation entry** — body wrapper preserves the +Swagger name (`item`), `userParameters` and `agentParameters` BOTH reference +that same root: + +```powershell +operations = @( + @{ + name = "PostItem" + displayName = "Create SharePoint list item" + userParameters = @( + @{ + name = "item" # SAME root name as the Swagger body + value = @{ Status = "Open" } # only the baked sub-fields + } + ) + agentParameters = @( + @{ + name = "item" # SAME root name — split with userParameters + schema = @{ + type = "object" + required = $true + properties = @{ + Title = @{ type = "string"; required = $true; description = "List item title." } + Assignee = @{ type = "object"; properties = @{ + Email = @{ type = "string"; description = "Assignee email." } + } } + Tags = @{ type = "array"; items = @{ type = "string" }; description = "Tags." } + # Status omitted from the schema — it's baked above + } + } + } + ) + } +) +``` + +At runtime the gateway merges `userParameters["item"]` (fixed `Status="Open"`) +with whatever the LLM supplied for `agentParameters["item"]` and POSTs the +combined object to the connector. + +> **Mental model — symmetric with triggers:** treat the user's per-leaf answers +> as a flat `Record` map, then assemble two nested objects +> per body root — one whose values are baked (`userParameters`) and one whose +> values become a JSON schema (`agentParameters`). The wrapper name is the +> Swagger body param's name (e.g. `item`, `emailMessage`) — NOT literal `"body"` +> like triggers use. See [trigger-setup.md](trigger-setup.md) §2c for the +> trigger-side flow. + > **Heads up — `ConvertTo-Json` depth.** PowerShell defaults to depth 2 and the > nested body schemas above are easily 4–6 levels deep. Always serialize with > `-Depth 20` (or higher), otherwise inner properties will be silently flattened diff --git a/Skills/General/azure-connectorgateway/references/trigger-setup.md b/Skills/General/azure-connectorgateway/references/trigger-setup.md index 914ddf4..a0bb2c7 100644 --- a/Skills/General/azure-connectorgateway/references/trigger-setup.md +++ b/Skills/General/azure-connectorgateway/references/trigger-setup.md @@ -91,6 +91,80 @@ parameters = @( ) ``` +### 2c. Worked example — Swagger → user answers → assembled body + +For Teams `PostMessageToChannelV3` the Swagger body is: + +```json +"requestBody": { // ← Swagger body param's own name + "in": "body", + "schema": { + "type": "object", + "required": ["recipient"], + "properties": { + "recipient": { + "type": "object", + "required": ["groupId", "channelId"], + "properties": { + "groupId": { "type": "string", "x-ms-dynamic-values": {...} }, + "channelId": { "type": "string", "x-ms-dynamic-values": {...} } + } + }, + "messageBody": { + "type": "object", + "properties": { + "content": { "type": "string" }, + "contentType": { "type": "string", "enum": ["html","text"] } + } + }, + "subject": { "type": "string" } + } + } +} +``` + +**Step A — enumerate every leaf:** `recipient.groupId`, `recipient.channelId`, +`messageBody.content`, `messageBody.contentType`, `subject`. Required ones: +`recipient.groupId`, `recipient.channelId` (parents implied). + +**Step B — STOP and ask per leaf** (record the user's answers): + +| Leaf | Source | User-supplied answer | +|---|---|---| +| `recipient.groupId` | `x-ms-dynamic-values` → `GetAllTeams` → STOP, user picks "Engineering" | value-path = `"abc-team-id"` | +| `recipient.channelId` | `x-ms-dynamic-values` → `GetChannelsForGroup(groupId)` → STOP, user picks "general" | `"19:def@thread.tacv2"` | +| `messageBody.content` | Free-form, ask user | `"Build broken on main"` | +| `messageBody.contentType` | Static enum, ask user (default `text`) | `"text"` | +| `subject` | Free-form, optional, ask user | *(skipped)* | + +**Step C — assemble the body as ONE nested object** named literally `"body"` +(NOT `"requestBody"`): + +```powershell +parameters = @( + @{ + name = "body" # literal "body" — NOT "requestBody" + value = @{ + recipient = @{ # assembled from recipient.* leaves + groupId = "abc-team-id" + channelId = "19:def@thread.tacv2" + } + messageBody = @{ # assembled from messageBody.* leaves + content = "Build broken on main" + contentType = "text" + } + # subject omitted because the user skipped it + } + } +) +``` + +> **Mental model:** treat the user's per-leaf answers as a flat +> `Record` map, then `setNestedValue` each entry into a +> single accumulator object whose root is `body`. That accumulator becomes one +> `parameters[]` entry. This mirrors Cascade's `serializeTriggerParams` exactly +> (`src\Cascade.Portal.Client\src\components\ConnectorGateways\TriggerWizard\utils\serializeTriggerParams.ts`). + > **Polling cadence:** if the operation has neither `x-ms-notification` nor > `x-ms-notification-content` in its Swagger, it polls (default ~3 min). Inform > the user; if they want a different cadence, omit `type` and add a `recurrence` From 8a4fb1418ba8ff16d54c350fe80cba7fa107914a Mon Sep 17 00:00:00 2001 From: apranaseth Date: Fri, 29 May 2026 20:50:13 -0700 Subject: [PATCH 09/14] Rebrand "Connector Gateway" -> "Connector Namespace" (display only) Cascade portal has renamed the user-facing term to "Connector Namespace" (see CreateConnectorNamespacePanel.tsx, ConnectorNamespaceIcon.tsx, the CONNECTOR_NAMESPACE_REGIONS_* constants, etc.). Update the skill to match. What changed: - Folder: Skills/General/azure-connectorgateway/ -> Skills/General/azure-connectornamespace/ (git mv preserves history). - SKILL.md frontmatter: name -> azure-connectornamespace; description rewritten with "Connector Namespace"; kept "connector gateway" as a legacy discovery keyword so users searching the old term still hit this skill. - All prose in SKILL.md + references/*.md: "Connector Gateway"/"gateway" -> "Connector Namespace"/"namespace" with proper case handling. - Variable placeholders: {gw} -> {namespace}, $gw -> $namespace, $gwBody -> $namespaceBody, $gwMi -> $namespaceMi, $gwTenant -> $namespaceTenant. - Example resource names: gateway-acl -> namespace-acl, tutorial-gw -> tutorial-ns, -> . - Added a "Naming note" section on SKILL.md explaining the rename and what stayed unchanged. - Added a gotchas.md row clarifying display name vs API strings. What stayed unchanged (API contracts): - ARM resource type: Microsoft.Web/connectorGateways (URL segment is the live REST API path). - Sandbox-group property: gatewayConnections[]. - RBAC string: Microsoft.Web/connectorGateways/*. - Cascade source folder reference: src/.../ConnectorGateways/. - Sandbox companion skill folder: Skills/Sandbox/azure-connectorgateway/ (separate skill, out of scope for this rebrand). - Test class name: AIGatewayApiTests_NotificationAuthValidation_*. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../SKILL.md | 93 ++++++++++--------- .../references/connections.md | 28 +++--- .../references/consent.md | 6 +- .../references/direct-api.md | 8 +- .../references/dynamic-values.md | 6 +- .../references/gotchas.md | 15 +-- .../references/mcp-server-config.md | 24 ++--- .../references/notification-authentication.md | 30 +++--- .../references/prerequisites.md | 15 +-- .../references/quickstart.md | 14 +-- .../references/trigger-flow.md | 20 ++-- .../references/trigger-setup.md | 34 +++---- .../references/tutorial.md | 46 ++++----- .../version.json | 0 14 files changed, 174 insertions(+), 165 deletions(-) rename Skills/General/{azure-connectorgateway => azure-connectornamespace}/SKILL.md (77%) rename Skills/General/{azure-connectorgateway => azure-connectornamespace}/references/connections.md (80%) rename Skills/General/{azure-connectorgateway => azure-connectornamespace}/references/consent.md (83%) rename Skills/General/{azure-connectorgateway => azure-connectornamespace}/references/direct-api.md (94%) rename Skills/General/{azure-connectorgateway => azure-connectornamespace}/references/dynamic-values.md (98%) rename Skills/General/{azure-connectorgateway => azure-connectornamespace}/references/gotchas.md (77%) rename Skills/General/{azure-connectorgateway => azure-connectornamespace}/references/mcp-server-config.md (95%) rename Skills/General/{azure-connectorgateway => azure-connectornamespace}/references/notification-authentication.md (80%) rename Skills/General/{azure-connectorgateway => azure-connectornamespace}/references/prerequisites.md (68%) rename Skills/General/{azure-connectorgateway => azure-connectornamespace}/references/quickstart.md (73%) rename Skills/General/{azure-connectorgateway => azure-connectornamespace}/references/trigger-flow.md (84%) rename Skills/General/{azure-connectorgateway => azure-connectornamespace}/references/trigger-setup.md (88%) rename Skills/General/{azure-connectorgateway => azure-connectornamespace}/references/tutorial.md (77%) rename Skills/General/{azure-connectorgateway => azure-connectornamespace}/version.json (100%) diff --git a/Skills/General/azure-connectorgateway/SKILL.md b/Skills/General/azure-connectornamespace/SKILL.md similarity index 77% rename from Skills/General/azure-connectorgateway/SKILL.md rename to Skills/General/azure-connectornamespace/SKILL.md index 88ad2f5..d0ae746 100644 --- a/Skills/General/azure-connectorgateway/SKILL.md +++ b/Skills/General/azure-connectornamespace/SKILL.md @@ -1,37 +1,44 @@ --- -name: azure-connectorgateway +name: azure-connectornamespace description: | - Azure Connector Gateway — manage gateways, connections, triggers, and MCP server configs. + Azure Connector Namespace — manage namespaces, connections, triggers, and MCP server configs. Connect external SaaS services (Office 365, Teams, SharePoint, OneDrive, Forms, GitHub, Azure Blob, ...) to any user-provided webhook URL via event-driven triggers, expose selected connector operations as an MCP server endpoint, or call connector operations on demand via `dynamicInvoke`. Use when: - - Creating or managing connector gateways and connections + - Creating or managing connector namespaces and connections - Creating or managing trigger configs that POST to an arbitrary callback URL - Subscribing to connector events (email, file, list-item, form response, Teams message) - Wiring event sources to a customer-owned webhook, Function App, Logic App, or API - Recurrence / sliding-window triggers that fire on a schedule - - Exposing connector operations as Model Context Protocol (MCP) tools at a gateway endpoint + - Exposing connector operations as Model Context Protocol (MCP) tools at a namespace endpoint - Calling connector APIs (send email, post Teams message, upload files, list items, ...) - Triggers: "connector gateway", "create trigger", "trigger config", "webhook trigger", + Triggers: "connector namespace", "connector gateway", "create trigger", "trigger config", "webhook trigger", "recurrence trigger", "schedule trigger", "on new email", "on new file", "on new item", "on form response", "callback url", "notification url", "mcp", "mcp server", "mcp tools", "model context protocol", "send email", "post teams message", "upload to onedrive", "automate" --- -# Azure Connector Gateway (generic) +# Azure Connector Namespace (generic) -Manage Microsoft.Web connector gateways, their connections, trigger configs, and MCP +Manage Microsoft.Web connector namespaces, their connections, trigger configs, and MCP server configs. This skill is **sandbox-agnostic** — it does not assume your callback target. Callbacks can be any HTTP(S) URL (Function App, Logic App, App Service, -ngrok, anywhere) and you choose how the gateway authenticates to it. +ngrok, anywhere) and you choose how the namespace authenticates to it. > If you specifically need to fan events into an Azure Container Apps sandbox group > (with declarative `gatewayConnections[]` wiring and sandbox callbacks), use the > companion skill at `Skills/Sandbox/azure-connectorgateway` instead. +> **Naming note.** This skill was previously called "Connector Gateway". The +> resource is now displayed as **"Connector Namespace"** everywhere (matching +> the Cascade portal rename — see `Cascade.Portal.Client/src/components/CreateConnectorNamespacePanel.tsx`). +> **The ARM resource type is still `Microsoft.Web/connectorGateways`** (legacy +> URL segment kept for backwards compatibility) and the property `gatewayConnections[]` +> on sandbox groups also keeps its name. Do not rewrite those API strings. + ## Three patterns this skill supports | Pattern | When to use | Output | @@ -48,19 +55,19 @@ ngrok, anywhere) and you choose how the gateway authenticates to it. | **No generated notebooks/scripts** | Walk the user through interactively. Do NOT generate a standalone notebook or script. | | **No guessing dynamic values** | `x-ms-dynamic-*` → call the API, present results, STOP. Never assume a team/channel/folder/site/list. | | **No guessing the callback URL** | The callback URL is **always** user-provided. Ask for it explicitly. Do NOT invent one. | -| **MSI audience: ask, then default** | For `ManagedServiceIdentity` callback auth, **ask the user for `audience`** (the AAD-protected resource the gateway should acquire a token for, e.g. an AAD app they own, `https://graph.microsoft.com/`, etc.). If the user doesn't provide one, default to `https://management.azure.com/`. **Never** use the callback URL as the audience. | +| **MSI audience: ask, then default** | For `ManagedServiceIdentity` callback auth, **ask the user for `audience`** (the AAD-protected resource the namespace should acquire a token for, e.g. an AAD app they own, `https://graph.microsoft.com/`, etc.). If the user doesn't provide one, default to `https://management.azure.com/`. **Never** use the callback URL as the audience. | | **Execute, don't ask** | Once you have inputs, run the commands. Don't ask "Can I run this?" | -| **`az rest` only** | No `az connectorgateway` or other extensions exist. Use `az rest` for ARM and `az rest --resource` for data-plane. | +| **`az rest` only** | No `az connectornamespace` (or legacy `az connectorgateway`) extension exists. Use `az rest` for ARM and `az rest --resource` for data-plane. | | **Always `@$tmpFile`** | For `az rest --body` in PowerShell — inline JSON breaks quoting. See [gotchas.md](references/gotchas.md). | | **Trigger body schema** | Properties root contains: `type` OR `connectionDetails`+`operationName`+`parameters`, plus `notificationDetails` (`callbackUrl`+`authentication`+`body?`). **Body-sourced leaves (top-level body param + every nested sub-property) are aggregated into ONE `parameters[]` entry whose `name` is literally `"body"` and whose `value` is the nested object** (dotted leaf names like `filter.labels` get rolled up via `setNestedValue`). The wrapper name `"body"` is independent of the Swagger body parameter's own name. Non-body dotted leaves are grouped under their root segment. See [trigger-setup.md](references/trigger-setup.md) §2b. | -| **Trigger needs `gateway-acl`** | For connector-event triggers, the gateway MI must have an access policy on the connection. See [trigger-setup.md](references/trigger-setup.md) Step 4. | +| **Trigger needs `namespace-acl`** | For connector-event triggers, the namespace MI must have an access policy on the connection. See [trigger-setup.md](references/trigger-setup.md) Step 4. | | **MCP user params** | Each `userParameters[]` entry is a **fixed value** for a connector-operation parameter (resolved via `dynamic-values` against the connection at config time). Each `agentParameters[]` entry declares a JSON-Schema input the **caller (LLM) supplies** at tool-call time. **Every required parameter (including required body sub-properties) MUST appear in one of the two arrays** — the runtime rejects the call with `missing required property ''` otherwise. See [mcp-server-config.md](references/mcp-server-config.md). | | **MCP per-parameter triage** | **STOP and ask the user, for each operation parameter (path/query/body field, including each required body sub-property), whether it should be a fixed `userParameter`, a caller-supplied `agentParameter`, or skipped (optional only).** Do this **before** the `mcpServerConfigs` PUT, even when no `x-ms-dynamic-*` markers are present. Decompose the body's object schema and triage each leaf. **For MCP, body wrapper name = the Swagger body param's own name (e.g. `emailMessage`).** See [mcp-server-config.md](references/mcp-server-config.md) §"Per-parameter triage workflow". | | **Parallel execution** | Run independent ops (connections, ACLs, dynamic-value lookups, MCP operations) as parallel tool calls. | -**When to STOP and ask the user:** subscription/resource group, gateway name, connection name, any parameter with dynamic values (teams/channels/folders/sites/lists), callback URL, callback authentication type, **MSI `audience` (if MSI auth chosen — default to `https://management.azure.com/` if user doesn't provide one)**, OAuth consent completion, **MCP per-parameter triage (`userParameter` vs `agentParameter` vs skip — for every path/query/body field, including each body sub-property)**. +**When to STOP and ask the user:** subscription/resource group, namespace name, connection name, any parameter with dynamic values (teams/channels/folders/sites/lists), callback URL, callback authentication type, **MSI `audience` (if MSI auth chosen — default to `https://management.azure.com/` if user doesn't provide one)**, OAuth consent completion, **MCP per-parameter triage (`userParameter` vs `agentParameter` vs skip — for every path/query/body field, including each body sub-property)**. -**When to EXECUTE immediately:** gateway/connection/trigger/MCP-config/access-policy CRUD, role assignments, dynamic-value lookups. +**When to EXECUTE immediately:** namespace/connection/trigger/MCP-config/access-policy CRUD, role assignments, dynamic-value lookups. --- @@ -81,37 +88,37 @@ ngrok, anywhere) and you choose how the gateway authenticates to it. --- -### Step 1: Gateway setup +### Step 1: Namespace setup -> **ARM base:** `https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways` +> **ARM base:** `https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorNamespaces` > **API version:** `2026-05-01-preview` -Ask the user: "Do you have an existing connector gateway, or should I create a new one?" +Ask the user: "Do you have an existing connector namespace, or should I create a new one?" - **Existing:** ask for the name and fetch it: ```bash az rest --method GET \ - --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}?api-version=2026-05-01-preview" \ + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{namespace}?api-version=2026-05-01-preview" \ --query "{name:name, location:location, principalId:identity.principalId, tenantId:identity.tenantId, identityType:identity.type, userAssigned:identity.userAssignedIdentities}" ``` -- **New:** ask for `{gw}` name + location. **Create with a SystemAssigned managed identity** +- **New:** ask for `{namespace}` name + location. **Create with a SystemAssigned managed identity** (required for trigger event subscriptions on connector-event triggers AND for `ManagedServiceIdentity` callback auth): ```powershell - $gwBody = @{ location = "{location}"; identity = @{ type = "SystemAssigned" } } | ConvertTo-Json -Compress - $tmp = New-TemporaryFile; Set-Content $tmp $gwBody + $namespaceBody = @{ location = "{location}"; identity = @{ type = "SystemAssigned" } } | ConvertTo-Json -Compress + $tmp = New-TemporaryFile; Set-Content $tmp $namespaceBody az rest --method PUT ` - --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}?api-version=2026-05-01-preview" ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{namespace}?api-version=2026-05-01-preview" ` --body "@$tmp" ` --query "{name:name, principalId:identity.principalId, tenantId:identity.tenantId}" Remove-Item $tmp ``` -**Capture `principalId` and `tenantId`** — needed later for the `gateway-acl` +**Capture `principalId` and `tenantId`** — needed later for the `namespace-acl` access policy and (optionally) for `ManagedServiceIdentity` callback authentication. > If a user later wants to call their callback URL using a **user-assigned** identity, -> they'll need to add that identity to the gateway separately. See +> they'll need to add that identity to the namespace separately. See > [notification-authentication.md](references/notification-authentication.md). --- @@ -127,15 +134,15 @@ Create connections in parallel: $connBody = @{ properties = @{ connectorName = "office365" }; location = "{location}" } | ConvertTo-Json -Compress $tmp = New-TemporaryFile; Set-Content $tmp $connBody az rest --method PUT ` - --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/connections/o365-conn?api-version=2026-05-01-preview" ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{namespace}/connections/o365-conn?api-version=2026-05-01-preview" ` --body "@$tmp" Remove-Item $tmp ``` Then generate consent links and open in the browser — **see [consent.md](references/consent.md)** for the exact body format and `Start-Process` pattern. Verify status `Connected` before continuing. See also [connections.md](references/connections.md) for the full CRUD reference. -> **For trigger configs:** also create a `gateway-acl` access policy granting the -> gateway MI access to the connection (required for the gateway to subscribe to +> **For trigger configs:** also create a `namespace-acl` access policy granting the +> namespace MI access to the connection (required for the namespace to subscribe to > connector events). See [trigger-setup.md](references/trigger-setup.md) Step 4. --- @@ -144,7 +151,7 @@ Then generate consent links and open in the browser — **see [consent.md](refer Ask the user: - **A) Trigger config** — push notifications to your callback URL when events happen, or on a schedule. -- **B) MCP server config** — expose selected connector operations as MCP tools at a gateway endpoint, callable by any MCP client (Claude Desktop, VS Code, etc.). +- **B) MCP server config** — expose selected connector operations as MCP tools at a namespace endpoint, callable by any MCP client (Claude Desktop, VS Code, etc.). - **C) Direct API call** — one-off `dynamicInvoke` (send an email now, list items now). **Stop and wait for the user's answer.** @@ -200,18 +207,18 @@ Ask the user: | `QueryString` | Add a `?key=value` automatically (e.g., Function App key) | | `Raw` | Send a literal `Authorization: ` header | | `Basic` | HTTP Basic with `username`/`password` | - | `ManagedServiceIdentity` | Gateway acquires a token for `audience` using its own MI (system- or user-assigned). **Ask the user for `audience`** — if they don't provide one, default to `https://management.azure.com/`. Never use the callback URL as the audience. See [notification-authentication.md](references/notification-authentication.md). | - | `ActiveDirectoryOAuth` | Gateway authenticates as an Entra app (tenant/clientId/secret/audience) | + | `ManagedServiceIdentity` | Namespace acquires a token for `audience` using its own MI (system- or user-assigned). **Ask the user for `audience`** — if they don't provide one, default to `https://management.azure.com/`. Never use the callback URL as the audience. See [notification-authentication.md](references/notification-authentication.md). | + | `ActiveDirectoryOAuth` | Namespace authenticates as an Entra app (tenant/clientId/secret/audience) | | `ClientCertificate` | Mutual TLS with a `pfx`/`password` | 6. **Create the trigger config** (one PUT). See [trigger-setup.md](references/trigger-setup.md) Step 3 for the canonical body templates for each source × auth combination. -7. **Create the `gateway-acl`** (only for connector-event triggers). See [trigger-setup.md](references/trigger-setup.md) Step 4. +7. **Create the `namespace-acl`** (only for connector-event triggers). See [trigger-setup.md](references/trigger-setup.md) Step 4. 8. **Verify** the trigger state is `Enabled`: ```bash az rest --method GET \ - --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/triggerConfigs/{name}?api-version=2026-05-01-preview" \ + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{namespace}/triggerConfigs/{name}?api-version=2026-05-01-preview" \ --query "properties.state" -o tsv ``` @@ -288,7 +295,7 @@ Ask the user: } } ``` - PUT to: `.../connectorGateways/{gw}/mcpServerConfigs/{name}?api-version=2026-05-01-preview` + PUT to: `.../connectorGateways/{namespace}/mcpServerConfigs/{name}?api-version=2026-05-01-preview` > **Always serialize with `ConvertTo-Json -Depth 20`** (or higher). The > default depth (2) silently flattens nested body schemas to the literal @@ -313,8 +320,8 @@ Ask the user: ### Final verification checklist **For trigger configs (path A):** -- ✅ Gateway exists; for `ManagedServiceIdentity` callback auth, gateway has the requested identity -- ✅ For connector-event triggers: connection `Connected`, `gateway-acl` exists on the connection +- ✅ Namespace exists; for `ManagedServiceIdentity` callback auth, namespace has the requested identity +- ✅ For connector-event triggers: connection `Connected`, `namespace-acl` exists on the connection - ✅ Trigger `properties.state` is `Enabled` - ✅ User-provided callback URL is reachable + accepts the chosen auth @@ -334,11 +341,11 @@ Ask the user: # ARM base: https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways # API ver: api-version=2026-05-01-preview -# Gateway -az rest --method GET --url ".../connectorGateways/{gw}?api-version=2026-05-01-preview" +# Namespace +az rest --method GET --url ".../connectorGateways/{namespace}?api-version=2026-05-01-preview" # Connections -az rest --method GET --url ".../connectorGateways/{gw}/connections?api-version=2026-05-01-preview" +az rest --method GET --url ".../connectorGateways/{namespace}/connections?api-version=2026-05-01-preview" # List trigger operations + summaries az rest --method GET --url ".../locations/{location}/managedApis/{connector}/apiOperations?api-version=2016-06-01" @@ -347,16 +354,16 @@ az rest --method GET --url ".../locations/{location}/managedApis/{connector}/api az rest --method GET --url ".../locations/{location}/managedApis/{connector}" --url-parameters "api-version=2016-06-01" "export=true" # Dynamic invoke -az rest --method POST --url ".../connectorGateways/{gw}/connections/{conn}/dynamicInvoke?api-version=2026-05-01-preview" --body '{"request":{"method":"GET","path":"/..."}}' +az rest --method POST --url ".../connectorGateways/{namespace}/connections/{conn}/dynamicInvoke?api-version=2026-05-01-preview" --body '{"request":{"method":"GET","path":"/..."}}' # Trigger configs -az rest --method GET --url ".../connectorGateways/{gw}/triggerConfigs?api-version=2026-05-01-preview" -az rest --method POST --url ".../connectorGateways/{gw}/triggerConfigs/{name}/disable?api-version=2026-05-01-preview" -az rest --method POST --url ".../connectorGateways/{gw}/triggerConfigs/{name}/enable?api-version=2026-05-01-preview" +az rest --method GET --url ".../connectorGateways/{namespace}/triggerConfigs?api-version=2026-05-01-preview" +az rest --method POST --url ".../connectorGateways/{namespace}/triggerConfigs/{name}/disable?api-version=2026-05-01-preview" +az rest --method POST --url ".../connectorGateways/{namespace}/triggerConfigs/{name}/enable?api-version=2026-05-01-preview" # MCP server configs -az rest --method GET --url ".../connectorGateways/{gw}/mcpServerConfigs?api-version=2026-05-01-preview" -az rest --method GET --url ".../connectorGateways/{gw}/mcpServerConfigs/{name}?api-version=2026-05-01-preview" --query "properties.mcpEndpointUrl" -o tsv +az rest --method GET --url ".../connectorGateways/{namespace}/mcpServerConfigs?api-version=2026-05-01-preview" +az rest --method GET --url ".../connectorGateways/{namespace}/mcpServerConfigs/{name}?api-version=2026-05-01-preview" --query "properties.mcpEndpointUrl" -o tsv ``` ## References diff --git a/Skills/General/azure-connectorgateway/references/connections.md b/Skills/General/azure-connectornamespace/references/connections.md similarity index 80% rename from Skills/General/azure-connectorgateway/references/connections.md rename to Skills/General/azure-connectornamespace/references/connections.md index 29c0ea7..b9d5546 100644 --- a/Skills/General/azure-connectorgateway/references/connections.md +++ b/Skills/General/azure-connectornamespace/references/connections.md @@ -1,7 +1,7 @@ # Connections A **connection** is a stored credential for one connector (e.g., one Office 365 -mailbox, one Teams tenant, one GitHub user) attached to one gateway. Triggers, +mailbox, one Teams tenant, one GitHub user) attached to one namespace. Triggers, MCP server configs, and direct-API calls all reference connections by name. This doc covers connection CRUD; for the **consent flow** that turns a freshly @@ -22,17 +22,17 @@ Use the `name` value (lowercase, e.g., `office365`, `sharepointonline`, `teams`, ## Create a connection -PUT to `.../connectorGateways/{gw}/connections/{connection_name}?api-version=2026-05-01-preview`. +PUT to `.../connectorGateways/{namespace}/connections/{connection_name}?api-version=2026-05-01-preview`. ```powershell $connBody = @{ - location = "{location}" # must match the gateway's location + location = "{location}" # must match the namespace's location properties = @{ connectorName = "{connector}" } # e.g. "office365" } | ConvertTo-Json -Compress $tmp = New-TemporaryFile; Set-Content $tmp $connBody az rest --method PUT ` - --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/connections/{conn_name}?api-version=2026-05-01-preview" ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{namespace}/connections/{conn_name}?api-version=2026-05-01-preview" ` --body "@$tmp" Remove-Item $tmp ``` @@ -45,7 +45,7 @@ broken; it just needs the user to complete OAuth. Run [consent.md](consent.md). ```bash az rest --method GET \ - --url ".../connectorGateways/{gw}/connections/{conn}?api-version=2026-05-01-preview" \ + --url ".../connectorGateways/{namespace}/connections/{conn}?api-version=2026-05-01-preview" \ --query "{name:name, status:properties.statuses[0].status, createdBy:properties.createdBy.objectId}" ``` @@ -55,7 +55,7 @@ A connection is ready when `status` is `Connected`. ```bash az rest --method GET \ - --url ".../connectorGateways/{gw}/connections?api-version=2026-05-01-preview" \ + --url ".../connectorGateways/{namespace}/connections?api-version=2026-05-01-preview" \ --query "value[].{name:name, connector:properties.connectorName, status:properties.statuses[0].status}" -o table ``` @@ -78,25 +78,25 @@ A connection's access policies control which Azure AD principals can **use** it (invoke operations via `dynamicInvoke`, subscribe to events for triggers, drive MCP tool calls, etc.). -The gateway's own MI needs an `gateway-acl` policy on every connection it must +The namespace's own MI needs an `namespace-acl` policy on every connection it must subscribe to for connector-event triggers, or invoke for MCP tool calls. -### Get the gateway's MI +### Get the namespace's MI ```bash az rest --method GET \ - --url ".../connectorGateways/{gw}?api-version=2026-05-01-preview" \ + --url ".../connectorGateways/{namespace}?api-version=2026-05-01-preview" \ --query "{principalId:identity.principalId, tenantId:identity.tenantId}" ``` -> If `identity` is null, enable a system-assigned MI on the gateway: +> If `identity` is null, enable a system-assigned MI on the namespace: > ```bash > az rest --method PATCH \ -> --url ".../connectorGateways/{gw}?api-version=2026-05-01-preview" \ +> --url ".../connectorGateways/{namespace}?api-version=2026-05-01-preview" \ > --body '{\"identity\":{\"type\":\"SystemAssigned\"}}' > ``` -### Create the gateway ACL +### Create the namespace ACL ```powershell $aclBody = @{ @@ -111,7 +111,7 @@ $aclBody = @{ $tmp = New-TemporaryFile; Set-Content $tmp $aclBody az rest --method PUT ` - --url ".../connections/{conn}/accessPolicies/gateway-acl?api-version=2026-05-01-preview" ` + --url ".../connections/{conn}/accessPolicies/namespace-acl?api-version=2026-05-01-preview" ` --body "@$tmp" Remove-Item $tmp ``` @@ -134,7 +134,7 @@ $aclBody = @{ # PUT to .../accessPolicies/{policy_name} ``` -Policy names are arbitrary but should be descriptive (`gateway-acl`, +Policy names are arbitrary but should be descriptive (`namespace-acl`, `developer-jane`, `agent-prod-mi`, ...). ### List policies diff --git a/Skills/General/azure-connectorgateway/references/consent.md b/Skills/General/azure-connectornamespace/references/consent.md similarity index 83% rename from Skills/General/azure-connectorgateway/references/consent.md rename to Skills/General/azure-connectornamespace/references/consent.md index 974994e..abbb368 100644 --- a/Skills/General/azure-connectorgateway/references/consent.md +++ b/Skills/General/azure-connectornamespace/references/consent.md @@ -7,7 +7,7 @@ How to generate consent links and authenticate connections. ```powershell # Get the connection's objectId and tenantId first $conn = az rest --method GET ` - --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/connections/{conn}?api-version=2026-05-01-preview" | ConvertFrom-Json + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{namespace}/connections/{conn}?api-version=2026-05-01-preview" | ConvertFrom-Json $objectId = $conn.properties.createdBy.objectId $tenantId = $conn.properties.createdBy.tenantId @@ -25,7 +25,7 @@ $body = @{ $tmpFile = New-TemporaryFile Set-Content $tmpFile $body $link = az rest --method POST ` - --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/connections/{conn}/listConsentLinks?api-version=2026-05-01-preview" ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{namespace}/connections/{conn}/listConsentLinks?api-version=2026-05-01-preview" ` --body "@$tmpFile" --query "value[0].link" -o tsv Remove-Item $tmpFile Start-Process $link @@ -44,7 +44,7 @@ Start-Process $link After user authenticates, verify: ```bash az rest --method GET \ - --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/connections?api-version=2026-05-01-preview" \ + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{namespace}/connections?api-version=2026-05-01-preview" \ --query "value[].{name:name, status:properties.statuses[0].status}" # All should show: Connected. If not, re-consent. ``` diff --git a/Skills/General/azure-connectorgateway/references/direct-api.md b/Skills/General/azure-connectornamespace/references/direct-api.md similarity index 94% rename from Skills/General/azure-connectorgateway/references/direct-api.md rename to Skills/General/azure-connectornamespace/references/direct-api.md index 76f7cd8..c38b29b 100644 --- a/Skills/General/azure-connectorgateway/references/direct-api.md +++ b/Skills/General/azure-connectornamespace/references/direct-api.md @@ -1,7 +1,7 @@ # Direct API Calls via `dynamicInvoke` Call connector operations on demand via `az rest` against the ARM `dynamicInvoke` endpoint. -The gateway injects the stored OAuth credential automatically. **Use `request` format (NOT `parameters`).** +The namespace injects the stored OAuth credential automatically. **Use `request` format (NOT `parameters`).** > **⚠️ Do NOT include `Content-*` headers in the request object** — `Content-Type` > is set by the platform. @@ -102,13 +102,13 @@ Map Swagger `in` field: `path` → URL path, `query` → queries dict, ```bash # Example: Create a file in OneDrive az rest --method POST \ - --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/connections/{conn}/dynamicInvoke?api-version=2026-05-01-preview" \ + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{namespace}/connections/{conn}/dynamicInvoke?api-version=2026-05-01-preview" \ --body '{ "request": { "method": "POST", "path": "/datasets/default/files", "queries": {"folderPath": "/", "name": "hello.txt"}, - "body": "Hello from Connector Gateway!" + "body": "Hello from Connector Namespace!" } }' ``` @@ -130,7 +130,7 @@ $body = @{ $tmp = New-TemporaryFile; Set-Content $tmp $body az rest --method POST ` - --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/connections/{conn}/dynamicInvoke?api-version=2026-05-01-preview" ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{namespace}/connections/{conn}/dynamicInvoke?api-version=2026-05-01-preview" ` --body "@$tmp" Remove-Item $tmp ``` diff --git a/Skills/General/azure-connectorgateway/references/dynamic-values.md b/Skills/General/azure-connectornamespace/references/dynamic-values.md similarity index 98% rename from Skills/General/azure-connectorgateway/references/dynamic-values.md rename to Skills/General/azure-connectornamespace/references/dynamic-values.md index cf5dcd2..d1ab75e 100644 --- a/Skills/General/azure-connectorgateway/references/dynamic-values.md +++ b/Skills/General/azure-connectornamespace/references/dynamic-values.md @@ -119,7 +119,7 @@ The Swagger extension specifies an `operationId` to call and how to extract item 3. Call `dynamicInvoke`: ```powershell az rest --method POST ` - --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/connections/{conn}/dynamicInvoke?api-version=2026-05-01-preview" ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{namespace}/connections/{conn}/dynamicInvoke?api-version=2026-05-01-preview" ` --body '{\"request\":{\"method\":\"GET\",\"path\":\"{resolved_path}\"}}' ` --headers "Content-Type=application/json" -o json ``` @@ -203,7 +203,7 @@ open.operationId = "ListRootFolders" $body = @{request=@{method="GET";path="/datasets/default/folders"}} | ConvertTo-Json -Compress $tmp = New-TemporaryFile; Set-Content $tmp $body az rest --method POST ` - --url ".../{gw}/connections/{conn}/dynamicInvoke?api-version=2026-05-01-preview" ` + --url ".../{namespace}/connections/{conn}/dynamicInvoke?api-version=2026-05-01-preview" ` --body "@$tmp" -o json > $env:TEMP\tree-response.json Remove-Item $tmp ``` @@ -254,7 +254,7 @@ Present to user: $body = @{request=@{method="GET";path=$browsePath}} | ConvertTo-Json -Compress $tmp = New-TemporaryFile; Set-Content $tmp $body az rest --method POST ` - --url ".../{gw}/connections/{conn}/dynamicInvoke?api-version=2026-05-01-preview" ` + --url ".../{namespace}/connections/{conn}/dynamicInvoke?api-version=2026-05-01-preview" ` --body "@$tmp" -o json > $env:TEMP\tree-response.json Remove-Item $tmp ``` diff --git a/Skills/General/azure-connectorgateway/references/gotchas.md b/Skills/General/azure-connectornamespace/references/gotchas.md similarity index 77% rename from Skills/General/azure-connectorgateway/references/gotchas.md rename to Skills/General/azure-connectornamespace/references/gotchas.md index 0e05cd9..3740c6c 100644 --- a/Skills/General/azure-connectorgateway/references/gotchas.md +++ b/Skills/General/azure-connectornamespace/references/gotchas.md @@ -1,14 +1,15 @@ # Gotchas & Troubleshooting -Common issues for the generic connector-gateway skill and their fixes. +Common issues for the generic connector-namespace skill and their fixes. | Issue | Solution | |-------|----------| -| **Trigger not firing** (connector event) | Make sure `gateway-acl` exists on the connection (gateway MI → connection). Without it the subscription silently fails. See [trigger-setup.md](trigger-setup.md) Step 4. | -| **Trigger state is `Enabled` but no runs** | Check `triggerConfigs/{name}/runs` for errors. Most commonly: the gateway couldn't reach `callbackUrl` (4xx/5xx from your endpoint) or authentication mismatch. | +| **"Connector Gateway" vs "Connector Namespace" — which name?** | The resource is now displayed everywhere as **"Connector Namespace"** (matching the Cascade portal rename). The underlying ARM resource type is still `Microsoft.Web/connectorGateways` (URL segment kept for backwards compatibility), and the sandbox-group property `gatewayConnections[]` also keeps its name. Use the new display name in prose; keep the legacy strings in URLs and JSON payloads. | +| **Trigger not firing** (connector event) | Make sure `namespace-acl` exists on the connection (namespace MI → connection). Without it the subscription silently fails. See [trigger-setup.md](trigger-setup.md) Step 4. | +| **Trigger state is `Enabled` but no runs** | Check `triggerConfigs/{name}/runs` for errors. Most commonly: the namespace couldn't reach `callbackUrl` (4xx/5xx from your endpoint) or authentication mismatch. | | **Trigger run shows `Unauthorized` from callback** | Your callback URL's auth doesn't match `notificationDetails.authentication`. Re-check the type/audience/secret. See [notification-authentication.md](notification-authentication.md). | | **`ManagedServiceIdentity` callback with no audience supplied by user** | `audience` is required. **Ask the user**; if they don't provide one, default to `https://management.azure.com/` (real AAD-protected resource). **Never** use the callback URL as the audience. See [notification-authentication.md](notification-authentication.md). | -| **`ManagedServiceIdentity` callback fails with "identity not configured"** | The requested MI isn't attached to the gateway. Either omit `identity` (uses SystemAssigned) or attach the UAMI to the gateway. See [notification-authentication.md](notification-authentication.md). | +| **`ManagedServiceIdentity` callback fails with "identity not configured"** | The requested MI isn't attached to the namespace. Either omit `identity` (uses SystemAssigned) or attach the UAMI to the namespace. See [notification-authentication.md](notification-authentication.md). | | **Connection stuck in `Error` / `Unauthenticated`** | This is the normal pre-consent state. Run [consent.md](consent.md) and have the user complete the browser flow. If still stuck after consent, regenerate the consent link — do NOT retry with different body formats. | | **Consent redirect shows error** | Body MUST be `{"parameters":[{"objectId":"...","tenantId":"...","redirectUrl":"https://microsoft.com","parameterName":"token"}]}`. Get `objectId`/`tenantId` from the connection's `properties.createdBy`. Always open with `Start-Process`. | | **`dynamicInvoke` 400: `parameters` not valid** | Use `{"request": {"method":..., "path":...}}` format. The older `{"parameters": {"operationId":...}}` format is not supported. | @@ -28,7 +29,7 @@ Common issues for the generic connector-gateway skill and their fixes. | **Trigger body wrapper named after the Swagger param (e.g. `requestBody`)** | Triggers always use the literal string `"body"` as the wrapper name, regardless of the Swagger body parameter's own name. (MCP is opposite — it uses the Swagger name.) See [trigger-setup.md](trigger-setup.md) §2b. | | **`callbackTarget` rejected** | That field does not exist in the schema. Use `notificationDetails.callbackUrl`. | | **PowerShell `ConvertFrom-Json` fails on Swagger** | `az rest ... export=true` returns content that piping breaks. Always `-o json > $env:TEMP\swagger.json` first, then read the file. | -| **Cleanup order** | Delete trigger configs and MCP server configs → delete access policies on connections → delete connections → delete gateway. Trigger configs hold subscriptions, so delete them first to avoid orphan webhooks. | -| **403 from `az rest` against ARM** | Your Azure CLI identity doesn't have RBAC on the resource group / gateway. You need at least `Microsoft.Web/connectorGateways/*` (Contributor or a custom role). | -| **PUT to gateway fails with "region not supported"** | Try another region. Common supported regions: `eastus`, `eastus2`, `westus`, `westus2`, `westus3`, `northeurope`, `westeurope`, `australiaeast`, `southeastasia`, `brazilsouth`. | +| **Cleanup order** | Delete trigger configs and MCP server configs → delete access policies on connections → delete connections → delete namespace. Trigger configs hold subscriptions, so delete them first to avoid orphan webhooks. | +| **403 from `az rest` against ARM** | Your Azure CLI identity doesn't have RBAC on the resource group / namespace. You need at least `Microsoft.Web/connectorGateways/*` (Contributor or a custom role). | +| **PUT to namespace fails with "region not supported"** | Try another region. Common supported regions: `eastus`, `eastus2`, `westus`, `westus2`, `westus3`, `northeurope`, `westeurope`, `australiaeast`, `southeastasia`, `brazilsouth`. | | **Provider not registered** | `az provider register --namespace Microsoft.Web` and wait for `Registered` state. | diff --git a/Skills/General/azure-connectorgateway/references/mcp-server-config.md b/Skills/General/azure-connectornamespace/references/mcp-server-config.md similarity index 95% rename from Skills/General/azure-connectorgateway/references/mcp-server-config.md rename to Skills/General/azure-connectornamespace/references/mcp-server-config.md index ade68df..3ede5fb 100644 --- a/Skills/General/azure-connectorgateway/references/mcp-server-config.md +++ b/Skills/General/azure-connectornamespace/references/mcp-server-config.md @@ -1,8 +1,8 @@ # MCP Server Config Expose selected connector operations as Model Context Protocol (MCP) tools at a -gateway-hosted endpoint. Any MCP client (Claude Desktop, VS Code, an Agent SDK) -can connect to the resulting `mcpEndpointUrl` and call the tools — the gateway +namespace-hosted endpoint. Any MCP client (Claude Desktop, VS Code, an Agent SDK) +can connect to the resulting `mcpEndpointUrl` and call the tools — the namespace forwards each tool call to the underlying connector using the stored OAuth credential. @@ -11,7 +11,7 @@ credential. | `kind` | What it is | Use this when | |---|---|---| | `ManagedMcpServer` *(default)* | A **connector-backed** MCP server — you map MCP tools to connector operations. **This is the main pattern for this skill.** | You want an LLM to use Office 365 / Teams / SharePoint / GitHub / ... operations as tools. | -| `HostedMcpServer` | A **container-image-backed** MCP server (e.g., `mcp-sql`). The gateway runs the image. | You want one of the curated containerized MCP servers (currently a small registry). | +| `HostedMcpServer` | A **container-image-backed** MCP server (e.g., `mcp-sql`). The namespace runs the image. | You want one of the curated containerized MCP servers (currently a small registry). | > The rest of this doc focuses on `ManagedMcpServer`. For `HostedMcpServer`, the > shape is the same except `properties.hostedMcpServer.hostedMcpServerId` (e.g., @@ -56,7 +56,7 @@ credential. | `properties.connectors[]` | required | One entry per backing connector | | `connectors[].name` | required | Connector key (e.g., `office365`, `sharepointonline`, `teams`) | | `connectors[].displayName` | required | Human-readable name shown to MCP clients | -| `connectors[].connectionName` | required | Name of the **already-created** connection on the same gateway | +| `connectors[].connectionName` | required | Name of the **already-created** connection on the same namespace | | `connectors[].operations[]` | required | Operations to expose as MCP tools | | `operations[].name` | required | The operation `name` from the connector's `apiOperations` (e.g., `Send_Email_(V2)`) | | `operations[].displayName` | required | Tool name shown to the LLM | @@ -132,7 +132,7 @@ When an operation has a `body` parameter whose schema is a complex object Mirror Swagger faithfully: `type`, `format`, `description`, `enum`, and `required` (as a boolean on each sub-property — not the JSON Schema array form, -this is the Connector-Gateway convention). For nested arrays use `items` with +this is the Connector-Namespace convention). For nested arrays use `items` with its own object schema. To bake parts of the body as fixed values, use the **same wrapper name** in @@ -215,7 +215,7 @@ operations = @( ) ``` -At runtime the gateway merges `userParameters["item"]` (fixed `Status="Open"`) +At runtime the namespace merges `userParameters["item"]` (fixed `Status="Open"`) with whatever the LLM supplied for `agentParameters["item"]` and POSTs the combined object to the connector. @@ -348,7 +348,7 @@ Resolve the schema once at config time, using ```bash # Once dataset + table are chosen, fetch the schema: az rest --method POST \ - --url ".../connectorGateways/{gw}/connections/{conn}/dynamicInvoke?api-version=2026-05-01-preview" \ + --url ".../connectorGateways/{namespace}/connections/{conn}/dynamicInvoke?api-version=2026-05-01-preview" \ --body "@$schemaBody" # (request body uses GetTable's operationId per the operation's # x-ms-dynamic-schema.operationId, with the resolved dataset + table) @@ -423,7 +423,7 @@ $body = @{ $tmp = New-TemporaryFile; Set-Content $tmp $body az rest --method PUT ` - --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/mcpServerConfigs/{mcp_name}?api-version=2026-05-01-preview" ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{namespace}/mcpServerConfigs/{mcp_name}?api-version=2026-05-01-preview" ` --body "@$tmp" Remove-Item $tmp ``` @@ -438,12 +438,12 @@ Remove-Item $tmp ```bash az rest --method GET \ - --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/mcpServerConfigs/{mcp_name}?api-version=2026-05-01-preview" \ + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{namespace}/mcpServerConfigs/{mcp_name}?api-version=2026-05-01-preview" \ --query "properties.mcpEndpointUrl" -o tsv ``` Format looks like: -`https:///subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/mcpServerConfigs/{mcp_name}/mcp` +`https:///subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{namespace}/mcpServerConfigs/{mcp_name}/mcp` Point your MCP client at this URL. @@ -489,7 +489,7 @@ $aclBody = @{ $tmp = New-TemporaryFile; Set-Content $tmp $aclBody # IMPORTANT: the path segment after /accessPolicies/ must equal {caller_object_id}. az rest --method PUT ` - --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/mcpServerConfigs/{mcp_name}/accessPolicies/{caller_object_id}?api-version=2026-05-01-preview" ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{namespace}/mcpServerConfigs/{mcp_name}/accessPolicies/{caller_object_id}?api-version=2026-05-01-preview" ` --body "@$tmp" Remove-Item $tmp ``` @@ -552,7 +552,7 @@ Validation rules (from BPM tests): - `resourceAuth` is required when `authenticationMode` ∈ {`OnBehalfOfUserWithApp`, `AppOnly`}, and forbidden when `authenticationMode` is `NotSpecified` or null. - `resourceAuth` is **only** valid on `HostedMcpServer` — providing it on `ManagedMcpServer` fails validation. -- For `identity.type = SystemAssigned`, the gateway must have a SystemAssigned MI; for user-assigned, the resource ID must be attached. +- For `identity.type = SystemAssigned`, the namespace must have a SystemAssigned MI; for user-assigned, the resource ID must be attached. ## Common mistakes diff --git a/Skills/General/azure-connectorgateway/references/notification-authentication.md b/Skills/General/azure-connectornamespace/references/notification-authentication.md similarity index 80% rename from Skills/General/azure-connectorgateway/references/notification-authentication.md rename to Skills/General/azure-connectornamespace/references/notification-authentication.md index 0fc7fea..18d9d94 100644 --- a/Skills/General/azure-connectorgateway/references/notification-authentication.md +++ b/Skills/General/azure-connectornamespace/references/notification-authentication.md @@ -1,7 +1,7 @@ # Notification (callback) Authentication Reference for `properties.notificationDetails.authentication` on a trigger config. -Pick one type per trigger. If you omit `authentication` entirely, the gateway just +Pick one type per trigger. If you omit `authentication` entirely, the namespace just POSTs to `callbackUrl` with no extra credentials — fine for callback URLs that already embed their own auth (e.g., Logic App SAS in the querystring or a Function App key embedded in the URL). @@ -45,16 +45,16 @@ You provide the full ` `. } ``` -### 4) `ManagedServiceIdentity` — gateway uses its own MI +### 4) `ManagedServiceIdentity` — namespace uses its own MI -The gateway uses its managed identity to call the callback. If `audience` is -provided, the gateway acquires an Entra token for that audience and sends it -as `Authorization: Bearer {token}`. If `audience` is omitted, the gateway +The namespace uses its managed identity to call the callback. If `audience` is +provided, the namespace acquires an Entra token for that audience and sends it +as `Authorization: Bearer {token}`. If `audience` is omitted, the namespace still calls using its MI (no AAD-protected token is minted for a specific resource). > ⚠️ **ASK THE USER for `audience`. If they don't provide one, default to `https://management.azure.com/`.** -> The audience is the AAD-protected resource the gateway acquires a token for. +> The audience is the AAD-protected resource the namespace acquires a token for. > Set it explicitly if the callback is AAD-protected and the user knows the > resource URI of the AAD app guarding it (e.g., `api://my-app`, > `https://graph.microsoft.com/`, `https://vault.azure.net/`, or a custom AAD @@ -62,12 +62,12 @@ resource). > audience — do not default to it. > > **Workflow:** -> 1. Ask the user: "What AAD resource should the gateway acquire a token for? (e.g., the App ID URI of an AAD-registered API you control, `https://graph.microsoft.com/`, etc.) — leave blank to default to `https://management.azure.com/`." +> 1. Ask the user: "What AAD resource should the namespace acquire a token for? (e.g., the App ID URI of an AAD-registered API you control, `https://graph.microsoft.com/`, etc.) — leave blank to default to `https://management.azure.com/`." > 2. If the user provides an audience, use it. -> 3. If the user declines, skips, or doesn't know → use `https://management.azure.com/` as the default. This is a real AAD-protected resource (ARM), so the gateway can mint a valid token for it. The callback may not validate the token, but at least nothing is fabricated. +> 3. If the user declines, skips, or doesn't know → use `https://management.azure.com/` as the default. This is a real AAD-protected resource (ARM), so the namespace can mint a valid token for it. The callback may not validate the token, but at least nothing is fabricated. > 4. If the callback isn't AAD-protected (e.g., a generic webhook), tell the user that MSI auth is providing no real security benefit — they may want `QueryString` or no auth instead. -**System-assigned with default audience** (simplest — gateway must have `identity.type = SystemAssigned`, user didn't specify): +**System-assigned with default audience** (simplest — namespace must have `identity.type = SystemAssigned`, user didn't specify): ```json "authentication": { @@ -85,7 +85,7 @@ resource). } ``` -**User-assigned** (gateway must have the user-assigned identity attached): +**User-assigned** (namespace must have the user-assigned identity attached): ```json "authentication": { @@ -97,10 +97,10 @@ resource). > Validation rules (from BPM `AIGatewayApiTests_NotificationAuthValidation_*`): > - `audience` is required (non-empty) — if the user doesn't provide one, default to `https://management.azure.com/` -> - If `identity` is omitted → gateway must have a SystemAssigned identity -> - If `identity` is set → it must be a valid `/subscriptions/.../userAssignedIdentities/{name}` resource ID AND that identity must already be attached to the gateway +> - If `identity` is omitted → namespace must have a SystemAssigned identity +> - If `identity` is set → it must be a valid `/subscriptions/.../userAssignedIdentities/{name}` resource ID AND that identity must already be attached to the namespace -To attach a user-assigned identity to an existing gateway: +To attach a user-assigned identity to an existing namespace: ```powershell $body = @{ @@ -113,7 +113,7 @@ $body = @{ } | ConvertTo-Json -Depth 5 -Compress $tmp = New-TemporaryFile; Set-Content $tmp $body az rest --method PATCH ` - --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}?api-version=2026-05-01-preview" ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{namespace}?api-version=2026-05-01-preview" ` --body "@$tmp" Remove-Item $tmp ``` @@ -152,7 +152,7 @@ Remove-Item $tmp → omit `authentication`. The URL is self-authenticating. 2. **Function App with a key** but you want to keep the key out of the URL → `QueryString` with `name: "code"`. -3. **Your own API, accepts Entra tokens, gateway is in the same tenant** +3. **Your own API, accepts Entra tokens, namespace is in the same tenant** → `ManagedServiceIdentity` (system-assigned is simplest). 4. **Your own API, accepts Entra tokens, you want fine-grained control / different tenant** → `ActiveDirectoryOAuth` with a dedicated app registration. diff --git a/Skills/General/azure-connectorgateway/references/prerequisites.md b/Skills/General/azure-connectornamespace/references/prerequisites.md similarity index 68% rename from Skills/General/azure-connectorgateway/references/prerequisites.md rename to Skills/General/azure-connectornamespace/references/prerequisites.md index 029f2bc..9b5ee8a 100644 --- a/Skills/General/azure-connectorgateway/references/prerequisites.md +++ b/Skills/General/azure-connectornamespace/references/prerequisites.md @@ -8,9 +8,10 @@ | Azure login | `az account show` | `az login` | | Python 3.8+ *(only if parsing Swagger locally)* | `python --version` | [python.org](https://python.org) | -> **No extensions required.** There are no `az connectorgateway` or `az aigateway` -> commands. Everything goes through `az rest` against ARM. Do not try to install -> a connector-gateway-specific extension. +> **No extensions required.** There are no `az connectornamespace`, +> `az connectorgateway`, or `az aigateway` commands. Everything goes through +> `az rest` against ARM. Do not try to install a connector-namespace-specific +> extension. ## Azure resource providers @@ -24,21 +25,21 @@ az provider show --namespace Microsoft.Web --query "registrationState" -o tsv ## Resource group -Pick or create one. The gateway, connections, trigger configs, and MCP server +Pick or create one. The namespace, connections, trigger configs, and MCP server configs all live inside it: ```bash az group create --name {rg} --location {location} ``` -Common locations where connector gateways are available: `eastus`, `eastus2`, +Common locations where connector namespaces are available: `eastus`, `eastus2`, `westus`, `westus2`, `westus3`, `northeurope`, `westeurope`, `australiaeast`, `southeastasia`, `brazilsouth`. If a PUT fails with a region error, try a different location. ## Optional: identity for `ManagedServiceIdentity` callback auth -If you want the trigger gateway to authenticate to your callback URL with a +If you want the trigger namespace to authenticate to your callback URL with a **user-assigned** managed identity, create it ahead of time and attach it to the -gateway when creating the gateway resource. See +namespace when creating the namespace resource. See [notification-authentication.md](notification-authentication.md) for the `identity` block shape. diff --git a/Skills/General/azure-connectorgateway/references/quickstart.md b/Skills/General/azure-connectornamespace/references/quickstart.md similarity index 73% rename from Skills/General/azure-connectorgateway/references/quickstart.md rename to Skills/General/azure-connectornamespace/references/quickstart.md index d3a88d3..5deb859 100644 --- a/Skills/General/azure-connectorgateway/references/quickstart.md +++ b/Skills/General/azure-connectornamespace/references/quickstart.md @@ -5,24 +5,24 @@ az login az account show --query "{subscription:id, tenant:tenantId}" -o table -# 2. List connector gateways in a resource group +# 2. List connector namespaces in a resource group az rest --method GET \ --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways?api-version=2026-05-01-preview" \ --query "value[].{name:name, location:location, identityType:identity.type, principalId:identity.principalId}" -o table -# 3. List connections on a gateway +# 3. List connections on a namespace az rest --method GET \ - --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/connections?api-version=2026-05-01-preview" \ + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{namespace}/connections?api-version=2026-05-01-preview" \ --query "value[].{name:name, connector:properties.connectorName, status:properties.statuses[0].status}" -o table -# 4. List trigger configs on a gateway +# 4. List trigger configs on a namespace 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" \ + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{namespace}/triggerConfigs?api-version=2026-05-01-preview" \ --query "value[].{name:name, state:properties.state, type:properties.type, connector:properties.connectionDetails.connectorName, op:properties.operationName, callbackUrl:properties.notificationDetails.callbackUrl}" -o table -# 5. List MCP server configs on a gateway +# 5. List MCP server configs on a namespace az rest --method GET \ - --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/mcpServerConfigs?api-version=2026-05-01-preview" \ + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{namespace}/mcpServerConfigs?api-version=2026-05-01-preview" \ --query "value[].{name:name, kind:kind, endpoint:properties.mcpEndpointUrl}" -o table # 6. Discover operations for a connector diff --git a/Skills/General/azure-connectorgateway/references/trigger-flow.md b/Skills/General/azure-connectornamespace/references/trigger-flow.md similarity index 84% rename from Skills/General/azure-connectorgateway/references/trigger-flow.md rename to Skills/General/azure-connectornamespace/references/trigger-flow.md index 10b97db..23295af 100644 --- a/Skills/General/azure-connectorgateway/references/trigger-flow.md +++ b/Skills/General/azure-connectornamespace/references/trigger-flow.md @@ -1,6 +1,6 @@ # Trigger Flow & Lifecycle -How a connector-gateway trigger actually works end-to-end, and the ARM operations +How a connector-namespace trigger actually works end-to-end, and the ARM operations you use to manage its lifecycle. ## Architecture @@ -9,7 +9,7 @@ you use to manage its lifecycle. ``` ┌─────────────────┐ 1. Subscribe ┌──────────────────────┐ -│ Connector SaaS │ ◀──────────────────── │ Connector Gateway │ +│ Connector SaaS │ ◀──────────────────── │ Connector Namespace │ │ (Office 365, │ │ (Microsoft.Web/ │ │ SharePoint, │ 2. Webhook event │ connectorGateways)│ │ Teams, ...) │ ────────────────────▶ │ │ @@ -28,30 +28,30 @@ you use to manage its lifecycle. └────────────────────────┘ ``` -1. When you `PUT` a connector-event trigger config, the gateway uses the +1. When you `PUT` a connector-event trigger config, the namespace uses the underlying **connection** to subscribe to the connector (via the operation's `x-ms-notification-url`). -2. When the SaaS detects the event, it POSTs a webhook to the gateway. -3. The gateway forwards the payload to your `callbackUrl`, applying any +2. When the SaaS detects the event, it POSTs a webhook to the namespace. +3. The namespace forwards the payload to your `callbackUrl`, applying any configured `authentication`. ### Recurrence / SlidingWindow trigger ``` ┌──────────────────────┐ 1. Timer fires ┌────────────────────────┐ -│ Connector Gateway │ ──────────────────▶ │ Your callback URL │ +│ Connector Namespace │ ──────────────────▶ │ Your callback URL │ │ (scheduler) │ │ (any HTTPS endpoint) │ └──────────────────────┘ └────────────────────────┘ ``` No external SaaS, no subscription, no connection needed. Just a timer inside the -gateway that POSTs to your `callbackUrl` per the `recurrence` schedule. +namespace that POSTs to your `callbackUrl` per the `recurrence` schedule. ## Required pieces -| Pattern | Connection? | `gateway-acl` ACL? | Notification auth? | +| Pattern | Connection? | `namespace-acl` ACL? | Notification auth? | |---|---|---|---| -| Connector event | Yes | Yes (gateway MI → connection) | Recommended | +| Connector event | Yes | Yes (namespace MI → connection) | Recommended | | Recurrence | No | No | Recommended | | SlidingWindow | No | No | Recommended | @@ -90,7 +90,7 @@ az rest --method GET --url ".../triggerConfigs/{name}/runs?api-version=2026-05-0 | `status` | Meaning | |---|---| | `Succeeded` | Callback returned 2xx | -| `Failed` | Callback returned 4xx/5xx, or the gateway couldn't reach it. See `error.message`. | +| `Failed` | Callback returned 4xx/5xx, or the namespace couldn't reach it. See `error.message`. | | `Skipped` | Trigger fired but a downstream rule (e.g., dedup) suppressed the callback. | | `Cancelled` | Trigger was disabled while a run was in flight. | diff --git a/Skills/General/azure-connectorgateway/references/trigger-setup.md b/Skills/General/azure-connectornamespace/references/trigger-setup.md similarity index 88% rename from Skills/General/azure-connectorgateway/references/trigger-setup.md rename to Skills/General/azure-connectornamespace/references/trigger-setup.md index a0bb2c7..207f3bb 100644 --- a/Skills/General/azure-connectorgateway/references/trigger-setup.md +++ b/Skills/General/azure-connectornamespace/references/trigger-setup.md @@ -1,6 +1,6 @@ # Trigger Setup -Detailed commands for creating trigger configs on a connector gateway. Triggers +Detailed commands for creating trigger configs on a connector namespace. Triggers fire on either a connector event (new email, new file, ...) or on a schedule (Recurrence / SlidingWindow), and POST a notification to your `callbackUrl`. @@ -8,7 +8,7 @@ fire on either a connector event (new email, new file, ...) or on a schedule | Source | When fires | Requires connection? | |---|---|---| -| **Connector event** | When the connector reports an event (e.g., `OnNewEmailV3`) | Yes — plus `gateway-acl` | +| **Connector event** | When the connector reports an event (e.g., `OnNewEmailV3`) | Yes — plus `namespace-acl` | | **Recurrence** | Every N `Second`/`Minute`/`Hour`/`Day` | No | | **SlidingWindow** | Every N units with a `startTime`/`endTime` window state | No | @@ -173,7 +173,7 @@ parameters = @( ## Step 3: PUT the trigger config -PUT to `.../connectorGateways/{gw}/triggerConfigs/{name}?api-version=2026-05-01-preview`. +PUT to `.../connectorGateways/{namespace}/triggerConfigs/{name}?api-version=2026-05-01-preview`. ### 3A. Connector-event trigger @@ -205,7 +205,7 @@ $triggerBody = @{ $tmp = New-TemporaryFile; Set-Content $tmp $triggerBody az rest --method PUT ` - --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/triggerConfigs/{trigger_name}?api-version=2026-05-01-preview" ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{namespace}/triggerConfigs/{trigger_name}?api-version=2026-05-01-preview" ` --body "@$tmp" Remove-Item $tmp ``` @@ -235,7 +235,7 @@ $triggerBody = @{ $tmp = New-TemporaryFile; Set-Content $tmp $triggerBody az rest --method PUT ` - --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/triggerConfigs/{trigger_name}?api-version=2026-05-01-preview" ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{namespace}/triggerConfigs/{trigger_name}?api-version=2026-05-01-preview" ` --body "@$tmp" Remove-Item $tmp ``` @@ -297,13 +297,13 @@ See [notification-authentication.md](notification-authentication.md) for all 6 a (`QueryString`, `Raw`, `Basic`, `ManagedServiceIdentity`, `ActiveDirectoryOAuth`, `ClientCertificate`) and their exact JSON shapes. -## Step 4: `gateway-acl` (connector-event triggers only) +## Step 4: `namespace-acl` (connector-event triggers only) -The gateway MI must have an access policy on the connection so the gateway can +The namespace MI must have an access policy on the connection so the namespace can subscribe to connector events. Skip this for Recurrence / SlidingWindow. ```powershell -# Get gateway's principalId and tenantId first (from Step 1 of SKILL.md) +# Get namespace's principalId and tenantId first (from Step 1 of SKILL.md) $aclBody = @{ location = "{location}" properties = @{ @@ -315,7 +315,7 @@ $aclBody = @{ } | ConvertTo-Json -Depth 5 -Compress $tmp = New-TemporaryFile; Set-Content $tmp $aclBody az rest --method PUT ` - --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/connections/{conn}/accessPolicies/gateway-acl?api-version=2026-05-01-preview" ` + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{namespace}/connections/{conn}/accessPolicies/namespace-acl?api-version=2026-05-01-preview" ` --body "@$tmp" Remove-Item $tmp ``` @@ -326,7 +326,7 @@ This is independent of the trigger PUT — **run them in parallel**. ```bash az rest --method GET \ - --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/triggerConfigs/{trigger}?api-version=2026-05-01-preview" \ + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{namespace}/triggerConfigs/{trigger}?api-version=2026-05-01-preview" \ --query "{state:properties.state, type:properties.type, callback:properties.notificationDetails.callbackUrl}" # state should be: Enabled ``` @@ -340,26 +340,26 @@ can take 1-2 minutes. ```bash # Disable az rest --method POST \ - --url ".../connectorGateways/{gw}/triggerConfigs/{name}/disable?api-version=2026-05-01-preview" + --url ".../connectorGateways/{namespace}/triggerConfigs/{name}/disable?api-version=2026-05-01-preview" # Enable az rest --method POST \ - --url ".../connectorGateways/{gw}/triggerConfigs/{name}/enable?api-version=2026-05-01-preview" + --url ".../connectorGateways/{namespace}/triggerConfigs/{name}/enable?api-version=2026-05-01-preview" # Delete az rest --method DELETE \ - --url ".../connectorGateways/{gw}/triggerConfigs/{name}?api-version=2026-05-01-preview" + --url ".../connectorGateways/{namespace}/triggerConfigs/{name}?api-version=2026-05-01-preview" ``` ## List recent runs (for debugging) ```bash az rest --method GET \ - --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/triggerConfigs/{name}/runs?api-version=2026-05-01-preview" \ + --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{namespace}/triggerConfigs/{name}/runs?api-version=2026-05-01-preview" \ --query "value[].{name:name, status:properties.status, start:properties.startTime, end:properties.endTime, error:properties.error.message}" -o table ``` -Each run shows whether the gateway succeeded in POSTing to your `callbackUrl` +Each run shows whether the namespace succeeded in POSTing to your `callbackUrl` and what HTTP status came back. ## Common mistakes @@ -372,8 +372,8 @@ and what HTTP status came back. | **Body sub-properties emitted as flat dotted-name params** (e.g. `body.filter.labels`) | The runtime won't unwrap them. Build a nested object and emit it as `{ name = "body"; value = @{ filter = @{ labels = ... } } }`. See §2b. | | **Body wrapper named after the Swagger param** (e.g. `requestBody` for Teams) | Triggers always use the literal string `"body"`. The Swagger body param's own name is irrelevant. (This is opposite to MCP, where the body wrapper preserves the Swagger name.) | | **`ConvertTo-Json -Depth` too shallow** → nested objects coerced to `"System.Collections.Hashtable"` strings | Use `-Depth 20` (or higher) when serializing trigger configs that contain nested body objects. Verify with a GET after the PUT. | -| Forgetting `gateway-acl` on a connector-event trigger | Subscription fails silently — trigger state may show `Enabled` but never fires. Create the ACL. | +| Forgetting `namespace-acl` on a connector-event trigger | Subscription fails silently — trigger state may show `Enabled` but never fires. Create the ACL. | | Inline JSON `--body '...'` in PowerShell | "Unsupported Media Type" — always `@$tmpFile`. See [gotchas.md](gotchas.md). | | `ManagedServiceIdentity` auth without an audience | `audience` is required (non-empty). Ask the user for it; if they don't provide one, default to `https://management.azure.com/`. | | `ManagedServiceIdentity` with callback URL as audience | The token will be meaningless (and rejected if the callback validates AAD tokens). Use a real AAD-protected resource URI — when in doubt, default to `https://management.azure.com/`. See [notification-authentication.md](notification-authentication.md). | -| `ManagedServiceIdentity` referencing a UAMI not on the gateway | Attach it to the gateway first (see [notification-authentication.md](notification-authentication.md)). | +| `ManagedServiceIdentity` referencing a UAMI not on the namespace | Attach it to the namespace first (see [notification-authentication.md](notification-authentication.md)). | diff --git a/Skills/General/azure-connectorgateway/references/tutorial.md b/Skills/General/azure-connectornamespace/references/tutorial.md similarity index 77% rename from Skills/General/azure-connectorgateway/references/tutorial.md rename to Skills/General/azure-connectornamespace/references/tutorial.md index 4d48e84..6985281 100644 --- a/Skills/General/azure-connectorgateway/references/tutorial.md +++ b/Skills/General/azure-connectornamespace/references/tutorial.md @@ -1,6 +1,6 @@ # Tutorial: Two end-to-end walkthroughs -Two minimal real-world scenarios you can run to verify a gateway, a connection, +Two minimal real-world scenarios you can run to verify a namespace, a connection, and a trigger config all work together. - **Tutorial A** (no connection) — recurrence trigger pinging your webhook @@ -14,7 +14,7 @@ Set these variables once at the top of your terminal session: $sub = "" $rg = "tutorial-cg-rg" $location = "eastus" -$gw = "tutorial-gw" +$namespace = "tutorial-ns" $preview = "2026-05-01-preview" ``` @@ -29,34 +29,34 @@ az group create --name $rg --location $location az provider register --namespace Microsoft.Web ``` -### 2. Connector gateway with system-assigned MI +### 2. Connector namespace with system-assigned MI ```powershell -$gwBody = @{ +$namespaceBody = @{ location = $location identity = @{ type = "SystemAssigned" } properties = @{} } | ConvertTo-Json -Depth 4 -Compress -$tmp = New-TemporaryFile; Set-Content $tmp $gwBody +$tmp = New-TemporaryFile; Set-Content $tmp $namespaceBody az rest --method PUT ` - --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$gw?api-version=$preview" ` + --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$namespace?api-version=$preview" ` --body "@$tmp" Remove-Item $tmp # Wait for provisioningState = Succeeded az rest --method GET ` - --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$gw?api-version=$preview" ` + --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$namespace?api-version=$preview" ` --query "{state:properties.provisioningState, mi:identity.principalId}" ``` -Save the `mi` value as `$gwMi`. Also capture `$gwTenant = (az account show --query tenantId -o tsv)`. +Save the `mi` value as `$namespaceMi`. Also capture `$namespaceTenant = (az account show --query tenantId -o tsv)`. --- ## Tutorial A — Recurrence trigger to your webhook -**Goal:** every 5 minutes, the gateway POSTs to `https://your-webhook.example.com/ping`. +**Goal:** every 5 minutes, the namespace POSTs to `https://your-webhook.example.com/ping`. ### A.1 Create the trigger config @@ -78,7 +78,7 @@ $triggerBody = @{ $tmp = New-TemporaryFile; Set-Content $tmp $triggerBody az rest --method PUT ` - --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$gw/triggerConfigs/tut-recur?api-version=$preview" ` + --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$namespace/triggerConfigs/tut-recur?api-version=$preview" ` --body "@$tmp" Remove-Item $tmp ``` @@ -87,7 +87,7 @@ Remove-Item $tmp ```bash az rest --method GET \ - --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$gw/triggerConfigs/tut-recur?api-version=$preview" \ + --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$namespace/triggerConfigs/tut-recur?api-version=$preview" \ --query "{state:properties.state, callback:properties.notificationDetails.callbackUrl}" ``` @@ -95,7 +95,7 @@ Within 5 minutes, check `triggerConfigs/tut-recur/runs`: ```bash az rest --method GET \ - --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$gw/triggerConfigs/tut-recur/runs?api-version=$preview" \ + --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$namespace/triggerConfigs/tut-recur/runs?api-version=$preview" \ --query "value[].{status:properties.status, start:properties.startTime, end:properties.endTime}" -o table ``` @@ -125,14 +125,14 @@ $triggerBody = @{ # Re-PUT with the same URL ``` -The gateway will mint a token with its system-assigned MI for `audience` and +The namespace will mint a token with its system-assigned MI for `audience` and attach it as `Authorization: Bearer ...`. ### A.4 Cleanup (Tutorial A only) ```bash az rest --method DELETE \ - --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$gw/triggerConfigs/tut-recur?api-version=$preview" + --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$namespace/triggerConfigs/tut-recur?api-version=$preview" ``` --- @@ -140,7 +140,7 @@ az rest --method DELETE \ ## Tutorial B — Connector event trigger from SharePoint **Goal:** when a new file appears in `Shared Documents` of a SharePoint site, the -gateway POSTs the file metadata to your webhook. +namespace POSTs the file metadata to your webhook. ### B.1 Create a SharePoint connection @@ -152,7 +152,7 @@ $connBody = @{ $tmp = New-TemporaryFile; Set-Content $tmp $connBody az rest --method PUT ` - --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$gw/connections/sp-conn?api-version=$preview" ` + --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$namespace/connections/sp-conn?api-version=$preview" ` --body "@$tmp" Remove-Item $tmp ``` @@ -167,11 +167,11 @@ Confirm `Connected`: ```bash az rest --method GET \ - --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$gw/connections/sp-conn?api-version=$preview" \ + --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$namespace/connections/sp-conn?api-version=$preview" \ --query "properties.statuses[0].status" ``` -### B.3 Grant the gateway MI access to the connection +### B.3 Grant the namespace MI access to the connection ```powershell $aclBody = @{ @@ -179,14 +179,14 @@ $aclBody = @{ properties = @{ principal = @{ type = "ActiveDirectory" - identity = @{ objectId = $gwMi; tenantId = $gwTenant } + identity = @{ objectId = $namespaceMi; tenantId = $namespaceTenant } } } } | ConvertTo-Json -Depth 5 -Compress $tmp = New-TemporaryFile; Set-Content $tmp $aclBody az rest --method PUT ` - --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$gw/connections/sp-conn/accessPolicies/gateway-acl?api-version=$preview" ` + --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$namespace/connections/sp-conn/accessPolicies/namespace-acl?api-version=$preview" ` --body "@$tmp" Remove-Item $tmp ``` @@ -236,7 +236,7 @@ $triggerBody = @{ $tmp = New-TemporaryFile; Set-Content $tmp $triggerBody az rest --method PUT ` - --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$gw/triggerConfigs/tut-spnewfile?api-version=$preview" ` + --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$namespace/triggerConfigs/tut-spnewfile?api-version=$preview" ` --body "@$tmp" Remove-Item $tmp ``` @@ -257,7 +257,7 @@ az rest --method GET --url ".../triggerConfigs/tut-spnewfile/runs?api-version=$p ```bash az rest --method DELETE --url ".../triggerConfigs/tut-spnewfile?api-version=$preview" -az rest --method DELETE --url ".../connections/sp-conn/accessPolicies/gateway-acl?api-version=$preview" +az rest --method DELETE --url ".../connections/sp-conn/accessPolicies/namespace-acl?api-version=$preview" az rest --method DELETE --url ".../connections/sp-conn?api-version=$preview" ``` @@ -267,7 +267,7 @@ az rest --method DELETE --url ".../connections/sp-conn?api-version=$preview" ```bash az rest --method DELETE \ - --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$gw?api-version=$preview" + --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/connectorGateways/$namespace?api-version=$preview" az group delete --name $rg --yes --no-wait ``` diff --git a/Skills/General/azure-connectorgateway/version.json b/Skills/General/azure-connectornamespace/version.json similarity index 100% rename from Skills/General/azure-connectorgateway/version.json rename to Skills/General/azure-connectornamespace/version.json From 5461a17ccd4216dd6922dd10f8bdfeaa6021df84 Mon Sep 17 00:00:00 2001 From: apranaseth Date: Sun, 31 May 2026 13:13:59 -0700 Subject: [PATCH 10/14] Fixed the skill --- Skills/Sandbox/azure-connectorgateway/SKILL.md | 4 ++-- Skills/Sandbox/azure-connectorgateway/references/gotchas.md | 2 +- .../azure-connectorgateway/references/trigger-setup.md | 5 +++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Skills/Sandbox/azure-connectorgateway/SKILL.md b/Skills/Sandbox/azure-connectorgateway/SKILL.md index 8e97828..4e7c53a 100644 --- a/Skills/Sandbox/azure-connectorgateway/SKILL.md +++ b/Skills/Sandbox/azure-connectorgateway/SKILL.md @@ -217,8 +217,8 @@ Ask the user: | Target | Callback URL | Notes | |--------|-------------|-------| -| ShellCommand | `.../executeShellCommand` | Auto-resumes sandbox; needs RBAC `c24cf47c-...` on sandbox group | -| ExecuteCommand | `.../executeCommand` | Same as above, no shell interpretation | +| ShellCommand | `https://management.{region}.azuredevcompute.io/.../executeShellCommand` | Auto-resumes sandbox; needs RBAC `c24cf47c-...` on sandbox group. **Regional host required** (unregional → 404 GlobalSandboxNotFound). | +| ExecuteCommand | `https://management.{region}.azuredevcompute.io/.../executeCommand` | Same as above, no shell interpretation. **Regional host required.** | | InvokePort | `https://{id}--{port}.proxy.azuredevcompute.io/...` | Sandbox must be running; needs port auth | After trigger creation → deploy handler. See [handler-guide.md](references/handler-guide.md). diff --git a/Skills/Sandbox/azure-connectorgateway/references/gotchas.md b/Skills/Sandbox/azure-connectorgateway/references/gotchas.md index eb3ceae..4eee65f 100644 --- a/Skills/Sandbox/azure-connectorgateway/references/gotchas.md +++ b/Skills/Sandbox/azure-connectorgateway/references/gotchas.md @@ -28,5 +28,5 @@ Common issues and their solutions. | `az rest --body` "Unsupported Media Type" | Inline JSON strings get mangled by PowerShell. Always use `@$tmpFile` pattern: write body to temp file, pass `--body "@$tmpFile"` | | `gatewayConnections[]` PATCH clobbered other entries | Always GET-merge-PATCH on the sandbox group — never PUT or unconditionally PATCH `gatewayConnections` with just your entry. Match existing entries case-insensitively by `resourceId.toLower()`. See [gateway-connections.md](gateway-connections.md) Step 4. | | `aca sandbox create` ignores connection wiring | The aca CLI does not yet expose `--gateway-connection`. Use the data-plane PUT via `az rest` with `gatewayConnections: [{resourceId}]` in the body. See [gateway-connections.md](gateway-connections.md) Step 5. | -| Sandbox data-plane endpoint 404s | The endpoint is **regional** — `https://management.{region}.azuredevcompute.io/...`, NOT `https://management.azuredevcompute.io/...` | +| Sandbox data-plane endpoint 404s | The endpoint is **regional** — `https://management.{region}.azuredevcompute.io/...`, NOT `https://management.azuredevcompute.io/...`. This applies to **ALL** sandbox data-plane URLs, including trigger `callbackUrl` for ShellCommand/ExecuteCommand. Symptom: trigger fires but callback fails with `{"title":"GlobalSandboxNotFound","status":404,...}`. | | aca CLI install 404 | GitHub releases URL requires auth. Use `gh release download` (needs `gh auth login`) or ask user for the .tgz path | diff --git a/Skills/Sandbox/azure-connectorgateway/references/trigger-setup.md b/Skills/Sandbox/azure-connectorgateway/references/trigger-setup.md index 0294b5c..ee3201d 100644 --- a/Skills/Sandbox/azure-connectorgateway/references/trigger-setup.md +++ b/Skills/Sandbox/azure-connectorgateway/references/trigger-setup.md @@ -115,8 +115,9 @@ Ask for callback type: ```powershell # Build trigger config body — ShellCommand example -# First construct the callback URL: -$callbackUrl = "https://management.azuredevcompute.io/subscriptions/{sub}/resourceGroups/{rg}/sandboxGroups/{sandbox_group}/sandboxes/{sandbox_id}/executeShellCommand?api-version=2026-02-01-preview" +# First construct the callback URL — MUST use the regional data-plane host +# (the unregional `management.azuredevcompute.io` returns 404 GlobalSandboxNotFound): +$callbackUrl = "https://management.{region}.azuredevcompute.io/subscriptions/{sub}/resourceGroups/{rg}/sandboxGroups/{sandbox_group}/sandboxes/{sandbox_id}/executeShellCommand?api-version=2026-02-01-preview" $triggerBody = @{ properties = @{ From 783113ce473b491331ad437c19757172727e4569 Mon Sep 17 00:00:00 2001 From: apranaseth Date: Sun, 31 May 2026 13:22:47 -0700 Subject: [PATCH 11/14] Adressed comments --- .claude-plugin/plugin.json | 3 ++- .plugin/plugin.json | 3 ++- README.md | 1 + .../references/notification-authentication.md | 11 ++++++----- .../azure-connectorgateway/references/quickstart.md | 2 +- marketplace.json | 9 +++++++++ 6 files changed, 21 insertions(+), 8 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 6ebab3a..41ae84a 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -18,6 +18,7 @@ "sandbox" ], "skills": [ - "./Skills/Sandbox/azure-connectorgateway" + "./Skills/Sandbox/azure-connectorgateway", + "./Skills/General/azure-connectornamespace" ] } diff --git a/.plugin/plugin.json b/.plugin/plugin.json index 6ebab3a..41ae84a 100644 --- a/.plugin/plugin.json +++ b/.plugin/plugin.json @@ -18,6 +18,7 @@ "sandbox" ], "skills": [ - "./Skills/Sandbox/azure-connectorgateway" + "./Skills/Sandbox/azure-connectorgateway", + "./Skills/General/azure-connectornamespace" ] } diff --git a/README.md b/README.md index 85f7451..a613414 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ claude plugin add Azure/Connectors | Skill | Description | |-------|-------------| | [azure-connectorgateway](Skills/Sandbox/azure-connectorgateway/SKILL.md) | Manage connector gateways, connections, and triggers — wire external services (Office 365, Teams, Forms, SharePoint, OneDrive, GitHub, Azure Blob) to sandbox apps via event-driven triggers or direct API calls using connection runtime URLs. | +| [azure-connectornamespace](Skills/General/azure-connectornamespace/SKILL.md) | Generic, sandbox-agnostic counterpart — manage connector namespaces, connections, trigger configs that POST to any HTTP(S) callback (Function App, Logic App, App Service, custom webhook), and MCP server configs that expose connector operations as MCP tools. | See [`Skills/Sandbox/README.md`](Skills/Sandbox/README.md) for more detail. diff --git a/Skills/General/azure-connectornamespace/references/notification-authentication.md b/Skills/General/azure-connectornamespace/references/notification-authentication.md index 18d9d94..73a60d5 100644 --- a/Skills/General/azure-connectornamespace/references/notification-authentication.md +++ b/Skills/General/azure-connectornamespace/references/notification-authentication.md @@ -47,11 +47,12 @@ You provide the full ` `. ### 4) `ManagedServiceIdentity` — namespace uses its own MI -The namespace uses its managed identity to call the callback. If `audience` is -provided, the namespace acquires an Entra token for that audience and sends it -as `Authorization: Bearer {token}`. If `audience` is omitted, the namespace -still calls using its MI (no AAD-protected token is minted for a specific -resource). +The namespace uses its managed identity to call the callback. `audience` is +**required** (see the validation rules below): the namespace acquires an Entra +token for that audience and sends it as `Authorization: Bearer {token}`. If +the user doesn't supply one, default to `https://management.azure.com/` rather +than omitting the field — an empty `audience` produces an invalid auth body +that the namespace rejects. > ⚠️ **ASK THE USER for `audience`. If they don't provide one, default to `https://management.azure.com/`.** > The audience is the AAD-protected resource the namespace acquires a token for. diff --git a/Skills/Sandbox/azure-connectorgateway/references/quickstart.md b/Skills/Sandbox/azure-connectorgateway/references/quickstart.md index 4263da1..e1dab0e 100644 --- a/Skills/Sandbox/azure-connectorgateway/references/quickstart.md +++ b/Skills/Sandbox/azure-connectorgateway/references/quickstart.md @@ -20,4 +20,4 @@ az rest --method GET \ --url "https://management.azure.com/subscriptions/{sub}/providers/Microsoft.Web/locations/{location}/managedApis/office365/apiOperations?api-version=2016-06-01" ``` -Or open the lab notebook: `labs/02-trigger-getting-started/01-trigger-getting-started.ipynb` +For an end-to-end walkthrough, see [`tutorial-welcome-emailer.md`](tutorial-welcome-emailer.md). diff --git a/marketplace.json b/marketplace.json index 4ded426..c265c91 100644 --- a/marketplace.json +++ b/marketplace.json @@ -17,6 +17,15 @@ "skills": [ "./Skills/Sandbox/azure-connectorgateway" ] + }, + { + "name": "azure-connectornamespace", + "source": ".", + "description": "Azure Connector Namespace (generic) — manage namespaces, connections, trigger configs that POST to any HTTP(S) callback (Function App, Logic App, App Service, custom webhook), and MCP server configs exposing connector operations as MCP tools. Sandbox-agnostic counterpart to azure-connectorgateway.", + "version": "0.1.0", + "skills": [ + "./Skills/General/azure-connectornamespace" + ] } ] } From 4e963dfa50178d081322743c03ec21087dd5200f Mon Sep 17 00:00:00 2001 From: apranaseth Date: Mon, 1 Jun 2026 11:16:18 -0700 Subject: [PATCH 12/14] Skils updates --- .claude-plugin/plugin.json | 24 ------------ .github/ISSUE_TEMPLATE/portal_bug.yml | 4 +- .plugin/plugin.json | 24 ------------ README.md | 8 ++-- Skills/Sandbox/README.md | 35 ------------------ marketplace.json | 16 ++------ plugin/.claude-plugin/plugin.json | 25 +++++++++++++ plugin/.plugin/plugin.json | 25 +++++++++++++ plugin/skills/aca-sandboxes/README.md | 37 +++++++++++++++++++ .../skills/aca-sandboxes}/SKILL.md | 29 ++++++++------- .../aca-sandboxes}/references/consent.md | 0 .../aca-sandboxes}/references/direct-api.md | 2 +- .../references/dynamic-values.md | 0 .../references/gateway-connections.md | 4 +- .../aca-sandboxes}/references/gotchas.md | 0 .../references/handler-guide.md | 0 .../references/prerequisites.md | 0 .../aca-sandboxes}/references/quickstart.md | 0 .../references/runtime-url-examples.md | 0 .../aca-sandboxes}/references/trigger-flow.md | 4 +- .../references/trigger-setup.md | 2 +- .../references/tutorial-welcome-emailer.md | 4 +- .../scripts/trigger-getting-started.py | 4 +- .../skills/aca-sandboxes}/version.json | 0 plugin/skills/connectors/README.md | 36 ++++++++++++++++++ .../skills/connectors}/SKILL.md | 4 +- .../connectors}/references/connections.md | 0 .../skills/connectors}/references/consent.md | 0 .../connectors}/references/direct-api.md | 0 .../connectors}/references/dynamic-values.md | 0 .../skills/connectors}/references/gotchas.md | 2 +- .../references/mcp-server-config.md | 0 .../references/notification-authentication.md | 0 .../connectors}/references/prerequisites.md | 0 .../connectors}/references/quickstart.md | 0 .../connectors}/references/trigger-flow.md | 0 .../connectors}/references/trigger-setup.md | 0 .../skills/connectors}/references/tutorial.md | 0 .../skills/connectors}/version.json | 0 39 files changed, 161 insertions(+), 128 deletions(-) delete mode 100644 .claude-plugin/plugin.json delete mode 100644 .plugin/plugin.json delete mode 100644 Skills/Sandbox/README.md create mode 100644 plugin/.claude-plugin/plugin.json create mode 100644 plugin/.plugin/plugin.json create mode 100644 plugin/skills/aca-sandboxes/README.md rename {Skills/Sandbox/azure-connectorgateway => plugin/skills/aca-sandboxes}/SKILL.md (94%) rename {Skills/Sandbox/azure-connectorgateway => plugin/skills/aca-sandboxes}/references/consent.md (100%) rename {Skills/Sandbox/azure-connectorgateway => plugin/skills/aca-sandboxes}/references/direct-api.md (99%) rename {Skills/Sandbox/azure-connectorgateway => plugin/skills/aca-sandboxes}/references/dynamic-values.md (100%) rename {Skills/Sandbox/azure-connectorgateway => plugin/skills/aca-sandboxes}/references/gateway-connections.md (98%) rename {Skills/Sandbox/azure-connectorgateway => plugin/skills/aca-sandboxes}/references/gotchas.md (100%) rename {Skills/Sandbox/azure-connectorgateway => plugin/skills/aca-sandboxes}/references/handler-guide.md (100%) rename {Skills/Sandbox/azure-connectorgateway => plugin/skills/aca-sandboxes}/references/prerequisites.md (100%) rename {Skills/Sandbox/azure-connectorgateway => plugin/skills/aca-sandboxes}/references/quickstart.md (100%) rename {Skills/Sandbox/azure-connectorgateway => plugin/skills/aca-sandboxes}/references/runtime-url-examples.md (100%) rename {Skills/Sandbox/azure-connectorgateway => plugin/skills/aca-sandboxes}/references/trigger-flow.md (98%) rename {Skills/Sandbox/azure-connectorgateway => plugin/skills/aca-sandboxes}/references/trigger-setup.md (99%) rename {Skills/Sandbox/azure-connectorgateway => plugin/skills/aca-sandboxes}/references/tutorial-welcome-emailer.md (99%) rename {Skills/Sandbox/azure-connectorgateway => plugin/skills/aca-sandboxes}/scripts/trigger-getting-started.py (99%) rename {Skills/Sandbox/azure-connectorgateway => plugin/skills/aca-sandboxes}/version.json (100%) create mode 100644 plugin/skills/connectors/README.md rename {Skills/General/azure-connectornamespace => plugin/skills/connectors}/SKILL.md (99%) rename {Skills/General/azure-connectornamespace => plugin/skills/connectors}/references/connections.md (100%) rename {Skills/General/azure-connectornamespace => plugin/skills/connectors}/references/consent.md (100%) rename {Skills/General/azure-connectornamespace => plugin/skills/connectors}/references/direct-api.md (100%) rename {Skills/General/azure-connectornamespace => plugin/skills/connectors}/references/dynamic-values.md (100%) rename {Skills/General/azure-connectornamespace => plugin/skills/connectors}/references/gotchas.md (93%) rename {Skills/General/azure-connectornamespace => plugin/skills/connectors}/references/mcp-server-config.md (100%) rename {Skills/General/azure-connectornamespace => plugin/skills/connectors}/references/notification-authentication.md (100%) rename {Skills/General/azure-connectornamespace => plugin/skills/connectors}/references/prerequisites.md (100%) rename {Skills/General/azure-connectornamespace => plugin/skills/connectors}/references/quickstart.md (100%) rename {Skills/General/azure-connectornamespace => plugin/skills/connectors}/references/trigger-flow.md (100%) rename {Skills/General/azure-connectornamespace => plugin/skills/connectors}/references/trigger-setup.md (100%) rename {Skills/General/azure-connectornamespace => plugin/skills/connectors}/references/tutorial.md (100%) rename {Skills/General/azure-connectornamespace => plugin/skills/connectors}/version.json (100%) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json deleted file mode 100644 index 41ae84a..0000000 --- a/.claude-plugin/plugin.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "azure-connectorgateway", - "description": "Azure Connector Gateway — manage gateways, connections, and triggers. Connects external services (Office 365, Teams, Microsoft Forms, SharePoint, OneDrive, GitHub, Azure Blob) to sandbox apps via event-driven triggers or direct API calls using connection runtime URLs.", - "version": "0.1.0", - "author": { - "name": "Microsoft", - "url": "https://www.microsoft.com" - }, - "homepage": "https://github.com/Azure/Connectors", - "repository": "https://github.com/Azure/Connectors", - "license": "MIT", - "keywords": [ - "azure", - "connectors", - "connector-gateway", - "triggers", - "ai-agents", - "sandbox" - ], - "skills": [ - "./Skills/Sandbox/azure-connectorgateway", - "./Skills/General/azure-connectornamespace" - ] -} diff --git a/.github/ISSUE_TEMPLATE/portal_bug.yml b/.github/ISSUE_TEMPLATE/portal_bug.yml index 1074b3b..e33019f 100644 --- a/.github/ISSUE_TEMPLATE/portal_bug.yml +++ b/.github/ISSUE_TEMPLATE/portal_bug.yml @@ -13,7 +13,7 @@ body: attributes: label: One-line summary description: What broke, in a sentence. - placeholder: e.g. "Connector gateway list shows duplicate rows after delete" + placeholder: e.g. "Connector list shows duplicate rows after delete" validations: required: true @@ -23,7 +23,7 @@ body: label: Area of the portal default: 6 options: - - Connector gateways + - Connectors - MCP servers - Connections - Triggers diff --git a/.plugin/plugin.json b/.plugin/plugin.json deleted file mode 100644 index 41ae84a..0000000 --- a/.plugin/plugin.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "azure-connectorgateway", - "description": "Azure Connector Gateway — manage gateways, connections, and triggers. Connects external services (Office 365, Teams, Microsoft Forms, SharePoint, OneDrive, GitHub, Azure Blob) to sandbox apps via event-driven triggers or direct API calls using connection runtime URLs.", - "version": "0.1.0", - "author": { - "name": "Microsoft", - "url": "https://www.microsoft.com" - }, - "homepage": "https://github.com/Azure/Connectors", - "repository": "https://github.com/Azure/Connectors", - "license": "MIT", - "keywords": [ - "azure", - "connectors", - "connector-gateway", - "triggers", - "ai-agents", - "sandbox" - ], - "skills": [ - "./Skills/Sandbox/azure-connectorgateway", - "./Skills/General/azure-connectornamespace" - ] -} diff --git a/README.md b/README.md index a613414..ed57075 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Or install via the marketplace (useful when more plugins are added later): ```bash /plugin marketplace add Azure/Connectors -/plugin install azure-connectorgateway@Azure-Connectors +/plugin install azure-connectornamespace@Azure-Connectors ``` ### Claude Code @@ -41,10 +41,10 @@ claude plugin add Azure/Connectors | Skill | Description | |-------|-------------| -| [azure-connectorgateway](Skills/Sandbox/azure-connectorgateway/SKILL.md) | Manage connector gateways, connections, and triggers — wire external services (Office 365, Teams, Forms, SharePoint, OneDrive, GitHub, Azure Blob) to sandbox apps via event-driven triggers or direct API calls using connection runtime URLs. | -| [azure-connectornamespace](Skills/General/azure-connectornamespace/SKILL.md) | Generic, sandbox-agnostic counterpart — manage connector namespaces, connections, trigger configs that POST to any HTTP(S) callback (Function App, Logic App, App Service, custom webhook), and MCP server configs that expose connector operations as MCP tools. | +| [azure-connectornamespace](plugin/skills/connectors/SKILL.md) | Generic, callback-agnostic — manage connector namespaces, connections, trigger configs that POST to any HTTP(S) callback (Function App, Logic App, App Service, custom webhook), and MCP server configs that expose connector operations as MCP tools. | +| [azure-connectornamespace-aca-sandbox](plugin/skills/aca-sandboxes/SKILL.md) | ACA-sandbox edition — manage connector namespaces, connections, and triggers; wire external services (Office 365, Teams, Forms, SharePoint, OneDrive, GitHub, Azure Blob) to Azure Container Apps sandbox apps via event-driven triggers or direct API calls using connection runtime URLs. | -See [`Skills/Sandbox/README.md`](Skills/Sandbox/README.md) for more detail. +See [`plugin/skills/connectors/README.md`](plugin/skills/connectors/README.md) for more detail. ## Resources diff --git a/Skills/Sandbox/README.md b/Skills/Sandbox/README.md deleted file mode 100644 index 3209d88..0000000 --- a/Skills/Sandbox/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# Azure Connectors Skills - -Skills for [Azure Connector Namespaces](https://connectors.azure.com) — install -once, drive connector gateways, connections, and triggers from natural language -in your coding agent. - -> The plugin descriptors live at the repo root (`.plugin/plugin.json`, -> `.claude-plugin/plugin.json`, and `marketplace.json`). Skill source lives in -> this folder. - -## Install - -### GitHub Copilot CLI - -```bash -# Direct install (single command) -/plugin install Azure/Connectors - -# Or via marketplace (useful when this repo adds more skills) -/plugin marketplace add Azure/Connectors -/plugin install azure-connectorgateway@Azure-Connectors -``` - -### Claude Code - -```bash -claude plugin add Azure/Connectors -``` - -## Skills - -| Skill | Description | -|-------|-------------| -| [azure-connectorgateway](azure-connectorgateway/SKILL.md) | Manage connector gateways, connections, and triggers — wire external services (Office 365, Teams, Forms, SharePoint, OneDrive, GitHub, Azure Blob) to sandbox apps via event-driven triggers or direct API calls using connection runtime URLs. | - diff --git a/marketplace.json b/marketplace.json index c265c91..9a70aba 100644 --- a/marketplace.json +++ b/marketplace.json @@ -9,22 +9,14 @@ "url": "https://github.com/Azure/Connectors" }, "plugins": [ - { - "name": "azure-connectorgateway", - "source": ".", - "description": "Azure Connector Gateway — manage gateways, connections, and triggers. Connects external services (Office 365, Teams, Microsoft Forms, SharePoint, OneDrive, GitHub, Azure Blob) to sandbox apps via event-driven triggers or direct API calls using connection runtime URLs.", - "version": "0.1.0", - "skills": [ - "./Skills/Sandbox/azure-connectorgateway" - ] - }, { "name": "azure-connectornamespace", - "source": ".", - "description": "Azure Connector Namespace (generic) — manage namespaces, connections, trigger configs that POST to any HTTP(S) callback (Function App, Logic App, App Service, custom webhook), and MCP server configs exposing connector operations as MCP tools. Sandbox-agnostic counterpart to azure-connectorgateway.", + "source": "./plugin", + "description": "Azure Connector Namespace — manage connector namespaces, connections, triggers, MCP server configs, and ACA sandbox wiring. Bundles a generic (callback-to-any-HTTPS-endpoint) skill plus an ACA-sandbox edition that wires triggers and direct API calls into Azure Container Apps sandboxes.", "version": "0.1.0", "skills": [ - "./Skills/General/azure-connectornamespace" + "./skills/connectors", + "./skills/aca-sandboxes" ] } ] diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json new file mode 100644 index 0000000..02c0aba --- /dev/null +++ b/plugin/.claude-plugin/plugin.json @@ -0,0 +1,25 @@ +{ + "name": "azure-connectornamespace", + "description": "Azure Connector Namespace — manage connector namespaces, connections, triggers, MCP server configs, and ACA sandbox wiring. Connects external services (Office 365, Teams, Microsoft Forms, SharePoint, OneDrive, GitHub, Azure Blob) to any HTTP(S) callback (Function App, Logic App, App Service, custom webhook) or to Azure Container Apps sandboxes via event-driven triggers or direct API calls using connection runtime URLs.", + "version": "0.1.0", + "author": { + "name": "Microsoft", + "url": "https://www.microsoft.com" + }, + "homepage": "https://github.com/Azure/Connectors", + "repository": "https://github.com/Azure/Connectors", + "license": "MIT", + "keywords": [ + "azure", + "connectors", + "connector-namespace", + "triggers", + "mcp", + "ai-agents", + "sandbox" + ], + "skills": [ + "./skills/aca-sandboxes", + "./skills/connectors" + ] +} diff --git a/plugin/.plugin/plugin.json b/plugin/.plugin/plugin.json new file mode 100644 index 0000000..02c0aba --- /dev/null +++ b/plugin/.plugin/plugin.json @@ -0,0 +1,25 @@ +{ + "name": "azure-connectornamespace", + "description": "Azure Connector Namespace — manage connector namespaces, connections, triggers, MCP server configs, and ACA sandbox wiring. Connects external services (Office 365, Teams, Microsoft Forms, SharePoint, OneDrive, GitHub, Azure Blob) to any HTTP(S) callback (Function App, Logic App, App Service, custom webhook) or to Azure Container Apps sandboxes via event-driven triggers or direct API calls using connection runtime URLs.", + "version": "0.1.0", + "author": { + "name": "Microsoft", + "url": "https://www.microsoft.com" + }, + "homepage": "https://github.com/Azure/Connectors", + "repository": "https://github.com/Azure/Connectors", + "license": "MIT", + "keywords": [ + "azure", + "connectors", + "connector-namespace", + "triggers", + "mcp", + "ai-agents", + "sandbox" + ], + "skills": [ + "./skills/aca-sandboxes", + "./skills/connectors" + ] +} diff --git a/plugin/skills/aca-sandboxes/README.md b/plugin/skills/aca-sandboxes/README.md new file mode 100644 index 0000000..7c48215 --- /dev/null +++ b/plugin/skills/aca-sandboxes/README.md @@ -0,0 +1,37 @@ +# Azure Connectors Skills + +Skills for [Azure Connector Namespaces](https://connectors.azure.com) — install +once, drive connectors, connections, and triggers from natural language +in your coding agent. + +> The plugin descriptors live under `plugin/` (`plugin/.plugin/plugin.json` and +> `plugin/.claude-plugin/plugin.json`); the marketplace manifest +> (`marketplace.json`) lives at the repo root. Skill sources live in +> sibling folders under `plugin/skills/`. + +## Install + +### GitHub Copilot CLI + +```bash +# Direct install (single command) +/plugin install Azure/Connectors + +# Or via marketplace (useful when this repo adds more skills) +/plugin marketplace add Azure/Connectors +/plugin install azure-connectornamespace@Azure-Connectors +``` + +### Claude Code + +```bash +claude plugin add Azure/Connectors +``` + +## Skills + +| Skill | Description | +|-------|-------------| +| [azure-connectornamespace-aca-sandbox](SKILL.md) | ACA-sandbox edition — manage connector namespaces, connections, and triggers; wire external services (Office 365, Teams, Forms, SharePoint, OneDrive, GitHub, Azure Blob) to Azure Container Apps sandbox apps via event-driven triggers or direct API calls using connection runtime URLs. | +| [azure-connectornamespace](../connectors/SKILL.md) | Generic, callback-agnostic counterpart — manage connector namespaces, connections, trigger configs that POST to any HTTP(S) callback (Function App, Logic App, App Service, custom webhook), and MCP server configs that expose connector operations as MCP tools. | + diff --git a/Skills/Sandbox/azure-connectorgateway/SKILL.md b/plugin/skills/aca-sandboxes/SKILL.md similarity index 94% rename from Skills/Sandbox/azure-connectorgateway/SKILL.md rename to plugin/skills/aca-sandboxes/SKILL.md index 4e7c53a..0b35c0e 100644 --- a/Skills/Sandbox/azure-connectorgateway/SKILL.md +++ b/plugin/skills/aca-sandboxes/SKILL.md @@ -1,29 +1,30 @@ --- -name: azure-connectorgateway +name: azure-connectornamespace-aca-sandbox description: | - Azure Connector Gateway — manage gateways, connections, and triggers. - Connects external services (Office 365, Teams, Microsoft Forms, SharePoint, - OneDrive, GitHub, Azure Blob) to sandbox apps via event-driven triggers or - direct API calls using connection runtime URLs. + 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. Use when: - - Creating or managing connector gateways and connections - - Creating or managing trigger configs on a connector gateway + - 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 sandbox callbacks + - 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) + - Automating workflows across Microsoft 365 services (Forms, Teams, Outlook, SharePoint, OneDrive) from within an ACA sandbox Triggers: "create trigger", "trigger config", "webhook trigger", - "connector gateway", "connection", "email trigger", "send email", + "connector namespace", "connector", "connection", "email trigger", "send email", "onedrive", "sharepoint", "teams", "teams message", "post message", - "microsoft forms", "forms", "form response", "form submission", + "microsoft forms", "forms", "form response", "form submission", "aca sandbox", + "sandbox group", "container apps sandbox", "notify", "notification", "automate", "when", "on new" --- -# Azure Connector Gateway +# Azure Connector Namespace — ACA sandbox edition -Manage connector gateways, connections, and triggers — connect external services +Manage connectors, connections, and triggers — connect external services to sandbox apps via direct API calls or event-driven triggers. ## Common scenarios @@ -116,7 +117,7 @@ Ask the user: > Use the subscription and resource group selected in Step 0. Ask the user: -- "Do you have an existing connector gateway, or should I create a new one?" +- "Do you have an existing connector, or should I create a new one?" - If **existing**: ask for the gateway name, then retrieve it (using sub/rg from Step 0): ```bash az rest --method GET \ diff --git a/Skills/Sandbox/azure-connectorgateway/references/consent.md b/plugin/skills/aca-sandboxes/references/consent.md similarity index 100% rename from Skills/Sandbox/azure-connectorgateway/references/consent.md rename to plugin/skills/aca-sandboxes/references/consent.md diff --git a/Skills/Sandbox/azure-connectorgateway/references/direct-api.md b/plugin/skills/aca-sandboxes/references/direct-api.md similarity index 99% rename from Skills/Sandbox/azure-connectorgateway/references/direct-api.md rename to plugin/skills/aca-sandboxes/references/direct-api.md index 3b028a4..a6bda8d 100644 --- a/Skills/Sandbox/azure-connectorgateway/references/direct-api.md +++ b/plugin/skills/aca-sandboxes/references/direct-api.md @@ -107,7 +107,7 @@ az rest --method POST \ "method": "POST", "path": "/datasets/default/files", "queries": {"folderPath": "/", "name": "hello.txt"}, - "body": "Hello from Connector Gateway!" + "body": "Hello from Connector!" } }' ``` diff --git a/Skills/Sandbox/azure-connectorgateway/references/dynamic-values.md b/plugin/skills/aca-sandboxes/references/dynamic-values.md similarity index 100% rename from Skills/Sandbox/azure-connectorgateway/references/dynamic-values.md rename to plugin/skills/aca-sandboxes/references/dynamic-values.md diff --git a/Skills/Sandbox/azure-connectorgateway/references/gateway-connections.md b/plugin/skills/aca-sandboxes/references/gateway-connections.md similarity index 98% rename from Skills/Sandbox/azure-connectorgateway/references/gateway-connections.md rename to plugin/skills/aca-sandboxes/references/gateway-connections.md index de5a301..d90ba37 100644 --- a/Skills/Sandbox/azure-connectorgateway/references/gateway-connections.md +++ b/plugin/skills/aca-sandboxes/references/gateway-connections.md @@ -1,6 +1,6 @@ # Gateway Connections — Declarative Sandbox ↔ Connection Wiring -How to wire a connector-gateway connection to a sandbox group + sandbox so that +How to wire a connector-namespace connection to a sandbox group + sandbox so that calls from inside the sandbox to the connection's **runtime URL** are authenticated automatically by the platform. @@ -217,7 +217,7 @@ entries: 2. It mints a Bearer token using the sandbox-group's SystemAssigned MI against the connection (this is why `sandbox-acl` must exist). 3. It adds `Authorization: Bearer ` to the outbound request. -4. The connector gateway authorizes the call (sandbox-group MI is on its ACL), +4. The connector authorizes the call (sandbox-group MI is on its ACL), exchanges the token for the stored OAuth credentials, and forwards to the downstream API (Office 365, Teams, etc.). diff --git a/Skills/Sandbox/azure-connectorgateway/references/gotchas.md b/plugin/skills/aca-sandboxes/references/gotchas.md similarity index 100% rename from Skills/Sandbox/azure-connectorgateway/references/gotchas.md rename to plugin/skills/aca-sandboxes/references/gotchas.md diff --git a/Skills/Sandbox/azure-connectorgateway/references/handler-guide.md b/plugin/skills/aca-sandboxes/references/handler-guide.md similarity index 100% rename from Skills/Sandbox/azure-connectorgateway/references/handler-guide.md rename to plugin/skills/aca-sandboxes/references/handler-guide.md diff --git a/Skills/Sandbox/azure-connectorgateway/references/prerequisites.md b/plugin/skills/aca-sandboxes/references/prerequisites.md similarity index 100% rename from Skills/Sandbox/azure-connectorgateway/references/prerequisites.md rename to plugin/skills/aca-sandboxes/references/prerequisites.md diff --git a/Skills/Sandbox/azure-connectorgateway/references/quickstart.md b/plugin/skills/aca-sandboxes/references/quickstart.md similarity index 100% rename from Skills/Sandbox/azure-connectorgateway/references/quickstart.md rename to plugin/skills/aca-sandboxes/references/quickstart.md diff --git a/Skills/Sandbox/azure-connectorgateway/references/runtime-url-examples.md b/plugin/skills/aca-sandboxes/references/runtime-url-examples.md similarity index 100% rename from Skills/Sandbox/azure-connectorgateway/references/runtime-url-examples.md rename to plugin/skills/aca-sandboxes/references/runtime-url-examples.md diff --git a/Skills/Sandbox/azure-connectorgateway/references/trigger-flow.md b/plugin/skills/aca-sandboxes/references/trigger-flow.md similarity index 98% rename from Skills/Sandbox/azure-connectorgateway/references/trigger-flow.md rename to plugin/skills/aca-sandboxes/references/trigger-flow.md index 46dbf89..40688c9 100644 --- a/Skills/Sandbox/azure-connectorgateway/references/trigger-flow.md +++ b/plugin/skills/aca-sandboxes/references/trigger-flow.md @@ -10,7 +10,7 @@ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ -│ Connector Gateway (ARM: Microsoft.Web/connectorGateways) │ +│ Connector (ARM: Microsoft.Web/connectorGateways) │ │ ├── Connection: OAuth-authorized access to the connector │ │ ├── Trigger Config: event subscription + callback delivery │ │ └── Access Policy: gateway MI granted access to connection │ @@ -32,7 +32,7 @@ ## End-to-End Flow -### Step 1: Create Connector Gateway with SystemAssigned Identity +### Step 1: Create Connector with SystemAssigned Identity ```bash az rest --method PUT \ diff --git a/Skills/Sandbox/azure-connectorgateway/references/trigger-setup.md b/plugin/skills/aca-sandboxes/references/trigger-setup.md similarity index 99% rename from Skills/Sandbox/azure-connectorgateway/references/trigger-setup.md rename to plugin/skills/aca-sandboxes/references/trigger-setup.md index ee3201d..8d2e71e 100644 --- a/Skills/Sandbox/azure-connectorgateway/references/trigger-setup.md +++ b/plugin/skills/aca-sandboxes/references/trigger-setup.md @@ -1,6 +1,6 @@ # Trigger Setup (Steps 5B–9B) -Detailed commands for creating event-driven triggers on a connector gateway. +Detailed commands for creating event-driven triggers on a connector. ## Step 5B: Discover trigger operations diff --git a/Skills/Sandbox/azure-connectorgateway/references/tutorial-welcome-emailer.md b/plugin/skills/aca-sandboxes/references/tutorial-welcome-emailer.md similarity index 99% rename from Skills/Sandbox/azure-connectorgateway/references/tutorial-welcome-emailer.md rename to plugin/skills/aca-sandboxes/references/tutorial-welcome-emailer.md index c46f202..dbdb9b8 100644 --- a/Skills/Sandbox/azure-connectorgateway/references/tutorial-welcome-emailer.md +++ b/plugin/skills/aca-sandboxes/references/tutorial-welcome-emailer.md @@ -5,7 +5,7 @@ Office 365 connector using **Direct API calls (Pattern A)**. ## What you'll build -- A connector gateway + OAuth connection to Office 365 +- A connector + OAuth connection to Office 365 - A sandbox running a Python script that sends a welcome email - Declarative **gatewayConnections** wiring (SG-level PATCH + per-sandbox PUT body) so the sandbox calls the runtime URL with **no auth code** — the @@ -43,7 +43,7 @@ RG="welcome-emailer-rg" LOCATION="eastus" ``` -## Step 2: Create connector gateway +## Step 2: Create connector ```powershell $gwBody = @{ location = $LOCATION; identity = @{ type = "SystemAssigned" } } | ConvertTo-Json -Compress diff --git a/Skills/Sandbox/azure-connectorgateway/scripts/trigger-getting-started.py b/plugin/skills/aca-sandboxes/scripts/trigger-getting-started.py similarity index 99% rename from Skills/Sandbox/azure-connectorgateway/scripts/trigger-getting-started.py rename to plugin/skills/aca-sandboxes/scripts/trigger-getting-started.py index 444b596..4cfd05e 100644 --- a/Skills/Sandbox/azure-connectorgateway/scripts/trigger-getting-started.py +++ b/plugin/skills/aca-sandboxes/scripts/trigger-getting-started.py @@ -9,7 +9,7 @@ Prerequisites: - Azure CLI signed in (az login) - - Connector gateway + connection already set up + - Connector + connection already set up - Sandbox group + sandbox already created Usage: @@ -27,7 +27,7 @@ parser = argparse.ArgumentParser(description="Trigger Getting Started") parser.add_argument("-g", "--resource-group", required=True, help="Resource group") -parser.add_argument("--gateway", required=True, help="Connector gateway name") +parser.add_argument("--gateway", required=True, help="Connector name") parser.add_argument("--connector", default="office365", help="Connector type (default: office365)") parser.add_argument("--connection-name", default=None, help="Connection name on the gateway") parser.add_argument("--sandbox-id", required=True, help="Sandbox ID for trigger target") diff --git a/Skills/Sandbox/azure-connectorgateway/version.json b/plugin/skills/aca-sandboxes/version.json similarity index 100% rename from Skills/Sandbox/azure-connectorgateway/version.json rename to plugin/skills/aca-sandboxes/version.json diff --git a/plugin/skills/connectors/README.md b/plugin/skills/connectors/README.md new file mode 100644 index 0000000..cc5f0fa --- /dev/null +++ b/plugin/skills/connectors/README.md @@ -0,0 +1,36 @@ +# Azure Connectors Skills + +Skills for [Azure Connector Namespaces](https://connectors.azure.com) — install +once, drive connector namespaces, connections, triggers, and MCP server configs +from natural language in your coding agent. + +> The plugin descriptors live under `plugin/` (`plugin/.plugin/plugin.json` and +> `plugin/.claude-plugin/plugin.json`); the marketplace manifest +> (`marketplace.json`) lives at the repo root. Skill sources live in +> sibling folders under `plugin/skills/`. + +## Install + +### GitHub Copilot CLI + +```bash +# Direct install (single command) +/plugin install Azure/Connectors + +# Or via marketplace (useful when this repo adds more skills) +/plugin marketplace add Azure/Connectors +/plugin install azure-connectornamespace@Azure-Connectors +``` + +### Claude Code + +```bash +claude plugin add Azure/Connectors +``` + +## Skills + +| Skill | Description | +|-------|-------------| +| [azure-connectornamespace](SKILL.md) | Generic, callback-agnostic — manage connector namespaces, connections, trigger configs that POST to any HTTP(S) callback (Function App, Logic App, App Service, custom webhook), and MCP server configs that expose connector operations as MCP tools. | +| [azure-connectornamespace-aca-sandbox](../aca-sandboxes/SKILL.md) | ACA-sandbox edition — manage connector namespaces, connections, and triggers; wire external services (Office 365, Teams, Forms, SharePoint, OneDrive, GitHub, Azure Blob) to Azure Container Apps sandbox apps via event-driven triggers or direct API calls using connection runtime URLs. | diff --git a/Skills/General/azure-connectornamespace/SKILL.md b/plugin/skills/connectors/SKILL.md similarity index 99% rename from Skills/General/azure-connectornamespace/SKILL.md rename to plugin/skills/connectors/SKILL.md index d0ae746..e0ad571 100644 --- a/Skills/General/azure-connectornamespace/SKILL.md +++ b/plugin/skills/connectors/SKILL.md @@ -14,7 +14,7 @@ description: | - Recurrence / sliding-window triggers that fire on a schedule - Exposing connector operations as Model Context Protocol (MCP) tools at a namespace endpoint - Calling connector APIs (send email, post Teams message, upload files, list items, ...) - Triggers: "connector namespace", "connector gateway", "create trigger", "trigger config", "webhook trigger", + Triggers: "connector namespace", "connector", "create trigger", "trigger config", "webhook trigger", "recurrence trigger", "schedule trigger", "on new email", "on new file", "on new item", "on form response", "callback url", "notification url", "mcp", "mcp server", "mcp tools", "model context protocol", @@ -30,7 +30,7 @@ ngrok, anywhere) and you choose how the namespace authenticates to it. > If you specifically need to fan events into an Azure Container Apps sandbox group > (with declarative `gatewayConnections[]` wiring and sandbox callbacks), use the -> companion skill at `Skills/Sandbox/azure-connectorgateway` instead. +> companion skill at `plugin/skills/aca-sandboxes` instead. > **Naming note.** This skill was previously called "Connector Gateway". The > resource is now displayed as **"Connector Namespace"** everywhere (matching diff --git a/Skills/General/azure-connectornamespace/references/connections.md b/plugin/skills/connectors/references/connections.md similarity index 100% rename from Skills/General/azure-connectornamespace/references/connections.md rename to plugin/skills/connectors/references/connections.md diff --git a/Skills/General/azure-connectornamespace/references/consent.md b/plugin/skills/connectors/references/consent.md similarity index 100% rename from Skills/General/azure-connectornamespace/references/consent.md rename to plugin/skills/connectors/references/consent.md diff --git a/Skills/General/azure-connectornamespace/references/direct-api.md b/plugin/skills/connectors/references/direct-api.md similarity index 100% rename from Skills/General/azure-connectornamespace/references/direct-api.md rename to plugin/skills/connectors/references/direct-api.md diff --git a/Skills/General/azure-connectornamespace/references/dynamic-values.md b/plugin/skills/connectors/references/dynamic-values.md similarity index 100% rename from Skills/General/azure-connectornamespace/references/dynamic-values.md rename to plugin/skills/connectors/references/dynamic-values.md diff --git a/Skills/General/azure-connectornamespace/references/gotchas.md b/plugin/skills/connectors/references/gotchas.md similarity index 93% rename from Skills/General/azure-connectornamespace/references/gotchas.md rename to plugin/skills/connectors/references/gotchas.md index 3740c6c..55d0e63 100644 --- a/Skills/General/azure-connectornamespace/references/gotchas.md +++ b/plugin/skills/connectors/references/gotchas.md @@ -4,7 +4,7 @@ Common issues for the generic connector-namespace skill and their fixes. | Issue | Solution | |-------|----------| -| **"Connector Gateway" vs "Connector Namespace" — which name?** | The resource is now displayed everywhere as **"Connector Namespace"** (matching the Cascade portal rename). The underlying ARM resource type is still `Microsoft.Web/connectorGateways` (URL segment kept for backwards compatibility), and the sandbox-group property `gatewayConnections[]` also keeps its name. Use the new display name in prose; keep the legacy strings in URLs and JSON payloads. | +| **"Connector" vs "Connector Namespace" — which name?** | The resource is now displayed everywhere as **"Connector Namespace"** (matching the Cascade portal rename). The underlying ARM resource type is still `Microsoft.Web/connectorGateways` (URL segment kept for backwards compatibility), and the sandbox-group property `gatewayConnections[]` also keeps its name. Use the new display name in prose; keep the legacy strings in URLs and JSON payloads. | | **Trigger not firing** (connector event) | Make sure `namespace-acl` exists on the connection (namespace MI → connection). Without it the subscription silently fails. See [trigger-setup.md](trigger-setup.md) Step 4. | | **Trigger state is `Enabled` but no runs** | Check `triggerConfigs/{name}/runs` for errors. Most commonly: the namespace couldn't reach `callbackUrl` (4xx/5xx from your endpoint) or authentication mismatch. | | **Trigger run shows `Unauthorized` from callback** | Your callback URL's auth doesn't match `notificationDetails.authentication`. Re-check the type/audience/secret. See [notification-authentication.md](notification-authentication.md). | diff --git a/Skills/General/azure-connectornamespace/references/mcp-server-config.md b/plugin/skills/connectors/references/mcp-server-config.md similarity index 100% rename from Skills/General/azure-connectornamespace/references/mcp-server-config.md rename to plugin/skills/connectors/references/mcp-server-config.md diff --git a/Skills/General/azure-connectornamespace/references/notification-authentication.md b/plugin/skills/connectors/references/notification-authentication.md similarity index 100% rename from Skills/General/azure-connectornamespace/references/notification-authentication.md rename to plugin/skills/connectors/references/notification-authentication.md diff --git a/Skills/General/azure-connectornamespace/references/prerequisites.md b/plugin/skills/connectors/references/prerequisites.md similarity index 100% rename from Skills/General/azure-connectornamespace/references/prerequisites.md rename to plugin/skills/connectors/references/prerequisites.md diff --git a/Skills/General/azure-connectornamespace/references/quickstart.md b/plugin/skills/connectors/references/quickstart.md similarity index 100% rename from Skills/General/azure-connectornamespace/references/quickstart.md rename to plugin/skills/connectors/references/quickstart.md diff --git a/Skills/General/azure-connectornamespace/references/trigger-flow.md b/plugin/skills/connectors/references/trigger-flow.md similarity index 100% rename from Skills/General/azure-connectornamespace/references/trigger-flow.md rename to plugin/skills/connectors/references/trigger-flow.md diff --git a/Skills/General/azure-connectornamespace/references/trigger-setup.md b/plugin/skills/connectors/references/trigger-setup.md similarity index 100% rename from Skills/General/azure-connectornamespace/references/trigger-setup.md rename to plugin/skills/connectors/references/trigger-setup.md diff --git a/Skills/General/azure-connectornamespace/references/tutorial.md b/plugin/skills/connectors/references/tutorial.md similarity index 100% rename from Skills/General/azure-connectornamespace/references/tutorial.md rename to plugin/skills/connectors/references/tutorial.md diff --git a/Skills/General/azure-connectornamespace/version.json b/plugin/skills/connectors/version.json similarity index 100% rename from Skills/General/azure-connectornamespace/version.json rename to plugin/skills/connectors/version.json From 4ca75949ea5bdeb3ecf17ef085abd6564bb979a1 Mon Sep 17 00:00:00 2001 From: apranaseth Date: Mon, 1 Jun 2026 11:38:19 -0700 Subject: [PATCH 13/14] fix(connectors-skill): ARM base URL should use connectorGateways (legacy resource type) Line 93 of plugin/skills/connectors/SKILL.md showed `Microsoft.Web/connectorNamespaces` as the ARM base, but the underlying ARM resource type is still `Microsoft.Web/connectorGateways` (legacy URL segment kept for backwards compatibility, as explicitly stated five lines higher on line 38 of the same file and in every actual `az rest` URL in this skill). The mismatched prose came from an accidental over-rebrand in 8a4fb14 (display-only rebrand commit). Restored to match the rest of the doc and the actual ARM resource type. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- plugin/skills/connectors/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/skills/connectors/SKILL.md b/plugin/skills/connectors/SKILL.md index e0ad571..f4f6249 100644 --- a/plugin/skills/connectors/SKILL.md +++ b/plugin/skills/connectors/SKILL.md @@ -90,7 +90,7 @@ ngrok, anywhere) and you choose how the namespace authenticates to it. ### Step 1: Namespace setup -> **ARM base:** `https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorNamespaces` +> **ARM base:** `https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways` > **API version:** `2026-05-01-preview` Ask the user: "Do you have an existing connector namespace, or should I create a new one?" From b2e172ea4f09c2120c680098a530997c8c15358c Mon Sep 17 00:00:00 2001 From: apranaseth Date: Mon, 1 Jun 2026 11:39:47 -0700 Subject: [PATCH 14/14] fix(connectors-skill): remove internal source-tree references Three places in the connectors skill leaked internal repo paths (`Cascade.Portal.Client/...`) that don't exist in this public repo and aren't actionable for readers. Rewrote each to describe the behavior in user-facing terms: - `plugin/skills/connectors/SKILL.md` line 35-37: `Cascade portal rename - see Cascade.Portal.Client/src/components/ CreateConnectorNamespacePanel.tsx` -> `portal rename`. - `plugin/skills/connectors/references/gotchas.md` line 7: `Cascade portal rename` -> `portal rename`. - `plugin/skills/connectors/references/trigger-setup.md` line 165-166: the `Cascade's serializeTriggerParams` reference with the full TS file path -> a sentence describing what the portal trigger wizard does before PUT, without naming the implementation file. Caught by Copilot reviewer on PR #124. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- plugin/skills/connectors/SKILL.md | 2 +- plugin/skills/connectors/references/gotchas.md | 2 +- plugin/skills/connectors/references/trigger-setup.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugin/skills/connectors/SKILL.md b/plugin/skills/connectors/SKILL.md index f4f6249..31d10f9 100644 --- a/plugin/skills/connectors/SKILL.md +++ b/plugin/skills/connectors/SKILL.md @@ -34,7 +34,7 @@ ngrok, anywhere) and you choose how the namespace authenticates to it. > **Naming note.** This skill was previously called "Connector Gateway". The > resource is now displayed as **"Connector Namespace"** everywhere (matching -> the Cascade portal rename — see `Cascade.Portal.Client/src/components/CreateConnectorNamespacePanel.tsx`). +> the portal rename). > **The ARM resource type is still `Microsoft.Web/connectorGateways`** (legacy > URL segment kept for backwards compatibility) and the property `gatewayConnections[]` > on sandbox groups also keeps its name. Do not rewrite those API strings. diff --git a/plugin/skills/connectors/references/gotchas.md b/plugin/skills/connectors/references/gotchas.md index 55d0e63..05b8f62 100644 --- a/plugin/skills/connectors/references/gotchas.md +++ b/plugin/skills/connectors/references/gotchas.md @@ -4,7 +4,7 @@ Common issues for the generic connector-namespace skill and their fixes. | Issue | Solution | |-------|----------| -| **"Connector" vs "Connector Namespace" — which name?** | The resource is now displayed everywhere as **"Connector Namespace"** (matching the Cascade portal rename). The underlying ARM resource type is still `Microsoft.Web/connectorGateways` (URL segment kept for backwards compatibility), and the sandbox-group property `gatewayConnections[]` also keeps its name. Use the new display name in prose; keep the legacy strings in URLs and JSON payloads. | +| **"Connector" vs "Connector Namespace" — which name?** | The resource is now displayed everywhere as **"Connector Namespace"** (matching the portal rename). The underlying ARM resource type is still `Microsoft.Web/connectorGateways` (URL segment kept for backwards compatibility), and the sandbox-group property `gatewayConnections[]` also keeps its name. Use the new display name in prose; keep the legacy strings in URLs and JSON payloads. | | **Trigger not firing** (connector event) | Make sure `namespace-acl` exists on the connection (namespace MI → connection). Without it the subscription silently fails. See [trigger-setup.md](trigger-setup.md) Step 4. | | **Trigger state is `Enabled` but no runs** | Check `triggerConfigs/{name}/runs` for errors. Most commonly: the namespace couldn't reach `callbackUrl` (4xx/5xx from your endpoint) or authentication mismatch. | | **Trigger run shows `Unauthorized` from callback** | Your callback URL's auth doesn't match `notificationDetails.authentication`. Re-check the type/audience/secret. See [notification-authentication.md](notification-authentication.md). | diff --git a/plugin/skills/connectors/references/trigger-setup.md b/plugin/skills/connectors/references/trigger-setup.md index 207f3bb..a9c05e3 100644 --- a/plugin/skills/connectors/references/trigger-setup.md +++ b/plugin/skills/connectors/references/trigger-setup.md @@ -162,8 +162,8 @@ parameters = @( > **Mental model:** treat the user's per-leaf answers as a flat > `Record` map, then `setNestedValue` each entry into a > single accumulator object whose root is `body`. That accumulator becomes one -> `parameters[]` entry. This mirrors Cascade's `serializeTriggerParams` exactly -> (`src\Cascade.Portal.Client\src\components\ConnectorGateways\TriggerWizard\utils\serializeTriggerParams.ts`). +> `parameters[]` entry. This mirrors the serialization the portal's trigger +> wizard performs before PUT. > **Polling cadence:** if the operation has neither `x-ms-notification` nor > `x-ms-notification-content` in its Swagger, it polls (default ~3 min). Inform