Skip to content

feat(skills): add setup-teams-channel skill - #11

Draft
jerelvelarde wants to merge 2 commits into
mainfrom
feat/setup-teams-channel-skill
Draft

feat(skills): add setup-teams-channel skill#11
jerelvelarde wants to merge 2 commits into
mainfrom
feat/setup-teams-channel-skill

Conversation

@jerelvelarde

Copy link
Copy Markdown
Contributor

Companion to #7. A repository-owned, browser-first skill for the provider half of getting a locally running Channels agent to answer in Microsoft Teams: the Azure Bot and its Entra app, the tenant-wide Graph consent, the Teams app package, and the install.

Draft, deliberately. The guardrails have not been validated against a completed Teams install — see What is unverified at the bottom. I would rather this sit in draft than borrow the credibility #7 earned by actually being tested.

Where the content comes from

Every claim was read from the Teams setup wizard shipped on CopilotKit/Intelligence main as of 2026-08-03 (apps/app-frontend/react-shell/src/channels/teams-guided-adapter-setup.tsx and teams-credential-fields.tsx), not derived from memory of how Teams bots usually work:

  • the seven-step sequence and its order
  • the three credentials — Client ID, Tenant ID, client secret — and that all three are write-only after setup, replaceable but never readable
  • the messaging endpoint path /api/channels/adapters/teams/messages
  • the Files.ReadWrite.All application permission and its Grant admin consent requirement
  • the ChannelMessage.Read.Group resource-specific consent and exactly what changes without it
  • the Teams bot reaction API as a required preview dependency

Two things the skill does that the wizard does not

1. Phase 0 checks access before building anything.

Two of the seven steps need privileges most developers in most tenants do not have, and both fail late — after an Entra app already exists and credentials have been pasted:

Gate Why it blocks Who can clear it
An Azure subscription in the directory An Azure Bot is a billed Azure resource; zero subscriptions means nowhere to create it Whoever holds Azure billing
Ability to grant tenant-wide admin consent Step 4 grants Files.ReadWrite.All, an application-wide Graph permission Global Admin / Privileged Role Admin / Cloud App Admin

This was verified against a real tenant, not theorised: Subscriptions: 0 of 0 and a blank Your Role: in Roles & admins. Notably, directory ownership does not clear the first one — Azure subscriptions are a billing artifact separate from Entra roles, so a Global Administrator with no subscription is still stuck at step 1. The skill says so explicitly, because it is a natural thing to assume.

Phase 0 also records the inverse, so agents stop treating non-blockers as blockers: registering an Entra app is usually permitted for all members via the tenant's Users can register applications setting, and sideloading needs only Upload a custom app under Teams → Apps → Manage your apps, which is independent of any admin role.

And one operational note that cost real time to discover: Entra role assignments do not appear until the session holds a fresh token, so a freshly granted role reads as blank until sign-out and back in.

2. It names the ChannelMessage.Read.Group symptom.

That permission is resource-specific consent, granted at install, not in Entra. Granted, the Channel receives ambient unmentioned channel messages. Not granted, the bot receives only what Teams routes directly to it — mentions and personal chats.

So "the bot replies when I @-mention it but ignores everything else" is the expected shape of a correctly installed bot missing that consent. Without this written down it reads as a delivery bug, and the debugging goes to entirely the wrong place.

Consistent with the CLI deferral

The skill makes no reference to the copilotkit channels commands and routes to the Intelligence dashboard throughout, matching setup-slack-channel. Per Ben Taylor on 2026-08-03, those commands defer to the web app until they have more real-world usage. channels-flow.ts was used only as a source for what the app package and client id must contain — never as developer-facing instructions.

Layout

.agents/skills/setup-teams-channel/
├── SKILL.md
└── references/azure-bot-and-entra.md

.claude/skills/setup-teams-channel is a symlink (git mode 120000) to the canonical copy, matching the existing setup-slack-channel pattern. AGENTS.md gains a table row and the symlink note now covers both skills.

Trigger boundaries

Three skills now touch this area, so the description is scoped to avoid the collision tracked in #10:

  • this skill — no Teams app exists yet; create one and connect a local checkout
  • setup-slack-channel — the same, for Slack
  • copilotkit-channels (in CopilotKit/CopilotKit/skills) — the code half; assumes the provider app exists

The description names the other two and hands off. Note the third lives in another repo, so the full three-way split completes alongside CopilotKit/CopilotKit#6340.

What is unverified

