diff --git a/README.md b/README.md
index f593e26ec..85fe2936c 100644
--- a/README.md
+++ b/README.md
@@ -276,6 +276,7 @@ Connect nanobot to your favorite chat platform. Want to build your own? See the
| **Email** | IMAP/SMTP credentials |
| **QQ** | App ID + App Secret |
| **Wecom** | Bot ID + Bot Secret |
+| **Microsoft Teams** | App ID + App Password + public HTTPS endpoint |
| **Mochat** | Claw token (auto-setup available) |
@@ -861,6 +862,56 @@ nanobot gateway
+
+Microsoft Teams (MVP — DM only)
+
+> Direct-message text in/out, tenant-aware OAuth, conversation reference persistence.
+> Uses a public HTTPS webhook — no WebSocket; you need a tunnel or reverse proxy.
+
+**1. Install the optional dependency**
+
+```bash
+pip install nanobot-ai[msteams]
+```
+
+**2. Create a Teams / Azure bot app registration**
+
+Create or reuse a Microsoft Teams / Azure bot app registration. Set the bot messaging endpoint to a public HTTPS URL ending in `/api/messages`.
+
+**3. Configure**
+
+```json
+{
+ "channels": {
+ "msteams": {
+ "enabled": true,
+ "appId": "YOUR_APP_ID",
+ "appPassword": "YOUR_APP_SECRET",
+ "tenantId": "YOUR_TENANT_ID",
+ "host": "0.0.0.0",
+ "port": 3978,
+ "path": "/api/messages",
+ "allowFrom": ["*"],
+ "replyInThread": true,
+ "mentionOnlyResponse": "Hi — what can I help with?",
+ "validateInboundAuth": true
+ }
+ }
+}
+```
+
+> - `replyInThread: true` replies to the triggering Teams activity when a stored `activity_id` is available.
+> - `mentionOnlyResponse` controls what Nanobot receives when a user sends only a bot mention (`Nanobot`). Set to `""` to ignore mention-only messages.
+> - `validateInboundAuth: true` (recommended for production) enables inbound Bot Framework bearer-token validation (signature, issuer, audience, lifetime, `serviceUrl`). **Default is `false`** — set explicitly to `true` for production deployments.
+
+**4. Run**
+
+```bash
+nanobot gateway
+```
+
+
+
## 🌐 Agent Social Network
🐈 nanobot is capable of linking to the agent social network (agent community). **Just send one message and your nanobot joins automatically!**
diff --git a/docs/MSTEAMS.md b/docs/MSTEAMS.md
deleted file mode 100644
index 285553e51..000000000
--- a/docs/MSTEAMS.md
+++ /dev/null
@@ -1,68 +0,0 @@
-# Microsoft Teams (MVP)
-
-This repository includes a built-in `msteams` channel MVP for Microsoft Teams direct messages.
-
-## Current scope
-
-- Direct-message text in/out
-- Tenant-aware OAuth token acquisition
-- Conversation reference persistence for replies
-- Public HTTPS webhook support through a tunnel or reverse proxy
-
-## Not yet included
-
-- Group/channel handling
-- Attachments and cards
-- Polls
-- Richer Teams activity handling
-
-## Example config
-
-```json
-{
- "channels": {
- "msteams": {
- "enabled": true,
- "appId": "YOUR_APP_ID",
- "appPassword": "YOUR_APP_SECRET",
- "tenantId": "YOUR_TENANT_ID",
- "host": "0.0.0.0",
- "port": 3978,
- "path": "/api/messages",
- "allowFrom": ["*"],
- "replyInThread": true,
- "mentionOnlyResponse": "Hi — what can I help with?",
- "validateInboundAuth": false,
- "restartNotifyEnabled": false,
- "restartNotifyPreMessage": "Nanobot agent initiated a gateway restart. I will message again when the gateway is back online.",
- "restartNotifyPostMessage": "Nanobot gateway is back online."
- }
- }
-}
-```
-
-## Behavior notes
-
-- `replyInThread: true` replies to the triggering Teams activity when a stored `activity_id` is available.
-- `replyInThread: false` posts replies as normal conversation messages.
-- If `replyInThread` is enabled but no `activity_id` is stored, Nanobot falls back to a normal conversation message.
-- `mentionOnlyResponse` controls what Nanobot receives when a user sends only a bot mention such as `Nanobot`.
-- Set `mentionOnlyResponse` to an empty string to ignore mention-only messages.
-- `validateInboundAuth: true` enables inbound Bot Framework bearer-token validation.
-- `validateInboundAuth: false` leaves inbound auth unenforced, which is safer while first validating a new relay, tunnel, or proxy path.
-- When enabled, Nanobot validates the inbound bearer token signature, issuer, audience, token lifetime, and `serviceUrl` claim when present.
-- `restartNotifyEnabled: true` enables optional Teams restart-notification configuration for external wrapper-script driven restarts.
-- `restartNotifyPreMessage` and `restartNotifyPostMessage` control the before/after announcement text used by that external wrapper.
-
-## Setup notes
-
-1. Create or reuse a Microsoft Teams / Azure bot app registration.
-2. Set the bot messaging endpoint to a public HTTPS URL ending in `/api/messages`.
-3. Forward that public endpoint to `http://localhost:3978/api/messages`.
-4. Start Nanobot with:
-
-```bash
-nanobot gateway
-```
-
-5. Optional: if you use an external restart wrapper (for example a script that stops and restarts the gateway), you can enable Teams restart announcements with `restartNotifyEnabled: true` and have the wrapper send `restartNotifyPreMessage` before restart and `restartNotifyPostMessage` after the gateway is back online.
diff --git a/nanobot/channels/msteams.py b/nanobot/channels/msteams.py
index 2987b03f8..06b707f81 100644
--- a/nanobot/channels/msteams.py
+++ b/nanobot/channels/msteams.py
@@ -32,7 +32,10 @@ from nanobot.channels.base import BaseChannel
from nanobot.config.paths import get_workspace_path
from nanobot.config.schema import Base
-MSTEAMS_AVAILABLE = importlib.util.find_spec("jwt") is not None
+MSTEAMS_AVAILABLE = (
+ importlib.util.find_spec("jwt") is not None
+ and importlib.util.find_spec("cryptography") is not None
+)
if TYPE_CHECKING:
import jwt
diff --git a/tests/test_msteams.py b/tests/test_msteams.py
index 96020adec..67ffb8531 100644
--- a/tests/test_msteams.py
+++ b/tests/test_msteams.py
@@ -52,6 +52,9 @@ class FakeHttpClient:
self.calls.append((url, kwargs))
return FakeResponse(self.payload, should_raise=self.should_raise)
+ async def aclose(self):
+ pass
+
@pytest.fixture
def make_channel(tmp_path, monkeypatch):