Teams Dev
microsoft/teams-sdk
A skill your agent uses whenever the user mentions Microsoft Teams in a development context — whether they're building, integrating, configuring, debugging, or just asking questions.
Add Microsoft Teams channel integration via Chat SDK. An agent skill from nanocoai/nanoclaw.
$ npx skills add nanocoai/nanoclaw --skill add-teams -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install nanocoai/nanoclaw add-teams --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/nanocoai/nanoclaw.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/add-teams .claude/skills/add-teams && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "add-teams" agent skill from https://github.com/nanocoai/nanoclaw/tree/main/.claude/skills/add-teams into .claude/skills/add-teams/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "add-teams", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/nanocoai/nanoclaw/tree/main/.claude/skills/add-teamsType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add nanocoai/nanoclaw --skill add-teams -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install nanocoai/nanoclaw add-teams --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/nanocoai/nanoclaw.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.claude/skills/add-teams .agents/skills/add-teams && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "add-teams" agent skill from https://github.com/nanocoai/nanoclaw/tree/main/.claude/skills/add-teams into .agents/skills/add-teams/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "add-teams", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add nanocoai/nanoclaw --skill add-teams -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install nanocoai/nanoclaw add-teams --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/nanocoai/nanoclaw.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.claude/skills/add-teams .cursor/skills/add-teams && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "add-teams" agent skill from https://github.com/nanocoai/nanoclaw/tree/main/.claude/skills/add-teams into .cursor/skills/add-teams/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "add-teams", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/nanocoai/nanoclaw.git --path .claude/skills/add-teams--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add nanocoai/nanoclaw --skill add-teams -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install nanocoai/nanoclaw add-teams --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/nanocoai/nanoclaw.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.claude/skills/add-teams .gemini/skills/add-teams && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "add-teams" agent skill from https://github.com/nanocoai/nanoclaw/tree/main/.claude/skills/add-teams into .gemini/skills/add-teams/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "add-teams", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install nanocoai/nanoclaw add-teamsInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add nanocoai/nanoclaw --skill add-teams -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/nanocoai/nanoclaw.git skills-src && mkdir -p .github/skills && cp -r skills-src/.claude/skills/add-teams .github/skills/add-teams && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "add-teams" agent skill from https://github.com/nanocoai/nanoclaw/tree/main/.claude/skills/add-teams into .github/skills/add-teams/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "add-teams", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add nanocoai/nanoclaw --skill add-teams -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install nanocoai/nanoclaw add-teams --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/nanocoai/nanoclaw.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.claude/skills/add-teams .opencode/skills/add-teams && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "add-teams" agent skill from https://github.com/nanocoai/nanoclaw/tree/main/.claude/skills/add-teams into .opencode/skills/add-teams/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "add-teams", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
add-teamsAdd Microsoft Teams channel integration via Chat SDK. An agent skill from nanocoai/nanoclaw.
Add Teams is an agent skill from nanocoai/nanoclaw. Add Microsoft Teams channel integration via Chat SDK.
Its SKILL.md is about 7.2k tokens, which your agent loads only when the skill is triggered. The skill folder holds 2 other files (for example `REMOVE.md` and `apply-fixtures.json`).
It works with Microsoft Teams. The repository describes itself as: A lightweight alternative to OpenClaw that runs in containers for security. Connects to WhatsApp, Telegram, Slack, Discord, Gmail and other messaging apps,, has memory, scheduled…. The licence is MIT.
4 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 66f0823. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
npmpnpmazcloudflaredFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
smba.trafficmanager.netlogin.microsoftonline.comapi.botframework.comAlso links to:
dev.teams.microsoft.comFrom URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
TEAMS_APP_PASSWORDCLIENT_SECRETFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Add Teams loads about 7.2k tokens when it runs. Until then it costs about 16 tokens; SKILL.md has 3,032 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check noted patterns worth knowing about, such as sudo or a known installer.
when `.env` already carries a Teams credential — either key; a partial pair( grep -q '^TEAMS_APP_ID=.' .env 2>/dev/null || grep -q '^TEAMS_APP_PASSWORD=.' .env 2>/dev/null ) && echo yes || echo nThe adapter reads these from `.env` (set-if-absent — a value you've already`.env`, never with your account. On a headless box that session is a`.env` the resolve steps are skipped, so there is nothing new to wire — thery value it produces maps onto the same `.env`5. **Store the credentials** in `.env` (the same 401 pairing rule applies —4. The credentials are in `.env` (`TEAMS_APP_ID`, `TEAMS_APP_PASSWORD`,ecrets), update `TEAMS_APP_PASSWORD` in `.env`, and restart.e credentials flow skips creation while `.env` has `TEAMS_APP_ID` **or**Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from nanocoai/nanoclaw at commit 66f0823, republished under its MIT licence (© nanocoai). 3,032 words, ~7,177 tokens.
.claude/skills/add-teams/SKILL.md (or your agent's skills folder). This skill also uses 2 other files; get the full folder from GitHub.Adds Microsoft Teams support via the Chat SDK bridge — interactive chat in team
channels, group chats, and direct messages. NanoClaw doesn't ship channels in
trunk — this skill copies the Teams adapter in from the channels branch.
The mechanical steps under Apply carry nc: directive fences: an agent
reads the prose and applies them, and a parser can apply them deterministically
from the same document. Every directive is idempotent, so the whole skill is
safe to re-run; anything a parser can't apply falls back to the prose beside it.
Teams has no "paste a token" shortcut — a bot has to exist in Microsoft's cloud before it can receive a message. The Microsoft Teams CLI collapses that into one sign-in and one create command: it registers the Entra app, generates the client secret, registers a Teams-managed bot (through the Teams Developer Portal — no Azure subscription needed), uploads the app package, and hands back an install link. The old ~7-step Azure portal walk survives only as a fallback in Alternatives for tenants where the Developer Portal is blocked.
Fetch the channels branch and copy the Teams adapter and its registration test
into src/channels/ (overwrite — the branch is canonical):
src/channels/teams.ts
src/channels/teams-registration.test.tsAppend the self-registration import to the channel barrel (skipped if the line is already present). This one line is the skill's only reach-in into core:
import './teams.js';Pinned to an exact version — the supply-chain policy rejects ranges and latest:
@chat-adapter/teams@4.29.0Build first: it guards the typed createChatSdkBridge(...) core call and proves
the dependency is installed. Then run the one integration test.
pnpm run buildpnpm exec vitest run src/channels/teams-registration.test.tsteams-registration.test.ts imports the real channel barrel and asserts the
registry contains teams. It goes red if the import line is deleted or drifts,
if the barrel fails to evaluate, or if @chat-adapter/teams isn't installed (the
import throws) — so it also covers the dependency from step 3. End-to-end
delivery against a real Teams workspace is verified manually once the service
runs.
The adapter is installed and registered, but it can't receive a message until a bot exists, points at this machine, and is installed into Teams. The Teams CLI does all of that below.
Re-running teams app create provisions a brand-new app registration and bot
each time — it never reuses the first one. So the flow starts with a probe:
when .env already carries a Teams credential — either key; a partial pair
means a half-finished setup that creating ANOTHER app would only corrupt —
every step below (prompts included) is skipped and the flow drops straight
through to Restart. To rotate credentials or finish a partial
configuration, see Troubleshooting; if your tunnel URL
changed, the fix is teams app update, not a re-run (also in Troubleshooting).
( grep -q '^TEAMS_APP_ID=.' .env 2>/dev/null || grep -q '^TEAMS_APP_PASSWORD=.' .env 2>/dev/null ) && echo yes || echo noBefore creating anything, tell the user:
Confirm you have everything Teams setup needs:
1. A Microsoft 365 account that can create Entra app registrations and upload custom apps (sideloading) — free personal Teams does NOT qualify; you need a Microsoft 365 Business / EDU / developer tenant.
2. A way to expose an HTTPS endpoint that forwards to this machine's webhook port 3000 (e.g. a Cloudflare Tunnel, or a reverse-proxied VPS). Start it now if it isn't running — e.g. `cloudflared tunnel --url http://localhost:3000` — the create step needs the URL up front. The next prompt asks for its public base URL: just the https:// origin, no trailing path.
Note: the bot is created single-tenant (only your own Microsoft 365 tenant can install it) — the right default for a self-hosted assistant. If you need a bot other tenants can install, set it up manually via the Alternatives section of this skill instead.Microsoft delivers bot messages to an HTTPS endpoint you control; it has to
reach this machine's webhook server (port 3000, configurable via
WEBHOOK_PORT) at /webhook/teams.
Paste your tunnel's public https:// URL — e.g. https://your-tunnel.trycloudflare.comOne more choice belongs to the human before anything is created. The name is
used everywhere at once: the Entra app registration, the bot, and the Teams
app are all created under it. There is no client-secret name to pick on this
path — the CLI generates the secret itself (Entra displayName default,
2-year expiry); rotating it later is in Troubleshooting.
What should the bot be called? One name covers the Entra app registration, the bot, and the Teams app (letters, digits, spaces, . _ -; max 30 characters) — e.g. NanoClaw.Installed globally with npm — not as a workspace dependency — deliberately:
the CLI's credential store (keytar) is a native module whose install script
must run to fetch its prebuilt binary, and pnpm's supply-chain policy blocks
dependency build scripts — a workspace install leaves the sign-in unable to
persist. The global install matches Microsoft's own instruction and keeps the
workspace policy intact. Pinned; re-running is a no-op. (If npm reports
EACCES here, your global prefix needs root — prefer a user-level Node like
nvm, or npm config set prefix ~/.npm-global.) --loglevel=error because
npm runs inside a pnpm script here and warns about every pnpm config var it
inherits — pure noise; real errors still print.
npm install -g @microsoft/teams.cli@3.0.2 --loglevel=errornpm's global bin directory is not reliably on PATH (custom prefixes rarely
are), so every step below calls the CLI by its absolute path,
$(npm prefix -g)/bin/teams (stderr of the prefix lookup silenced — same
pnpm-config noise as above). Where this document says to run teams … by
hand, use that path too if plain teams isn't found.
Every teams command is a separate process, so the sign-in must survive into
the next one via the CLI's on-disk token cache. A "libsecret not found —
token cache will be stored unencrypted" warning here is safe to ignore: the
CLI falls back to a plaintext cache file that persists fine, and setup signs
the session out at the end anyway. The login output may
also report "Azure CLI: not installed" — informational only; this flow
creates a Teams-managed bot precisely so the Azure CLI is never needed (it
only matters for --azure bots and the manual portal path). The
step below verifies persistence by re-reading the session from a fresh
process after login. In an interactive terminal the login opens a browser;
on a headless box (SSH) it prints a device code — open
microsoft.com/devicelogin on any machine and enter it. If this step fails,
run teams login then teams status by hand: status must say logged in, or
the cache is not persisting (see Troubleshooting).
"$(npm prefix -g 2>/dev/null)/bin/teams" login && "$(npm prefix -g 2>/dev/null)/bin/teams" status --json 2>/dev/null | grep -q '"loggedIn": true' && printf '=== NANOCLAW SETUP: TEAMS-LOGIN ===\nSTATUS: success\n=== END ===\n'One command registers the Entra app, generates a client secret (Graph can take
~30s to see the new app — the CLI retries), registers a Teams-managed bot, and
uploads the app package to the Teams Developer Portal. It needs the sign-in
from the previous step (AUTH_REQUIRED means run that first). The bot is
always created single-tenant (--sign-in-audience myOrg) — the right default
for a self-hosted assistant, applied without asking; for a bot other
Microsoft 365 tenants can install, set it up manually per
Alternatives.
"$(npm prefix -g 2>/dev/null)/bin/teams" app create --name "{{app_name}}" --endpoint "{{public_url}}/webhook/teams" --sign-in-audience myOrg --jsonThe adapter reads these from .env (set-if-absent — a value you've already
filled in is never overwritten). The pairing matters: SingleTenant requires
TEAMS_APP_TENANT_ID, and a multi-tenant app must instead set
TEAMS_APP_TYPE=MultiTenant with no tenant ID — a mismatch makes the
adapter authenticate against the wrong authority and every message fails with
a 401 from Bot Framework.
TEAMS_APP_ID={{app_id}}
TEAMS_APP_PASSWORD={{app_password}}
TEAMS_APP_TENANT_ID={{app_tenant_id}}
TEAMS_APP_TYPE=SingleTenantThe CLI-created app ships with placeholder icons; this swaps in the NanoClaw mascot (the same PNGs the manual-path package bakes into its zip), so the install dialog below already shows it. Cosmetic — a failure is logged and skipped, never blocking setup. Re-runnable any time while signed in to the Teams CLI:
"$(npm prefix -g 2>/dev/null)/bin/teams" app update {{teams_app_id}} --color-icon setup/assets/teams/color.png --outline-icon setup/assets/teams/outline.png --json || echo "Icon update failed — cosmetic only, continuing."The account signed into the Teams CLI is the account that just created the bot — that human is the wiring target this flow suggests. Its identity comes from the CLI session, so this runs before the sign-out step below:
"$(npm prefix -g 2>/dev/null)/bin/teams" status --json 2>/dev/nullNothing is wired without a confirmed target, and someone is always wired — there is no skip. The account signed into the Teams CLI is often NOT the person setting up NanoClaw, so a no leads to a clarifying choice: wire the logged-in Teams user after all, or a different Teams user by Microsoft Entra object ID. Identities are shown by sign-in name, never a raw ID:
Detected the account that created the bot: {{owner_upn}}. Wiring the assistant to it means its first message arrives in that account's Teams DMs.Wire the assistant to this account?You're currently logged in to Teams as {{owner_upn}}.
- To wire the assistant to this logged-in Teams user, choose "logged-in-account".
- To wire a different Teams user, get their Microsoft Entra object ID — found at entra.microsoft.com > Users > (person) > Overview > Object ID, or Teams admin center > Manage users — and choose "other-account". Once wired, the assistant messages them first.Which Teams user should the assistant be wired to?Paste the Microsoft Entra object ID of the Teams user to wire (a GUID like 00000000-0000-0000-0000-000000000000).Either choice re-enters the exact same path as a yes above — rebind the wiring target and flip the branch, so the link chain below needs no second copy per branch:
printf '{"aad":"%s","wire":"yes"}' "{{target_aad_id}}"echo yesThe app package is already uploaded — no manifest zip, no manual sideload. Tell the user:
Install the bot into Teams:
1. Open {{install_link}} — Teams opens with the app's install dialog. Click Add.
2. If you need the link again later, run: teams app get {{teams_app_id}} --install-link
3. If Teams refuses with a custom-app-upload error, a tenant admin must enable sideloading: Teams Admin Center > Teams apps > Setup policies > Global > "Upload custom apps" = On.
Once the app shows up in your Teams sidebar (or app list), continue.Nothing to do in Teams yet — these are background API calls, and the whole
chain runs only for a confirmed target from
Confirm the wiring target (the detected owner
on a yes, or the provided Entra object ID). Same move as
Slack's conversations.open and Discord's users/@me/channels:
create the bot↔owner 1:1 conversation proactively with the bot's own
credentials, so the assistant messages the human first — nobody has to DM the
bot to bootstrap it. This only works now that the app is installed (the step
above); if Microsoft hasn't finished propagating the install yet, the create
below can fail once — re-running the skill is safe.
First a Bot Framework token from the app credentials:
curl -sf -X POST "https://login.microsoftonline.com/{{app_tenant_id}}/oauth2/v2.0/token" --data-urlencode "grant_type=client_credentials" --data-urlencode "client_id={{app_id}}" --data-urlencode "client_secret={{app_password}}" --data-urlencode "scope=https://api.botframework.com/.default" | jq -er '.access_token'Create the 1:1 conversation (the AAD object id from the CLI session is a
valid member id; smba.trafficmanager.net/teams is the global service URL —
the same default the adapter itself uses):
curl -sf -X POST "https://smba.trafficmanager.net/teams/v3/conversations" -H "Authorization: Bearer {{bot_token}}" -H "Content-Type: application/json" -d '{"bot":{"id":"28:{{app_id}}"},"members":[{"id":"{{owner_aad_id}}","name":"","role":"user"}],"tenantId":"{{app_tenant_id}}","channelData":{"tenant":{"id":"{{app_tenant_id}}"}},"isGroup":false}' | jq -er '.id'The adapter identifies inbound senders by their Bot Framework 29: id, not
the AAD id — the owner must be wired under that handle or their replies
would not be recognized. The conversation was created with exactly one
member (the owner), so its member list is the owner by construction; the
filter only guards against channels that list the bot itself (28: ids).
(Don't select by .aadObjectId here — the field is not reliably present in
this response and its GUID casing varies.)
curl -sf "https://smba.trafficmanager.net/teams/v3/conversations/{{conversation_id}}/members" -H "Authorization: Bearer {{bot_token}}" | jq -er '[.[] | select((.id // "") | startswith("28:") | not)][0] | {id, name: (.name // .givenName // "Teams user")}'Compose the platform id exactly as the adapter encodes thread ids
(teams:{b64url conversation}:{b64url service url}):
node -e 'const c=process.argv[1];const s="https://smba.trafficmanager.net/teams/";console.log("teams:"+Buffer.from(c).toString("base64url")+":"+Buffer.from(s).toString("base64url"))' "{{conversation_id}}"The Microsoft 365 session was only needed to create the bot and identify its
owner — the running adapter authenticates with the app credentials in
.env, never with your account. On a headless box that session is a
plaintext token file, which is worth removing unless you plan more teams …
commands (rotate secret, endpoint update, RSC — each just needs a fresh
teams login, a ~30-second device code):
Sign out of the Teams CLI now? The bot doesn't need this login to run — signing out is recommended on shared or headless boxes, and `teams login` gets you back any time."$(npm prefix -g 2>/dev/null)/bin/teams" logoutRestart the service so it loads the Teams adapter and the credentials you just stored:
bash setup/lib/restart.shOn a fresh create, Link the bot to your account already resolved
everything the wire needs — owner_handle (the owner's 29: id) and
platform_id (the bot↔owner DM). The setup wizard wires automatically from
those and the welcome message lands in the owner's Teams DMs. Applying this
skill outside the wizard? Run the same wire yourself:
pnpm exec tsx scripts/init-first-agent.ts --channel teams --user-id "teams:<owner_handle>" --platform-id "<platform_id>" --display-name "<the human's name>" --agent-name "<assistant name>" --role ownerFallback (re-runs, or the link step failed): with credentials already in
.env the resolve steps are skipped, so there is nothing new to wire — the
first run's wiring still stands. If the install was never wired at all, the
DM-first path always works: DM the bot once ("hi" is fine) — the router
auto-creates the messaging group row in data/v2.db from that first inbound
— then run /init-first-agent (or /manage-channels) with your coding
agent.
If you're in the middle of /setup, return to the setup flow now — it wires
the owner automatically from the resolved values. Otherwise wire per
Finish wiring.
teamsteams:{base64url-conversation-id}:{base64url-service-url} — auto-generated by the adapter from the first inbound activity, not human-readable. Use the auto-created messaging group for wiring.The Credentials flow above always creates a single-tenant bot (only your
Microsoft 365 tenant can install it) — the right default for a self-hosted
assistant, so the skill doesn't ask. For a bot any tenant can install, run
the create by hand with multipleOrgs and store the matching env pairing —
MultiTenant with no tenant ID (the same 401 pairing rule from the
credentials step):
"$(npm prefix -g)/bin/teams" app create --name "YourBot" --endpoint "https://your-domain/webhook/teams" --sign-in-audience multipleOrgs --jsonTEAMS_APP_ID=<CLIENT_ID from the output>
TEAMS_APP_PASSWORD=<CLIENT_SECRET from the output>
TEAMS_APP_TYPE=MultiTenantInstall via the installLink in the output, then continue from
Restart. If this skill already created a single-tenant app,
start over first — see Rotate or recreate credentials in
Troubleshooting.
For tenants where the Teams Developer Portal is blocked. Unlike the CLI path,
the Azure Bot resource in step 3 requires an active Azure subscription.
This is the classic walk; every value it produces maps onto the same .env
keys. Ask the human before creating anything: the app registration name,
single vs multi tenant, a client secret description, and (this path only) a
separate Azure Bot handle.
App registration: in https://portal.azure.com, search "App registrations" → "New registration". Name it (e.g. "NanoClaw"); Supported account types: Single tenant (most common for self-host) or Multi tenant. From the Overview page copy the Application (client) ID and — single tenant only — the Directory (tenant) ID.
Client secret: in the app registration, "Certificates & secrets" → "New client secret" (expires 180 days or longer). Copy the Value now — Azure shows it once (the Value column, not the Secret ID).
Azure Bot resource: search "Azure Bot" → Create. Bot handle: any unique
name; Type of App: must match step 1; Creation type: "Use existing app
registration" with the App ID from step 1. After creating, open the bot →
Configuration and set Messaging endpoint to
https://your-domain/webhook/teams, then Apply.
Enable the Teams channel: Azure Bot resource → Channels → Microsoft Teams → Accept terms → Apply.
Store the credentials in .env (the same 401 pairing rule applies —
SingleTenant needs the tenant ID, MultiTenant must omit it):
TEAMS_APP_ID=<Application (client) ID>
TEAMS_APP_PASSWORD=<client secret Value>
TEAMS_APP_TYPE=SingleTenant
TEAMS_APP_TENANT_ID=<Directory (tenant) ID>Build the app package (manifest + icons, written in-process to
data/teams/teams-app-package.zip — no zip binary needed):
pnpm exec tsx setup/channels/teams-manifest-build.ts --app-id YOUR_APP_ID --url https://your-domainSideload: Microsoft Teams → Apps → Manage your apps → Upload an app → "Upload a custom app" → select the zip → Add.
Continue from Restart.
Or create the bot resource with the Azure CLI instead of the portal:
az group create --name nanoclaw-rg --location eastus
az bot create --resource-group nanoclaw-rg --name nanoclaw-bot --app-type SingleTenant --appid YOUR_APP_ID --tenant-id YOUR_TENANT_ID --endpoint "https://your-domain/webhook/teams"
az bot msteams create --resource-group nanoclaw-rg --name nanoclaw-botBy default the bot only receives messages when @-mentioned. With a CLI-created bot, grant the resource-specific-consent (RSC) permissions directly — no manifest edit, no re-upload; the app version is bumped automatically:
teams app rsc add <teams-app-id> ChannelMessage.Read.Group --type Application
teams app rsc add <teams-app-id> ChatMessage.Read.Chat --type ApplicationThen update/reinstall the app in the team so the new permissions get consented.
(<teams-app-id> is the Teams App ID shown in the install step — recover it
any time with teams app list, or find the app at
https://dev.teams.microsoft.com/apps.)
On the manual path, regenerate the package with RSC baked in and sideload it again (the manifest version is bumped so the upload supersedes the original):
pnpm exec tsx setup/channels/teams-manifest-build.ts --app-id YOUR_APP_ID --url https://your-domain --rscteams status shows whether sideloading is enabled at both tenant
and user level; the login output prints the same check.
Free personal Teams does not support sideloading at all — use a Microsoft 365 Business / EDU / developer tenant.
The login step's sideloading probe is advisory — policy edits can take hours to propagate and the probe has been seen flapping between runs on the same account. The authoritative test is whether the install link's Add actually works; only act on the probe if the install itself refuses.
teams: command not foundThe CLI installed fine but npm's global bin directory isn't on your PATH — a
common state with custom npm prefixes. Find it with npm prefix -g (the
binary is at <prefix>/bin/teams), then either add that directory to PATH or
symlink the binary somewhere already on it. The skill's own steps are immune —
they invoke the absolute path.
AUTH_REQUIRED after a successful sign-inThe sign-in didn't persist: each teams command is a separate process, and
when the CLI's credential store can't load it silently falls back to an
in-memory cache that dies with the login process. Symptom check:
teams status says logged out right after a login succeeded. The known
cause: the CLI was installed as a pnpm workspace dependency — pnpm's
supply-chain policy skips dependency build scripts, so keytar (the CLI's
native credential store) never gets its binary and the whole store fails to
load. Use the global npm install this skill performs — and pnpm uninstall @microsoft/teams.cli if a workspace copy lingers, so teams resolves to
the global one. (The "libsecret not found → stored unencrypted" warning is
NOT this failure — that fallback persists fine and is safe to ignore.)
After fixing, sign in again and confirm teams status shows logged in, then
re-run this skill.
teams app list shows the Teams App ID, then
teams app get <teams-app-id> --install-link.https://<your-domain>/webhook/teams, and your tunnel (e.g.
cloudflared tunnel --url http://localhost:3000) must be forwarding to
this machine's port 3000. Check
with teams app doctor <teams-app-id> (CLI-created bots) or Azure
Bot → Configuration (manual path).grep -i teams logs/nanoclaw.log | tail..env (TEAMS_APP_ID, TEAMS_APP_PASSWORD,
TEAMS_APP_TYPE).Point the bot at the new endpoint:
teams app update <teams-app-id> --endpoint "https://new-domain/webhook/teams"
(manual path: Azure Bot → Configuration → Messaging endpoint).
Unauthorized / 401 from Azure Bot ServiceEither the credential pairing is wrong, or the secret is dead:
TEAMS_APP_TYPE=SingleTenant requires TEAMS_APP_TENANT_ID;
MultiTenant must have no tenant ID set. A mismatch authenticates
against the wrong authority and every send/receive 401s.teams app auth secret create <teams-app-id> (or Azure portal →
Certificates & secrets), update TEAMS_APP_PASSWORD in .env, and restart.The credentials flow skips creation while .env has TEAMS_APP_ID or
TEAMS_APP_PASSWORD — deleting just one line does not make the skill
regenerate it (that would pair a new app with stale keys). To rotate only the
secret, use the 401 section above. To start over completely: delete all
TEAMS_* lines from .env, optionally delete the old app at
https://dev.teams.microsoft.com/apps (CLI path) or in Azure Portal → App
registrations (manual path), then re-run this skill. Re-running
teams app create with old credentials still in .env would otherwise create
a second, orphaned app.
A Teams bot's platform ID is derived from the first inbound activity, so wire the messaging group that the router auto-creates after you DM the bot — don't guess the platform ID. See Finish wiring above.
© nanocoai, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 2 other files in .claude/skills/add-teams of nanocoai/nanoclaw.
Open the folder on GitHubat commit 66f0823
Add Teams next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Add Teams this skillnanocoai/nanoclaw | 31k | — | ~7.2k | Automated safety check: Notes | MIT | |
| Teams Devmicrosoft/teams-sdk | 729 | — | ~1.2k | Automated safety check: Pass | MIT | |
| Vexa MeetingsVexa-ai/vexa | 2.9k | — | ~631 | Automated safety check: Pass | Apache-2.0 | |
| Add Channel Setup Guidenovuhq/novu | 40k | — | ~1.7k | Automated safety check: Pass | Custom licence | |
| Spfx Releasepnp/docker-spfx | 134 | — | ~572 | Automated safety check: Notes | MIT | |
| Soul Interviewldbumble/taskuary | 138 | — | ~1.4k | Automated safety check: Pass | MIT |
microsoft/teams-sdk
A skill your agent uses whenever the user mentions Microsoft Teams in a development context — whether they're building, integrating, configuring, debugging, or just asking questions.
Vexa-ai/vexa
Sends a Vexa bot into a meeting, follows the live transcript and writes meeting notes with speakers, decisions and action items into Obsidian, Notion or Markdown.
novuhq/novu
Add a new chat channel's layer-1 setup guide — the in-dashboard <ChannelSetupGuide that walks a developer through connecting the channel itself (create app/bot → save credentials → install/verify →…
pnp/docker-spfx
Automate SPFx version releases - branch, update files, commit, open PR, then tag after merge
ldbumble/taskuary
Conduct a seven-question adaptive interview and turn the answers into Taskuary's SOUL.md.
sigcli/sigcli
Interact with Microsoft Teams — send and read messages, search conversations, look up people, check calendar, get meeting transcripts, and manage chats.
nanocoai/nanoclaw
Installs or refreshes Iron Proxy and its Iron Control web console for NanoClaw, with a local Docker setup, database, credentials and a human approval bridge.
nanocoai/nanoclaw
Installs or refreshes OneCLI as the gateway provider for NanoClaw, copying the adapter files, registering the provider and running the setup script.
nanocoai/nanoclaw
Drives a web browser from the shell with the agent-browser CLI: open pages, read an element snapshot, click and fill by reference, grab text and screenshots.
nanocoai/nanoclaw
Guides a conversational migration from an OpenClaw install to NanoClaw v2, carrying over identity, channel credentials, scheduled tasks and workspace files.
nanocoai/nanoclaw
Wires up an additional phone number onto an already-installed Dial channel, so one NanoClaw install answers SMS and AI voice calls on more than one line.
nanocoai/nanoclaw
Installs the `dial` CLI and a credential in NanoClaw agent containers so chosen agents can send SMS, place AI voice calls and receive verification codes.
Works with
Add Microsoft Teams channel integration via Chat SDK. An agent skill from nanocoai/nanoclaw. Add Teams is an agent skill from nanocoai/nanoclaw. Add Microsoft Teams channel integration via Chat SDK.
Run `npx skills add nanocoai/nanoclaw --skill add-teams -a claude-code`. Or copy the skill folder (.claude/skills/add-teams in nanocoai/nanoclaw) into .claude/skills/add-teams in your project. Claude Code loads it when a task matches its description.
Run `npx skills add nanocoai/nanoclaw --skill add-teams -a codex`. Or copy the skill folder (.claude/skills/add-teams in nanocoai/nanoclaw) into .agents/skills/add-teams in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add nanocoai/nanoclaw --skill add-teams -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/add-teams, .gemini/skills/add-teams, .github/skills/add-teams and .opencode/skills/add-teams in your project.
Going by SKILL.md and its folder, Add Teams needs the command-line tools its instructions call (npm, pnpm, az and cloudflared) and credentials named TEAMS_APP_PASSWORD and CLIENT_SECRET. Our summary lists: Node.js; A credential in CLIENT_SECRET.
SKILL.md names 4 domains. In commands or code: smba.trafficmanager.net, login.microsoftonline.com and api.botframework.com; the agent is likely to contact these when it follows the instructions. As links in the text: dev.teams.microsoft.com. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found notes only (mentions a .env file), nothing it rates as a warning. It is not a guarantee. Review the folder before installing.
Add Teams is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 7.2k tokens (SKILL.md is roughly 29k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Add Teams: Teams Dev (microsoft/teams-sdk, 729 stars), Vexa Meetings (Vexa-ai/vexa, 2.9k stars), Add Channel Setup Guide (novuhq/novu, 40k stars) and Spfx Release (pnp/docker-spfx, 134 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
nanocoai (a GitHub organization) maintains it in nanocoai/nanoclaw, which has 30,902 GitHub stars. The repository holds 59 skills in this directory. The repository was last updated on October 6, 2026.
Source: nanocoai/nanoclaw on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.