The ordering and guardrails have not been exercised against a completed Teams install. #7's authority came from three scenarios run with fresh agents without the skill to establish a baseline, then re-run with it. This skill has had none of that, because the Azure subscription gate blocked validation. The skill states this in its own closing section rather than leaving a reader to assume parity with #7.

Reviewers should treat the sequence as sound but provisional, and the prohibitions as predictions.

The prediction I would most like tested. #7's hardest prohibition exists because two of three baseline agents independently invented the direct-adapter escape hatch under pressure. Teams has a worse version of that trap, and it is one we ship: examples/teams in the CopilotKit monorepo is a working Teams demo built on the self-hosted @copilotkit/channels/teams adapter, explicitly "testable in the Microsoft 365 Agents Playground with no Microsoft credentials." An agent stuck at the Azure subscription gate is one search away from it. That pivot is not something it has to invent — it is a committed, working repo that appears to solve the exact problem. The prohibition is already written; whether it holds is the first thing a baseline run should try to break.

Follow-ups, not blockers

  • Validate end to end once a tenant with an Azure subscription exists. An M365 Developer Program sandbox tenant clears both Phase 0 gates at once and is a cleaner room than a tenant that already runs a production bot.
  • The reference set is deliberately thin — one file, covering the Azure/Entra phase. feat(skills): add setup-slack-channel skill #7's Slack equivalent has six, grown from a real run. This one should grow the same way, from evidence, rather than being padded now.

A repository-owned, browser-first skill for the provider half of getting a
locally running Channels agent to answer in Microsoft Teams: the Azure Bot and
its Entra app, the tenant-wide Graph consent, the Teams app package, and the
install.

Grounded in the Teams setup wizard shipped on CopilotKit/Intelligence main as of
2026-08-03 — the seven-step sequence, the three write-only credentials, the
/api/channels/adapters/teams/messages endpoint path, the Files.ReadWrite.All
consent requirement, the ChannelMessage.Read.Group behaviour split, and the
reaction preview dependency were all read from that wizard rather than assumed.

Deliberately makes no reference to the `copilotkit channels` commands: per Ben
Taylor on 2026-08-03, those defer to the web app until they have more real-world
usage. The skill routes to the Intelligence dashboard throughout, matching
setup-slack-channel.

Two things this skill does that the wizard does not:

- Phase 0 checks access before building anything. Two of the seven steps need
  privileges most developers lack — an Azure subscription for the Azure Bot, and
  an admin role to grant tenant-wide consent — and both fail late, after an
  Entra app already exists. Verified against a real tenant that had neither.
- Names the ChannelMessage.Read.Group symptom explicitly. A bot that answers
  mentions and ignores ordinary channel messages is a correctly installed bot
  without resource-specific consent, not a delivery failure.

The guardrail section is marked unverified: unlike setup-slack-channel, whose
prohibitions came from observed agent failures against a live setup, this has
not been exercised against a completed Teams install. The Azure subscription
gate blocked validation.

.claude/skills/setup-teams-channel is a symlink to the canonical copy, matching
the existing setup-slack-channel pattern.
Drove the whole provider-side flow in a live tenant on 2026-08-03. Two of the
skill's claims were wrong, one was right, and several new facts surfaced. All of
it is now grounded in observation rather than in the dashboard's happy path.

Wrong, and removed:

- Phase 0 no longer stops on "no Azure subscription". The Teams Developer Portal
  (Tools > Bot management > New bot) creates the bot, its Entra app registration,
  the client secret, and the messaging endpoint from a single form field with no
  subscription at all. Proven in a tenant holding zero Azure subscriptions. The
  previous gate would have halted a run that can actually succeed, which is the
  worst possible defect in a skill an agent is told to trust.
- Step 1 now routes to the Developer Portal and explicitly says to ignore the
  dashboard's "Open Azure portal" button.

Right, and kept as gate 1:

- Tenant-wide admin consent for Files.ReadWrite.All. This is what actually
  blocked the run, and it blocks late: bot, endpoint, credentials and Channel all
  succeed, then the adapter attach fails.

New, from the run:

- A "When the adapter reports teams_setup_failed" section. Its own remediation
  text ("Reconnect the Teams app") points away from the cause; the real
  diagnostic order is client ID, Teams channel, endpoint, then API permissions.
  In the observed case the first three passed and API permissions was empty.
- The bot's ID is the Entra app's Application (client) ID. Confirmed, which
  removes a guess the earlier draft flagged as unverified.
