Connect a single-tenant Teams bot in Microsoft's public cloud using the Microsoft 365 Agents SDK. You need a tenant that permits installing your Teams app, an Azure Bot registration, and a public HTTPS endpoint. A tenant administrator may need to approve custom app upload or installation.
-
Create a single-tenant Microsoft Entra application and a client secret. Record its Application (client) ID, Directory (tenant) ID, and the secret value (not the secret ID). Create an Azure Bot resource using this app ID and tenant; enable its Microsoft Teams channel. Microsoft's Agents SDK quickstart describes the registration options.
-
From this repository, install and start the echo server:
python -m pip install -e '.[teams]' export TEAMS_APP_ID='application-client-id' export TEAMS_TENANT_ID='directory-tenant-id' export TEAMS_APP_PASSWORD='client-secret-value' python examples/teams_echo.py
It listens on
127.0.0.1:3978. Forward a public HTTPS URL through a development tunnel to this port, or deploy behind an HTTPS reverse proxy. For containers, setTEAMS_HOST=0.0.0.0;TEAMS_PORToverrides the port. -
Set the Azure Bot Messaging endpoint to
https://YOUR_HOST/api/messages. Keep bearer authorization enabled. Anonymous curl requests are expected to return 401; they do not test a Teams conversation. -
In the Teams Developer Portal, create a Teams app and add a bot capability using the same app ID. Enable Personal scope first; optionally add Team and Group Chat scopes. Complete the portal's required app metadata/icons, download the app package, and upload it through Teams Apps → Manage your apps → Upload an app (subject to tenant policy). Install it for yourself or in your test team.
-
Send
helloin the bot's personal chat. Expecthelloback. In a team/channel or group chat, send@YourBot hello. Stop the local server with Ctrl+C.
Personal chats accept ordinary messages. Group chats and channels require a bot mention on each incoming message. Personal and group chats retain their shared conversation; channel replies preserve the original root thread. Only the bot's own mention is removed. Sender IDs remain Teams IDs scoped by the configured tenant; display names do not authorize access.
Teams(app_id=..., app_password=..., tenant_id=...) validates Microsoft JWTs,
audience/issuer, the configured tenant, and HTTPS Microsoft service URLs. This
adapter supports public-cloud single-tenant client-secret authentication. It does
not add Graph chat-reading permissions, tenant-wide message subscriptions,
anonymous emulator access, or sovereign-cloud configuration.
For an existing aiohttp host, use teams.create_app() and manage its runner
yourself. This keeps the authentication middleware and /api/messages route.
Bind a receiver before serving requests and call await teams.close() at
shutdown. Use either your host or teams.run(), not both. Supplying adapter=
injects a Microsoft CloudAdapter for tests or a host-owned connector factory;
the adapter's turn-error handler is replaced so errors propagate rather than
posting diagnostics to the conversation.
Normal messages are acknowledged with HTTP 202 after admission to bounded background work; replies use a fresh continuation context, not an expired HTTP turn. A full queue returns 503 before admission. See delivery semantics.
Replies use plain text and accept up to 24,000 UTF-8 bytes, leaving room for the activity envelope below Teams' message limit. Split larger answers into separate replies. Incoming file/card metadata is retained, but download, upload, card actions, rich replies, and native-history capabilities are not implemented here.
| Symptom | Check |
|---|---|
| HTTP 401 from real Teams traffic | Azure Bot app ID, tenant, client-secret value/expiry, and proxy forwarding of Authorization. |
| Bot not available in Teams | Teams channel enabled in Azure Bot; matching manifest bot ID; tenant custom-app policy. |
| DM works but channel does not | Install the app in that team and mention it on each message. |
No traffic at /api/messages |
Public HTTPS endpoint and tunnel/proxy path point to this process. |
| Send fails after setup worked | Rotate an expired client secret and restart; verify the app remains installed. |
Local tests use Microsoft's real activity, continuation, and routing code with fake connectors, plus unauthenticated-request rejection. They do not validate your Azure registration. The first live Teams reply above verifies that setup.