Mmcp.market

add-teams skill

by nanocoai·nanocoai/nanoclaw·31k stars·MIT

Add Microsoft Teams channel integration via Chat SDK.

A100/100content scan

Is the add-teams skill safe?

Clean: nothing in its files matched our rules. We read 3 files in the folder on 2026-09-28.

No findings.

Install the add-teams skill

A skill is a folder. Copy it into your agent's skills folder and the agent loads it when the task matches its description.

git clone --depth 1 https://github.com/nanocoai/nanoclaw.git /tmp/nanoclaw
mkdir -p ~/.claude/skills
cp -r /tmp/nanoclaw/.claude/skills/add-teams ~/.claude/skills/add-teams
available in every project

In the Claude apps, zip the folder and upload it from the Skills settings. The folder on GitHub

The instructions your agent would load

SKILL.md as published, without the frontmatter. Read it on GitHub

Add Microsoft Teams Channel

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.

Apply

1. Copy the adapter and its registration test

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.ts

2. Register the adapter

Append 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';

3. Install the adapter package

Pinned to an exact version — the supply-chain policy rejects ranges and latest:

@chat-adapter/teams@4.29.0

4. Build and validate

Build first: it guards the typed createChatSdkBridge(...) core call and proves the dependency is installed. Then run the one integration test.

pnpm run build
pnpm exec vitest run src/channels/teams-registration.test.ts

teams-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.

Credentials

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.

Check for existing credentials

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 no

Before 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.

Public URL

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.com

App name

One 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.

Install the Teams CLI

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=error

npm'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.

Sign in to Microsoft 365

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'

Create the bot

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 --json

Store the credentials

The adapter reads these from .env (set-if-absent — a value you've already filled in is never overwritten). The pairing matters: SingleTenant requires TEAMSAPPTENANTID, and a multi-tenant app must instead set TEAMSAPPTYPE=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=SingleTenant

Set the app icons

The 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."

Who owns this bot

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/null

Confirm the wiring target

Nothing 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}}"

More skills from nanocoai/nanoclaw

  • Aadd-anydocAdd local office-document-to-Markdown conversion to NanoClaw agent containers with the pinned Firecrawl AnyDoc CLI. Use when agents need to read attached Word, PowerPoint, Excel, OpenDocument, RTF, EPUB, CSV, or text-based PDF files without uploading them to a hosted parser.
  • Aadd-atomic-chat-toolAdd Atomic Chat MCP server so the container agent can call local models served by the Atomic Chat desktop app via its OpenAI-compatible API.
  • Fadd-clidashAdd clidash — a zero-dependency, read-only web dashboard that derives its tabs and tables at runtime from any CLI that lists resources as JSON. Ships pre-wired for NanoClaw's ncl CLI (agent groups, sessions, channels, users, roles), plus message-activity charts, a log tail, and a read-only file viewer for group skills/CLAUDE.md/profiles.
  • Aadd-codexUse Codex (OpenAI's codex app-server) as a full agent provider — planning, tool orchestration, MCP tools, server-side history, session resume — alongside or instead of Claude. ChatGPT subscription or OpenAI API key, vault-only via the selected gateway. Per-group via `ncl groups config update --provider codex`. Distinct from using OpenAI as an MCP tool (where Claude remains the planner).
  • Aadd-dashboardAdd a monitoring dashboard to NanoClaw. Installs @nanoco/nanoclaw-dashboard and a pusher that sends periodic JSON snapshots.
  • Aadd-deltachatAdd DeltaChat channel integration via @deltachat/stdio-rpc-server. Native adapter — no Chat SDK bridge. Email-based messaging with end-to-end encryption.
  • Cadd-dialAdd Dial channel integration — a real phone number for SMS and AI voice calls via the Dial platform (getdial.ai). Native adapter — no Chat SDK bridge.
  • Aadd-dial-numberAdd another phone number to an existing Dial channel — a second (or third) public line for the agent, so one NanoClaw install answers SMS and AI voice calls on multiple numbers. Use when Dial is already installed and the operator wants an additional number (e.g. a personal line plus a support line). Requires the Dial channel to already be installed (see /add-dial).
  • Aadd-dial-toolGive chosen NanoClaw agents a real phone number as a container tool — the `dial` CLI baked into the agent image plus OneCLI credential injection for api.getdial.ai, scoped per agent, so the agents you pick can send SMS, place AI voice calls, and receive verification codes from inside the sandbox. Independent of the Dial channel; idempotent; re-run to change which agents may use it. Use when the user wants agents to text, call, or run `dial …` from a chat, without wiring Dial as a messaging channel.
  • Aadd-discordAdd Discord bot channel integration via Chat SDK.
  • Aadd-emacsAdd Emacs as a channel. Opens an interactive chat buffer and org-mode integration so you can talk to NanoClaw from within Emacs (Doom, Spacemacs, or vanilla). Local HTTP bridge — no bot token or external service needed.
  • Aadd-gchatAdd Google Chat channel integration via Chat SDK.

All agent skills → · MCP servers