skills/azure-connectors/SKILL.md
Work with Azure managed API connectors (Office 365 and Microsoft Teams) to send emails, read emails, post Teams messages, reply to Teams threads, and list Teams channels. Also helps create and authenticate new connector resources in Azure. Remembers selected connectors in a `.env.connectors` file so you only configure once per repo. USE THIS SKILL whenever the user wants to interact with Office 365 email or Microsoft Teams channels through Azure, wants to set up an API connection in Azure, or mentions sending/reading emails or posting Teams messages via connectors. Covers: creating connections, authenticating via OAuth, persisting connector selections, listing teams and channels, posting and replying to messages, sending and reading emails.
npx skillsauth add anthonychu/skills azure-connectorsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill helps you work with Azure managed API connectors — specifically Office 365
(email) and Microsoft Teams (channel messages). It uses az cli to make API calls
directly, with no Python or SDK dependencies.
Azure managed connectors are pre-built API integrations hosted by Azure. Each connector (Office 365, Teams, Salesforce, SharePoint, etc.) is an Azure resource that stores OAuth credentials and proxies API calls through Azure Resource Manager (ARM).
The flow is:
dynamicInvoke endpoint — your request is wrapped and
forwarded to the connector's backendAll API calls go through ARM, so you authenticate with your Azure identity (via az cli),
and the connector handles the downstream OAuth token to the actual service (Office 365,
Teams, etc.).
az CLI installed and logged in (az login).env.connectors)To avoid asking the user for subscription, resource group, and connection details every time,
this skill uses a .env.connectors file in the repo root to remember which connectors have
been set up.
Always check for .env.connectors at the start of any connector operation. If the file
exists and has the needed connector, use those values directly — don't re-ask the user.
# Each value is the full ARM resource ID of the most recently used connector of that type
SELECTED_OFFICE365_CONNECTOR=/subscriptions/abc-123-def-456/resourceGroups/my-connectors-rg/providers/Microsoft.Web/connections/office365
SELECTED_TEAMS_CONNECTOR=/subscriptions/abc-123-def-456/resourceGroups/my-connectors-rg/providers/Microsoft.Web/connections/teams
The variable names follow the pattern SELECTED_{CONNECTOR_UPPER}_CONNECTOR where
{CONNECTOR_UPPER} is the connector type in uppercase (e.g., OFFICE365, TEAMS).
The value is always the full resource ID — this means you can use it directly as CONN_ID
for API calls without needing to reconstruct the path.
.env.connectors.env.connectorsCONN_ID — don't ask the user for
subscription, resource group, or connection name. Confirm briefly: "Using your saved
Office 365 connector ($SELECTED_OFFICE365_CONNECTOR)" so the user knows what's happening.if [ -f .env.connectors ]; then
source .env.connectors
fi
For example, if the user wants to send an email and SELECTED_OFFICE365_CONNECTOR is set,
you can jump straight to making the API call with CONN_ID="$SELECTED_OFFICE365_CONNECTOR".
If the needed variable is missing, fall back to asking the user.
When saving, append or update entries. Use this approach:
# Helper: set a key in .env.connectors (creates file if needed)
set_connector_env() {
local key="$1" value="$2" file=".env.connectors"
if [ -f "$file" ] && grep -q "^${key}=" "$file"; then
sed -i '' "s|^${key}=.*|${key}=${value}|" "$file" # macOS sed
else
echo "${key}=${value}" >> "$file"
fi
}
# Example: after creating an office365 connection
CONN_RESOURCE_ID="/subscriptions/$SUB_ID/resourceGroups/$RG/providers/Microsoft.Web/connections/$CONN_NAME"
set_connector_env SELECTED_OFFICE365_CONNECTOR "$CONN_RESOURCE_ID"
.gitignore includes .env.connectorsAfter writing to .env.connectors, check if it's already in .gitignore. If not, add it:
if [ -f .gitignore ]; then
grep -qxF '.env.connectors' .gitignore || echo '.env.connectors' >> .gitignore
else
echo '.env.connectors' > .gitignore
fi
This prevents connector details (subscription IDs, resource groups) from being committed.
When you have access to an interactive prompt tool (e.g., askQuestion, ask_user,
vscode_askQuestions, or similar), use it to guide the user through selecting their
Azure context instead of making them type out subscription IDs and resource group names.
This makes the experience much smoother — the user picks from a list rather than
copy-pasting GUIDs.
Use this flow when all of these are true:
.env.connectors for the needed typeIf the user already gave you the details ("my sub is abc-123, resource group is my-rg"), skip discovery and use what they gave you.
List the user's Azure subscriptions and let them pick:
az account list --query '[].{name:name, id:id, isDefault:isDefault}' -o json
Present the results and ask which subscription to use. If there's only one, use it automatically and confirm.
Check if connectors of the needed type already exist in the subscription:
SUB_ID="selected-subscription-id"
az rest --method GET \
--url "https://management.azure.com/subscriptions/$SUB_ID/providers/Microsoft.Web/connections?api-version=2016-06-01" \
| jq '[.value[] | select(.properties.api.name == "office365" or .properties.api.name == "teams") | {name: .name, type: .properties.api.name, resourceGroup: .id | split("/")[4], status: .properties.overallStatus, id: .id}]'
If connectors are found, present them and ask the user to pick one. Include the connection status so they can see which ones are authenticated. Always include a "Create a new connector" option at the end of the list — even if existing connectors are found, the user may want a fresh one.
If no connectors exist, list resource groups and ask where to create it:
az group list --query '[].{name:name, location:location}' -o json
After the user picks (or creates) a connector, save it to .env.connectors as usual.
This way they only go through the interactive flow once per connector type.
The conversation might look like:
User: "Send an email to [email protected]"
Agent: No saved Office 365 connector found. Let me help you pick one.
(lists subscriptions via prompt tool)
User picks: "My Company Subscription (abc-123)"
(searches for existing office365 connectors in that subscription)
Agent: Found 2 Office 365 connectors:
office365inprod-connectors(Connected)office365-devindev-rg(Connected)- Create a new connector
User picks: #1
(saves to .env.connectors, then sends the email)
If you don't have an interactive prompt tool, fall back to the non-interactive approach:
ask in the chat message for the subscription ID, resource group, and connection name,
or try az account show for the current subscription and search from there.
First, check if .env.connectors has a saved connector for the type you need:
if [ -f .env.connectors ]; then
source .env.connectors
fi
# If the connector is already saved, you can use it directly:
# CONN_ID="$SELECTED_OFFICE365_CONNECTOR" # or $SELECTED_TEAMS_CONNECTOR
# Otherwise, gather Azure context from az cli / the user
SUB_ID=$(az account show --query id -o tsv)
RG="your-resource-group"
LOCATION="westus"
Replace {CONNECTOR} with office365 or teams:
CONNECTOR="office365" # or "teams"
CONN_NAME="$CONNECTOR" # name for the connection resource
az rest --method PUT \
--url "https://management.azure.com/subscriptions/$SUB_ID/resourceGroups/$RG/providers/Microsoft.Web/connections/$CONN_NAME?api-version=2016-06-01" \
--body "{
\"location\": \"$LOCATION\",
\"properties\": {
\"api\": {
\"id\": \"/subscriptions/$SUB_ID/providers/Microsoft.Web/locations/$LOCATION/managedApis/$CONNECTOR\"
},
\"displayName\": \"$CONNECTOR\",
\"parameterValues\": {}
}
}"
After creating, open the Azure Portal to authenticate. The portal's connection edit page
is the only reliable way to complete the OAuth flow — do NOT use the listConsentLinks
or confirmConsentCode APIs, as they are fragile and often fail silently.
# Open the connection in Azure Portal to authenticate
PORTAL_URL="https://portal.azure.com/?feature.customportal=false#@your-tenant.onmicrosoft.com/resource/subscriptions/$SUB_ID/resourceGroups/$RG/providers/Microsoft.Web/connections/$CONN_NAME/edit"
echo "Open this URL to authenticate: $PORTAL_URL"
open "$PORTAL_URL" # macOS — use xdg-open on Linux
In the portal, click Authorize and sign in with the account you want the connection to use. Click Save after authorizing.
After creating and authenticating, save the connection details so you don't have to ask for them again:
set_connector_env() {
local key="$1" value="$2" file=".env.connectors"
if [ -f "$file" ] && grep -q "^${key}=" "$file"; then
sed -i '' "s|^${key}=.*|${key}=${value}|" "$file"
else
echo "${key}=${value}" >> "$file"
fi
}
CONN_RESOURCE_ID="/subscriptions/$SUB_ID/resourceGroups/$RG/providers/Microsoft.Web/connections/$CONN_NAME"
CONNECTOR_UPPER=$(echo "$CONNECTOR" | tr '[:lower:]' '[:upper:]')
set_connector_env "SELECTED_${CONNECTOR_UPPER}_CONNECTOR" "$CONN_RESOURCE_ID"
# Ensure .env.connectors is gitignored
if [ -f .gitignore ]; then
grep -qxF '.env.connectors' .gitignore || echo '.env.connectors' >> .gitignore
else
echo '.env.connectors' > .gitignore
fi
# Check connection status
az rest --method GET \
--url "https://management.azure.com/subscriptions/$SUB_ID/resourceGroups/$RG/providers/Microsoft.Web/connections/$CONN_NAME?api-version=2016-06-01" \
--query 'properties.overallStatus' -o tsv
# Should print: Connected
All connector actions use the dynamicInvoke endpoint. The pattern is always:
# Use saved connector if available, otherwise construct from parts
CONN_ID="${SELECTED_OFFICE365_CONNECTOR:-/subscriptions/$SUB_ID/resourceGroups/$RG/providers/Microsoft.Web/connections/$CONN_NAME}"
az rest --method POST \
--url "https://management.azure.com${CONN_ID}/dynamicInvoke?api-version=2016-06-01" \
--body '{
"request": {
"method": "GET",
"path": "/api/path/here",
"queries": {},
"body": {}
}
}'
The response is always wrapped: {"response": {"statusCode": "OK", "body": {...}}}.
Use --query 'response.body' to extract the actual data.
az rest handles authentication automatically — no need to manage tokens manually.
For the detailed API paths for each connector, see the reference files:
references/teams-api.md — Teams channel messages, replies, teams, channelsreferences/office365-api.md — Email send, read, reply, manage# Load saved connector resource IDs if available
if [ -f .env.connectors ]; then source .env.connectors; fi
# Use the saved resource ID directly as CONN_ID
CONN_ID="$SELECTED_OFFICE365_CONNECTOR" # or $SELECTED_TEAMS_CONNECTOR
development
Interactive git and GitHub tutor that teaches through hands-on practice in VS Code's terminal. Adapts to any skill level — from someone who's never opened a terminal to principal engineers filling knowledge gaps. Covers git commands, concepts, branching, merging, rebasing, GitHub workflows, and more. Tracks progress, streaks, and achievements in a `.git-tutor/` folder. USE THIS SKILL whenever the user wants to learn git, practice git, understand git concepts, get a git tutorial, learn GitHub, or says things like "teach me git", "I want to practice git", "help me understand branching", "git tutorial", "I'm new to git", "how does git work", "let's do more git practice", or asks to start the git tutorial. Also triggers for questions about git concepts when the user seems to be in a learning context rather than needing a quick answer for active development work.
tools
Build, scaffold, extend, deploy, and troubleshoot event-driven AI agents and scheduled serverless agent apps on Azure Functions using azurefunctions-agents-runtime. Use when the user wants a scheduled agent, morning briefing, daily digest, timer agent, inbox summary, email or Teams briefing, background AI workflow, connector-triggered agent, event-driven AI automation, HTTP/chat agent, webhook-style agent, or Azure Functions hosted agent. Covers .agent.md, agents.config.yaml, Foundry gpt-4.1/gpt-5.x model choice, dynamic sessions for code execution and web browsing, built-in chat/API/MCP endpoints, remote MCP servers, Connector Namespaces, Office 365 or Teams MCP tools/triggers, custom Python tools, Agent Skills, azd deployment, local.settings.json, Application Insights, local development, and troubleshooting.
development
Maintainer-only workflow for handling GitHub Secret Scanning alerts on OpenClaw. Use when Codex needs to triage, redact, clean up, and resolve secret leakage found in issue comments, issue bodies, PR comments, or other GitHub content.
development
Maintainer workflow for OpenClaw releases, prereleases, changelog release notes, and publish validation. Use when Codex needs to prepare or verify stable or beta release steps, align version naming, assemble release notes, check release auth requirements, or validate publish-time commands and artifacts.