Microsoft Teams
适用范围
在以下情况使用本页:
- 配置或开发 Microsoft Teams 集成
- 设置本地开发 webhook 隧道
- 排查 Teams bot 权限或文件上传问题
状态:捆绑插件。使用 Azure Bot Framework Direct Line API 与 Microsoft Teams 通信。
概述
- 通过 Azure Bot Framework 与 Teams 集成。
- 支持 DM(个人聊天)和频道/群组消息。
- 消息通过 Azure Bot webhook 路由到 Gateway。
- 支持 RSC(资源特定许可)权限和 Graph API 历史。
- 文件附件通过 Teams bot 文件 API 或 SharePoint 处理。
- 支持通过 Adaptive Cards 发送轮询。
- 支持 @提及和回复样式(回复、提及)。
- 需要公开可访问的 webhook 端点(或 ngrok/tailscale 等隧道)。
快速开始
- 在 Azure Portal 中创建 Azure Bot:
- 获取 App ID 和 客户端密钥(机密)。
- 启用 带有流量的 Direct Line。
- 获取 Direct Line 端点:
https://<bot-name>.botframework.com/api/messages。
- 创建 Teams 应用包(manifest + 图标)。
- 在 Teams 中安装应用并授予 RSC 权限。
- 配置 Clawdbot:
{ channels: { msteams: { enabled: true, botAppId: "your-bot-app-id", botClientSecret: "your-bot-client-secret", tenantId: "your-tenant-id" } } } - 启动 Gateway;它将注册 webhook 处理程序并开始接收消息。
访问控制
- DM 策略:
pairing、allowlist、open或disabled。- 默认:
pairing(配对代码)。 - 使用
channels.msteams.allowFrom控制谁可以发送 DM。
- 默认:
- 群组/频道:
open、allowlist或disabled。- 默认:
allowlist。 - 使用
channels.msteams.groupAllowFrom控制谁可以在群组中触发。
- 默认:
- 提及要求:默认情况下,群组/频道中的消息需要 @提及。
- 使用
channels.msteams.requireMention=false禁用。 - 通过
channels.msteams.groups按团队/频道配置。
- 使用
Azure Bot 设置步骤
步骤 1:在 Azure Portal 中创建 Bot
- 转到 Azure Portal → 创建资源 → Azure Bot。
- 配置:
- Bot handle:bot 的唯一 ID。
- 定价层:标准 或更高。
- 应用类型:多租户。
- 获取 App ID 和 客户端密钥(机密)。
- 启用 Direct Line 通道:
- 在 Azure Bot 中,转到 通道 → Direct Line。
- 添加具有密钥的站点。
- 复制 Direct Line 端点:
https://<bot-name>.botframework.com/api/messages。
步骤 2:创建 Teams 应用包
Teams 应用需要一个 manifest(manifest.json)和两个图标:
color.png:192x192 像素。outline.png:32x32 像素。
示例 manifest:
{
"$schema": "https://developer.microsoft.com/json-schemas/teams/v1.16/MicrosoftTeams.schema.json",
"manifestVersion": "1.16",
"version": "1.0.0",
"id": "YOUR-BOT-APP-ID",
"packageName": "com.clawdbot.teams",
"developer": {
"name": "Your Name",
"websiteUrl": "https://example.com",
"privacyUrl": "https://example.com/privacy",
"termsOfUseUrl": "https://example.com/terms"
},
"icons": {
"color": "color.png",
"outline": "outline.png"
},
"name": {
"short": "Clawdbot",
"full": "Clawdbot AI Assistant"
},
"description": {
"short": "AI assistant for Microsoft Teams",
"full": "An AI assistant that integrates with Microsoft Teams."
},
"accentColor": "#FFFFFF",
"bots": [
{
"botId": "YOUR-BOT-APP-ID",
"needsChannelSelector": false,
"isNotificationOnly": false,
"scopes": ["personal", "team", "groupchat"]
}
],
"webApplicationInfo": {
"id": "YOUR-BOT-APP-ID",
"resource": "https://RSC_PERMISSION_ID"
},
"permissions": {
"resourceSpecific": [
{
"name": "ChannelMessage.Read.Group",
"type": "Application"
}
]
},
"validDomains": ["*.botframework.com"]
}打包应用:
- 创建一个包含
manifest.json和图标的文件夹。 - 压缩文件夹(
.zip)。
步骤 3:在 Teams 中安装应用
- 转到 Teams Developer Portal → 应用 → 导入应用。
- 上传
.zip文件。 - 将应用添加到团队或聊天。
- 授予 RSC 权限(如果提示)。
最小仅文本设置(无 RSC/Graph)
如果只需要基本的 DM/群组文本消息(无历史、无文件):
- 跳过 RSC 权限:从 manifest 中删除
permissions.resourceSpecific。 - 跳过 Graph API:无需配置 Graph 权限。
- 基本配置:
{ channels: { msteams: { enabled: true, botAppId: "YOUR-BOT-APP-ID", botClientSecret: "YOUR-CLIENT-SECRET" } } }
限制:无历史上下文、无文件附件、无频道图像。
历史上下文
Clawdbot 可以从 Teams 拉取历史消息以提供上下文。
方式 1:RSC 权限(推荐)
- 在 manifest 中添加
ChannelMessage.Read.Group(团队)或ChatMessage.Read.Chat(群组聊天)。 - Clawdbot 通过 Graph API 获取历史。
- 无需用户登录。
方式 2:Graph API
- 配置
channels.msteams.graph.tenantId、clientId、clientSecret。 - Clawdbot 使用应用程序权限访问聊天历史。
配置:
{
channels: {
msteams: {
historyLimit: 50, // 每个聊天的消息数
graph: {
tenantId: "your-tenant-id",
clientId: "your-graph-client-id",
clientSecret: "your-graph-client-secret"
}
}
}
}注意事项:
- RSC 权限是按团队/聊天的。需要将应用添加到每个团队/聊天。
- Graph API 需要应用程序权限(
ChannelMessage.Read.All、Chat.Read.All)。
RSC 权限
RSC(资源特定许可)允许 bot 访问特定团队或聊天中的消息,而无需用户登录。
示例 Teams manifest:
{
"permissions": {
"resourceSpecific": [
{
"name": "ChannelMessage.Read.Group",
"type": "Application"
}
]
},
"webApplicationInfo": {
"id": "YOUR-BOT-APP-ID",
"resource": "https://RSC_PERMISSION_ID"
}
}支持的 RSC 权限:
ChannelMessage.Read.Group:读取团队频道中的消息。ChannelMessage.Send.Group:向团队频道发送消息。ChatMessage.Read.Chat:读取群组聊天中的消息。ChatMessage.Send.Chat:向群组聊天发送消息。
更新现有应用
如果您已在 Teams 中安装应用:
- 更新 manifest 中的
version。 - 重新上传
.zip文件。 - 删除并重新添加应用以刷新权限。
RSC vs Graph API 功能
| 功能 | RSC | Graph API |
|---|---|---|
| 读取频道消息 | 是 | 是 |
| 读取群组聊天消息 | 是 | 是 |
| 发送消息 | 是 | 是 |
| 读取历史 | 是 | 是 |
| 读取文件/附件 | 否 | 是 |
| 需要 admin 同意 | 否 | 是 |
配置
完整的 channels.msteams 配置选项:
{
channels: {
msteams: {
enabled: true,
botAppId: "your-bot-app-id",
botClientSecret: "your-bot-client-secret",
tenantId: "your-tenant-id", // 可选,用于 Graph API
// Webhook 服务器
serve: {
port: 3334,
path: "/teams/webhook"
},
// 公开暴露(选择一个)
// publicUrl: "https://example.ngrok.app/teams/webhook",
// tunnel: { provider: "ngrok" },
// tailscale: { mode: "funnel", path: "/teams/webhook" }
// 访问控制
dmPolicy: "pairing", // pairing | allowlist | open | disabled
allowFrom: ["[email protected]"],
groupPolicy: "allowlist", // open | allowlist | disabled
groupAllowFrom: ["[email protected]"],
// 提及
requireMention: true, // 默认 true(群组)
groups: {
"*": { requireMention: true }, // 所有群组的默认值
"19:[email protected]": { requireMention: false } // 覆盖特定群组
},
// 历史
historyLimit: 50,
dmHistoryLimit: 20,
// Graph API(可选,用于历史/文件)
graph: {
tenantId: "your-tenant-id",
clientId: "your-client-id",
clientSecret: "your-client-secret"
},
// 附件/媒体
mediaMaxMb: 8,
mediaAllowHosts: ["*.microsoft.com", "*.azureedge.net"],
// SharePoint(群组文件上传)
sharePointSiteId: "contoso.sharepoint.com,guid1,guid2"
}
}
}路由与会话
- Clawdbot 在消息交互后存储会话引用以进行主动消息传递。
- 目标格式:
- 用户:
user:<aad-object-id>或user:<display-name> - 群组/频道:
conversation:<conversation-id>
- 用户:
回复样式
Clawdbot 支持多种回复样式:
- plain:纯文本消息。
- mention:提及用户(默认)。
- reply:回复特定消息(需要
replyToId)。
配置:
{
channels: {
msteams: {
replyStyle: "mention" // plain | mention | reply
}
}
}附件(图像/文件)
当前限制:
- DM:图像和文件附件通过 Teams bot 文件 API 工作。
- 频道/群组:附件存储在 M365 存储(SharePoint/OneDrive)中。webhook 负载仅包含 HTML 存根,而不是实际文件字节。需要 Graph API 权限才能下载频道附件。
如果没有 Graph 权限,包含图像的频道消息将仅作为文本接收(bot 无法访问图像内容)。
默认情况下,Clawdbot 仅从 Microsoft/Teams 主机名下载媒体。通过 channels.msteams.mediaAllowHosts 覆盖(使用 ["*"] 允许任何主机)。
在群组聊天中发送文件
Bot 可以使用 FileConsentCard 流程在 DM 中发送文件(内置)。但是,在群组聊天/频道中发送文件需要额外设置:
| 上下文 | 文件发送方式 | 所需设置 |
|---|---|---|
| DM | FileConsentCard → 用户接受 → bot 上传 | 开箱即用 |
| 群组聊天/频道 | 上传到 SharePoint → 共享链接 | 需要 sharePointSiteId + Graph 权限 |
| 图像(任何上下文) | Base64 编码内联 | 开箱即用 |
为什么群组聊天需要 SharePoint
Bot 没有个人 OneDrive 驱动器(/me/drive Graph API 端点不适用于应用程序标识)。要在群组聊天/频道中发送文件,bot 会上传到 SharePoint 站点并创建共享链接。
设置
在 Entra ID (Azure AD) → 应用注册中添加 Graph API 权限:
Sites.ReadWrite.All(应用程序)- 将文件上传到 SharePointChat.Read.All(应用程序)- 可选,启用按用户共享链接
为租户授予 admin 同意。
获取您的 SharePoint 站点 ID:
# 通过 Graph Explorer 或使用有效令牌的 curl: curl -H "Authorization: Bearer $TOKEN" \ "https://graph.microsoft.com/v1.0/sites/{hostname}:/{site-path}" # 示例:对于位于 "contoso.sharepoint.com/sites/BotFiles" 的站点 curl -H "Authorization: Bearer $TOKEN" \ "https://graph.microsoft.com/v1.0/sites/contoso.sharepoint.com:/sites/BotFiles" # 响应包括:"id": "contoso.sharepoint.com,guid1,guid2"配置 Clawdbot:
{ channels: { msteams: { // ... 其他配置 ... sharePointSiteId: "contoso.sharepoint.com,guid1,guid2" } } }
共享行为
| 权限 | 共享行为 |
|---|---|
仅 Sites.ReadWrite.All | 组织范围内共享链接(组织中的任何人都可以访问) |
Sites.ReadWrite.All + Chat.Read.All | 按用户共享链接(仅聊天成员可以访问) |
按用户共享更安全,因为只有聊天参与者可以访问文件。如果缺少 Chat.Read.All 权限,bot 将回退到组织范围内共享。
回退行为
| 场景 | 结果 |
|---|---|
群组聊天 + 文件 + 配置了 sharePointSiteId | 上传到 SharePoint,发送共享链接 |
群组聊天 + 文件 + 无 sharePointSiteId | 尝试 OneDrive 上传(可能失败),仅发送文本 |
| 个人聊天 + 文件 | FileConsentCard 流程(无需 SharePoint 即可工作) |
| 任何上下文 + 图像 | Base64 编码内联(无需 SharePoint 即可工作) |
文件存储位置
上传的文件存储在配置的 SharePoint 站点的默认文档库中的 /ClawdbotShared/ 文件夹中。
轮询(Adaptive Cards)
Clawdbot 将 Teams 轮询作为 Adaptive Cards 发送(没有原生 Teams 轮询 API)。
- CLI:
clawdbot message poll --channel msteams --target conversation:<id> ... - 投票由 gateway 记录在
~/.clawdbot/msteams-polls.json中。 - Gateway 必须保持在线以记录投票。
- 轮询尚未自动发布结果摘要(如需要,请检查存储文件)。
Adaptive Cards(任意)
使用 message 工具或 CLI 向 Teams 用户或对话发送任何 Adaptive Card JSON。
card 参数接受 Adaptive Card JSON 对象。当提供 card 时,消息文本是可选的。
Agent 工具:
{
"action": "send",
"channel": "msteams",
"target": "user:<id>",
"card": {
"type": "AdaptiveCard",
"version": "1.5",
"body": [{"type": "TextBlock", "text": "Hello!"}]
}
}CLI:
clawdbot message send --channel msteams \
--target "conversation:19:[email protected]" \
--card '{"type":"AdaptiveCard","version":"1.5","body":[{"type":"TextBlock","text":"Hello!"}]}'有关 card 架构和示例,请参阅 Adaptive Cards 文档。有关目标格式详细信息,请参阅下面的目标格式。
目标格式
MSTeams 目标使用前缀来区分用户和对话:
| 目标类型 | 格式 | 示例 |
|---|---|---|
| 用户(按 ID) | user:<aad-object-id> | user:40a1a0ed-4ff2-4164-a219-55518990c197 |
| 用户(按名称) | user:<display-name> | user:John Smith(需要 Graph API) |
| 群组/频道 | conversation:<conversation-id> | conversation:19:[email protected] |
| 群组/频道(原始) | <conversation-id> | 19:[email protected](如果包含 @thread) |
CLI 示例:
# 按 ID 向用户发送
clawdbot message send --channel msteams --target "user:40a1a0ed-..." --message "Hello"
# 按显示名称向用户发送(触发 Graph API 查找)
clawdbot message send --channel msteams --target "user:John Smith" --message "Hello"
# 向群组聊天或频道发送
clawdbot message send --channel msteams --target "conversation:19:[email protected]" --message "Hello"
# 向对话发送 Adaptive Card
clawdbot message send --channel msteams --target "conversation:19:[email protected]" \
--card '{"type":"AdaptiveCard","version":"1.5","body":[{"type":"TextBlock","text":"Hello"}]}'Agent 工具示例:
{
"action": "send",
"channel": "msteams",
"target": "user:John Smith",
"message": "Hello!"
}{
"action": "send",
"channel": "msteams",
"target": "conversation:19:[email protected]",
"card": {"type": "AdaptiveCard", "version": "1.5", "body": [{"type": "TextBlock", "text": "Hello"}]}
}注意:没有 user: 前缀,名称默认为群组/团队解析。按显示名称定位人员时,请始终使用 user:。
主动消息传递
- 主动消息仅在用户交互后才可能,因为此时我们存储了会话引用。
- 有关
dmPolicy和允许列表控制,请参阅/gateway/configuration。
团队和频道 ID(常见陷阱)
Teams URL 中的 groupId 查询参数不是用于配置的团队 ID。而是从 URL 路径中提取 ID:
团队 URL:
https://teams.microsoft.com/l/team/19%3ABk4j...%40thread.tacv2/conversations?groupId=...
└────────────────────────────┘
团队 ID(URL 解码此部分)频道 URL:
https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?groupId=...
└─────────────────────────┘
频道 ID(URL 解码此部分)对于配置:
- 团队 ID =
/team/之后的路径段(URL 解码,例如19:[email protected]) - 频道 ID =
/channel/之后的路径段(URL 解码) - 忽略
groupId查询参数
私有频道
Bot 在私有频道中的支持有限:
| 功能 | 标准频道 | 私有频道 |
|---|---|---|
| Bot 安装 | 是 | 有限 |
| 实时消息(webhook) | 是 | 可能不工作 |
| RSC 权限 | 是 | 可能表现不同 |
| @提及 | 是 | 如果 bot 可访问 |
| Graph API 历史 | 是 | 是(需要权限) |
如果私有频道不工作的解决方法:
- 使用标准频道进行 bot 交互
- 使用 DM - 用户始终可以直接向 bot 发送消息
- 使用 Graph API 进行历史访问(需要
ChannelMessage.Read.All)
故障排除
常见问题
- 频道中未显示图像: 缺少 Graph 权限或 admin 同意。重新安装 Teams 应用并完全退出/重新打开 Teams。
- 频道中无响应: 默认情况下需要提及;设置
channels.msteams.requireMention=false或按团队/频道配置。 - 版本不匹配(Teams 仍显示旧 manifest): 删除并重新添加应用,并完全退出 Teams 以刷新。
- 来自 webhook 的 401 Unauthorized: 在没有 Azure JWT 的情况下手动测试时预期 - 表示端点可访问但身份验证失败。使用 Azure Web Chat 进行正确测试。
Manifest 上传错误
- “Icon file cannot be empty”:manifest 引用的图标文件为 0 字节。创建有效的 PNG 图标(
outline.png为 32x32,color.png为 192x192)。 - “webApplicationInfo.Id already in use”:应用仍安装在另一个团队/聊天中。首先找到并卸载它,或等待 5-10 分钟以进行传播。
- 上传时出现 “Something went wrong”:改为通过 https://admin.teams.microsoft.com 上传,打开浏览器 DevTools (F12) → 网络选项卡,并检查响应正文以获取实际错误。
- 旁加载失败:尝试"将应用上传到组织的应用目录"而不是"上传自定义应用" - 这通常会绕过旁加载限制。
RSC 权限不工作
- 验证
webApplicationInfo.id与您的 bot 的应用 ID 完全匹配 - 重新上传应用并在团队/聊天中重新安装
- 检查您的组织管理员是否阻止了 RSC 权限
- 确认您使用正确的范围:
ChannelMessage.Read.Group用于团队,ChatMessage.Read.Chat用于群组聊天
参考资料
- 创建 Azure Bot - Azure Bot 设置指南
- Teams Developer Portal - 创建/管理 Teams 应用
- Teams 应用 manifest 架构
- 使用 RSC 接收频道消息
- RSC 权限参考
- Teams bot 文件处理(频道/群组需要 Graph)
- 主动消息传递