- Microsoft Teams is enabled by default on a Developer-Portal-created bot.
- A Developer-Portal app starts with no API permissions, so step 4 is an
  addition, not a confirmation.
- Phase 0 gate 2: the org-wide Channel cap. It is org-wide rather than
  per-project, so a new project does not help, and a capped Create channel button
  is indistinguishable from a disabled one — no disabled attribute, silently
  no-ops. Read the "N of M used" counter. Worded to survive the cap changing.
- Deleting another provider's Channel to free a slot trades one working setup for
  another, and the credentials on the deleted side are write-only.

Provenance now separates what was observed from what is still read from source:
the ready()/setup_required trap, the ChannelMessage.Read.Group split and the
reaction preview dependency remain unverified, because the consent gate stopped
the run before a real mention could be answered.

references/azure-bot-and-entra.md renamed to
references/bot-registration-and-permissions.md to match.
@jerelvelarde

Copy link
Copy Markdown
Contributor Author

Drove the whole provider-side flow in a live tenant today (d4089b3). The headline: two of this skill's original claims were wrong, and finding that out is the most useful thing that happened.

The Azure requirement does not exist

The skill — and the dashboard's Teams step it was derived from — routed step 1 through portal.azure.com, where a bot is an Azure Bot: a billed resource needing a subscription. Phase 0 therefore told an agent to stop when there was no subscription.

That was wrong. Developer Portal → Tools → Bot management → New bot asks for a name and nothing else — no subscription, no resource group, no region, no pricing tier — and produces the bot, its Entra app registration, the client secret, and the messaging endpoint. Done in a tenant holding zero Azure subscriptions.

So the original gate would have halted a run that can actually succeed. That is the worst kind of defect here: it was also the section I'd argued hardest for, so it was the one most likely to be trusted. Removed, and step 1 now says explicitly to ignore the dashboard's Open Azure portal button.

This is a product finding as much as a skill fix. If Developer Portal is the shorter path, the dashboard is sending everyone through billing they may not control.

The consent gate is real, and it is what actually blocked the run

Files.ReadWrite.All + Grant admin consent is now Phase 0 gate 1, because it is what stopped us — and it stops you late. Bot, endpoint, credentials, Channel: all succeeded. The adapter attach is where it surfaced, as teams_setup_failed.

Better than either gate being right: both are now evidence-backed rather than inferred.

New section: when the adapter reports teams_setup_failed

The failure's own remediation text — "Reconnect the Teams app and complete setup again to restore delivery"points away from the cause. It reads as though the Teams app or its install is broken. It usually isn't.

The diagnostic order that actually worked, cheapest first: client ID → Teams channel enabled → endpoint saved → API permissions → secret. In the observed case the first three all passed and API permissions was completely empty, with Grant admin consent greyed out. An empty permissions list plus a blank Your Role: is the signature.

Other facts the run settled

  • The bot's ID is the Entra app's Application (client) ID. The previous draft flagged this as an unverified guess; confirmed against Entra → App registrations → Owned applications.
  • Microsoft Teams is enabled by default on a Developer-Portal-created bot.
  • A Developer-Portal app starts with no API permissions at all, so step 4 is an addition rather than a confirmation.
  • New Phase 0 gate 2 — the org-wide Channel cap. It is org-wide, not per-project, so creating a new project does not help. Two traps recorded: a capped Create channel button is indistinguishable from a disabled one — greyed, silently no-ops, and carries no disabled attribute, so the accessibility tree won't tell you either; read the N of M used counter. And deleting another provider's Channel to free a slot trades one working setup for another, with the deleted side's credentials being write-only and needing re-issue.

The cap wording is deliberately generic. Caps vary and this one is being lifted, so pinning a number would have made the skill stale within a day — the exact failure mode that produced #9.

Provenance now separates observed from read

Still unverified, and the skill says so: the ready() / setup_required trap, the ChannelMessage.Read.Group behaviour split, and the reaction preview dependency. The consent gate stopped the run before a real mention could be answered, so gates 1 and 3 of "Done means three things" were never exercised.

Staying in draft until a mention gets a real reply. The provider-side route is now observed rather than assumed, which is a real step up, but the guardrails still have not been tested against an agent under pressure the way setup-slack-channel's were — and that is where this skill's value ultimately has to come from.

Also renamed

references/azure-bot-and-entra.mdreferences/bot-registration-and-permissions.md, since Azure is no longer the route.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant