Microsoft Teams
For: tenant administrators and plugin users.
ai.oz0/teams connects Microsoft Teams as a chat channel. Mention the bot in a channel or group chat, or message it in a personal chat, to start a bound agent.
Answers, questions and approvals arrive in the same thread as Adaptive Cards. Mention the bot in a reply to continue. Notification rules can also send waiting approvals to a channel or personal bot chat for a decision on the card.
| Manifest name | teams (hub name ai.oz0/teams) |
| Tool | oz0_teams: normalize (the event source), post (the channel), send (an agent posts a message) |
| Event source and channel | teams, on POST https://<edge>/events/<tenant>/teams (the bot's messaging endpoint) |
| How deliveries are checked | The edge checks each delivery's bearer token (jwt scheme) before a node sees it |
| Tenant secrets | TEAMS_APP_ID (the bot's app ID, its tokens' audience) and TEAMS_CLIENT_SECRET (read by the edge only), both from Connect |
| Connection | teams: your own Azure Bot in your Microsoft Entra tenant, in three checked steps, and its Teams app package; posts get a token the edge mints for each call |
| Egress | the Bot Framework's service hosts for Teams (smba.trafficmanager.net, and GCC's) |
What it needs
- A public HTTPS edge. The Bot Framework sends the bot's activities to its messaging endpoint, so your edge's
HTTPS port must be reachable from the internet with a certificate a public CA signed. An edge only your network
reaches cannot take Teams' activities: put a reverse proxy or a load balancer with a public address in front of
https://<edge>/events/<tenant>/teamsalone. Events over an outbound connection do not cover Teams. - Your own Azure Bot, single tenant, in your organization's Microsoft Entra tenant: an Azure subscription you may create a resource in.
- A Teams admin, or a person allowed to upload custom apps, to upload the app package.
Create your own bot, about ten minutes
Every cluster uses a bot of its own, since the Bot Framework sends a bot's activities to one endpoint and yours go to your edge. Connect walks you through it, checking each step before the next:
oz0 plugin install hub:ai.oz0/teams
oz0 connection start teams --open --wait 30m
Or in the console: Credentials, Connections, teams, Connect. The edge's page for each step:
- app. Create an Azure Bot in the Azure portal opens the portal's new Azure Bot. Give it a handle, pick
Single Tenant as its type and Create new Microsoft App ID, and create it. Under the bot's
Settings, Configuration, paste its Microsoft App ID and App Tenant ID on the edge's page. The app ID is
no secret, but the edge also seals it as
TEAMS_APP_ID: every token the Bot Connector sends must be for it. - secret. On the same page, Manage Password opens the app registration's Certificates & secrets: make a New client secret and paste its Value (not its ID) on the edge's page. Before it seals anything, the edge asks your tenant's token endpoint for a Bot Framework token with the pasted ID and secret; a wrong secret is caught here, and nothing is saved.
- install. Copy the Messaging endpoint the page shows into the bot's Configuration and save. Under
Channels, add Microsoft Teams. Download the Teams app package the page offers (
oz0-teams-app.zip: its manifest with the bot's ID, the personal, team and group chat scopes, and icons), and upload it in Teams (Apps, Manage your apps, Upload an app), or have your Teams admin publish it to your organization. Add the app to a team, or open a chat with it: Teams tells the bot, and the page goes on once that activity passes the edge's check.
The package is also on the Connections tab (Download oz0-teams-app.zip) and in the CLI:
oz0 connection package teams
Then bind the channel and map people:
oz0 channel bind helpdesk --channel teams --agent support-triage --room "19:[email protected]"
oz0 channel member add teams 29:1aBc... alice
A channel's ID is in its link (Get link to channel, the part after /channel/, URL-decoded). People are best
linked by themselves: each makes a code under Chat channels in the console (or oz0 channel link) and sends
link <code> to the bot in a personal chat. That chat is where notification rules
reach them.
Microsoft's commercial cloud only (and GCC, which uses it). GCC High and DoD do not work yet: their Bot Connector
signs with another issuer and key set (https://api.botframework.us, login.botframework.azure.us), which the
plugin's event source does not accept, so every activity would be refused.
The edge sends the bot's client secret only to Microsoft Entra's token endpoint,
https://login.microsoftonline.com/<tenant ID>/oauth2/v2.0/token, which the plugin names literally: no param of the
connection, and no other admin, can point it elsewhere. Nothing but that grant reads the secret, and no node ever gets
it.
In Teams
@oz0 what broke last night?in a channel the app is in starts the binding's default agent; the bot answers in the post's thread. In a group chat, mention it the same way; in a personal chat, just write.- A reply that mentions the bot goes on with the conversation, or answers an open question. Teams sends a bot only the channel messages that mention it.
- Questions are cards with a button per choice (more than five choices are one list and Choose, since Teams draws six buttons a card), or a text box and Answer.
Approvals are cards with Approve and Deny, and Edit and approve with the call's fields as the card's inputs (text, number, a choice, several choices sent by their positions, so an option may hold a comma, a date, a date and a time in UTC (a value with another offset is shown converted, so an untouched field stays the same instant), a toggle) in the standard field set; arguments the card cannot show keep their values, and the card names them.
An optional choice or date can be left empty. A typed question is a card with its fields and Answer.
Open in OZ opens the job's page in the console (Action.OpenUrl).
Only mapped people with the operator role decide (and the approval's deciders, when it names them); a click on any other copy of a card's buttons decides nothing.
- An answer that matches its agent's contract and is key-values comes as a FactSet, a table as a FactSet per row (at most ten), each value escaped as an agent's text is (results as cards).
- Every card starts with the tenant's AI label, and its message carries Teams' own AI-generated label too, while the label is on. Nothing an agent writes mentions anyone: the plugin sends no mentions at all.
Teams shows a bot's channel message to everyone in the channel, so a message meant for one person (why a request was refused) goes to the thread as well.
The edge refuses a delivery whose token is not signed by a key the Bot Connector publishes, is not from
https://api.botframework.com, is not for your bot's app ID, or has expired (five minutes of clock skew). The
plugin refuses an activity whose serviceUrl is not the one its token names, or whose channel the signing key does
not endorse (a key that lists no endorsements endorses none), and the edge posts replies only to a serviceUrl on the
Bot Framework's hosts the plugin names.
For agents
An agent that lists oz0_teams.send may post to a conversation: send(conversation, text, thread?, service_url?),
with the conversation's ID and, in a channel, the thread's first message's ID. The message carries the tenant's AI
label. Give agents send, never post.
A rate limit (429) or the service's 5xx is reported as retryable, and the platform posts again after the wait Teams asked for. 401 or 403 is final: the bot is not in the conversation, or its app is not installed there.
Settings
| Variable | What it does |
|---|---|
TEAMS_SERVICE_HOSTS | The service hosts posts may go to, comma separated (the four above unless set) |
TEAMS_CA_FILE | Another CA bundle for those hosts, such as a proxy of your own; a path in the plugin, or absolute |
Troubleshooting
| What you see | Why, and what to do |
|---|---|
secret fails with Invalid client secret | You pasted the secret's ID, or a secret of another app registration. Make a new one and paste its Value. |
secret fails with AADSTS700016 or AADSTS90002 | The app ID or the tenant ID is not this tenant's: start again from app. |
| install waits | Teams has not sent the bot anything yet: is the messaging endpoint saved, the Teams channel added, and the app added to a team or a chat? Is the edge reachable from the internet (try the endpoint from outside: a POST without a token answers 401)? |
| install says the delivery was refused | The token was not for this bot's app ID (another bot's endpoint points here), or the edge's clock is off by more than five minutes. |
| The bot does not answer in a channel | It answers mentions only; is the channel bound (oz0 channel list)? oz0 channel threads shows what it did. |
| Cards come without buttons | A Teams client older than Adaptive Cards 1.4 shows Action.Submit instead, which works the same. |
Legal
What the plugin's name, these pages and the default app name may say about Microsoft and its products, and what Microsoft's developer terms ask of a bot, is an open question for the project's lawyers. Whether the AI label on Teams messages meets a particular law is not something this page decides.
A message an agent sends asks by default
The plugin's suggested policy asks a person before each send ("the plugin asks before it sends a message"), as a
function that does not say whether it destroys anything did already. The install writes it as a rule of the tenant's
tool policy, marked as the plugin's. An admin who lets an agent post on its own switches it, with a reason:
oz0 plugin policy teams --set oz0_teams.send=allow --reason "...". The channel's own replies (post) are the platform's
and not asked about.
Discord
The verified plugin ai.oz0/discord - Discord's /oz0 starts your agents, and answers, questions and approvals come back with buttons.
ai.oz0/ai-spend
What the company spends at Anthropic and OpenAI directly, read from their admin APIs on your own nodes and shown by team beside Orchestrator Zero's own spend.