Outlook Delegate 技能
作为 代理人 通过 Microsoft Graph API 访问另一用户的 Outlook/Microsoft 365 电子邮件和日历。支持三种发送模式:作为自己、作为所有者或代表所有者。
代理架构
此技能适用于以下场景:
- 你的 AI 助手(代理人)拥有自己的 Microsoft 365 账户
- 所有者 已授予助手对其邮箱/日历的代理访问权限
- 助手可以作为自己、作为所有者或代表所有者发送电子邮件
发送模式解释
所有三种模式使用相同的 Graph API 调用(/users/{delegate}/sendMail 并将 from 字段设置)。Send As 和 Send on Behalf 之间的区别完全由授予的 Exchange 权限决定,而不是 API 端点。
| 模式 | 命令 | 需要的 Exchange 权限 | from 字段 |
sender 字段 |
收件人看到的 |
|---|---|---|---|---|---|
| 作为自己 | send |
(不需要额外的) | 代理人 | 代理人 | “From: Assistant” |
| 作为所有者(Send As) | send-as |
仅 SendAs | 所有者 | 所有者 | “From: Owner” |
| 代表所有者 | send-behalf |
仅 SendOnBehalf | 所有者 | 代理人 | “From: Assistant on behalf of Owner” |
⚠️ 重要: 不要同时授予 SendAs 和 SendOnBehalf 权限。如果同时授予,Exchange 总是使用 SendAs,并且永远不会显示 “on behalf of” 指示。根据你期望的行为选择一个。
工作原理
当你调用 send-as 或 send-behalf 时,技能会进行相同的 API 调用:通过代理人的端点发送,并将所有者放在 from 字段中。Microsoft Graph 自动将 sender 属性设置为经过身份验证的用户(代理人)。收件人是否看到 “on behalf of” 完全取决于 Exchange 权限:
- SendAs 权限 → Graph 将
sender和from都设置为所有者。没有代理指示。 - SendOnBehalf 权限 → Graph 保持
sender为代理人,from为所有者。收件人看到 “on behalf of.”
配置
配置文件:~/.outlook-mcp/config.json
{
"client_id": "你的应用客户端 ID",
"client_secret": "你的应用客户端密钥",
"tenant_id": "你的租户 ID",
"owner_email": "owner@yourdomain.com",
"owner_name": "所有者显示名称",
"delegate_email": "assistant@yourdomain.com",
"delegate_name": "AI Assistant",
"timezone": "America/New_York"
}
| 字段 | 描述 |
|---|---|
client_id |
Microsoft Entra ID 应用注册客户端 ID |
client_secret |
Microsoft Entra ID 应用注册客户端密钥 |
tenant_id |
你的 Microsoft Entra 租户 ID(设置过程中自动检测) |
owner_email |
助手作为代理访问的邮箱 |
owner_name |
所有者的显示名称(用于 From 字段) |
delegate_email |
助手自己的电子邮件地址 |
delegate_name |
助手的显示名称 |
timezone |
日历操作的 IANA 时区(例如 America/New_York, Europe/London, UTC) |
设置要求
1. Microsoft Entra ID 应用注册
在 Azure 门户中创建应用注册:
- 访问 portal.azure.com → Microsoft Entra ID → 应用注册
- 新注册:
- 名称:“AI Assistant Mail Access”
- 支持的账户类型:“仅限此组织目录中的账户”(单一租户)
- 重定向 URI:
http://localhost:8400/callback
- 记录 应用程序(客户端)ID 和 目录(租户)ID
2. 配置 API 权限
在你的应用 → API 权限 → 添加权限 → Microsoft Graph → 委托权限:
所有模式都需要:
Mail.ReadWrite— 读取/写入助手自己的邮件Mail.Send— 作为助手发送邮件Calendars.ReadWrite— 读取/写入日历User.Read— 读取自己的个人资料offline_access— 刷新令牌
代理访问需要:
Mail.ReadWrite.Shared— 读取/写入共享邮箱Mail.Send.Shared— 代表他人发送Calendars.ReadWrite.Shared— 读取/写入共享日历
点击 “授予管理员同意”(需要管理员)。
3. 创建客户端密钥
- 证书和密钥 → 新客户端密钥
- 描述:“AI Assistant”
- 过期时间:选择适当的时长
- 立即复制值(仅显示一次)
4. 授予 Exchange 代理权限
所有者(或管理员)必须通过 PowerShell 授予助手访问权限。
首先选择你的发送模式,然后授予适当的权限:
# 连接到 Exchange Online
Install-Module -Name ExchangeOnlineManagement
Connect-ExchangeOnline -UserPrincipalName admin@yourdomain.com
# 必需:完整邮箱访问权限(用于阅读所有者的邮件)
Add-MailboxPermission -Identity "owner@yourdomain.com" `
-User "assistant@yourdomain.com" `
-AccessRights FullAccess `
-InheritanceType All `
-AutoMapping $false
# 必需:日历代理访问权限
Add-MailboxFolderPermission -Identity "owner@yourdomain.com:\Calendar" `
-User "assistant@yourdomain.com" `
-AccessRights Editor `
-SharingPermissionFlags Delegate
然后选择以下之一 — 不要同时授予:
# 选项 A:作为发送(电子邮件直接显示为所有者,没有指示)
Add-RecipientPermission -Identity "owner@yourdomain.com" `
-Trustee "assistant@yourdomain.com" `
-AccessRights SendAs `
-Confirm:$false
# 选项 B:代表发送(电子邮件显示为 "助手代表所有者")
Set-Mailbox -Identity "owner@yourdomain.com" `
-GrantSendOnBehalfTo "assistant@yourdomain.com"
验证权限:
# 检查邮箱权限
Get-MailboxPermission -Identity "owner@yourdomain.com" | Where-Object {$_.User -like "*assistant*"}
# 检查 Send As
Get-RecipientPermission -Identity "owner@yourdomain.com" | Where-Object {$_.Trustee -like "*assistant*"}
# 检查 Send on Behalf
Get-Mailbox "owner@yourdomain.com" | Select-Object GrantSendOnBehalfTo
# 检查日历权限
Get-MailboxFolderPermission -Identity "owner@yourdomain.com:\Calendar"
5. 权限总结
| 操作 | Graph 权限 | Exchange 权限 |
|---|---|---|
| 读取所有者的邮件 | Mail.ReadWrite.Shared |
FullAccess |
| 作为自己发送 | Mail.Send |
(不需要) |
| 作为所有者发送 | Mail.Send.Shared |
SendAs 仅 |
| 代表所有者发送 | Mail.Send.Shared |
SendOnBehalf 仅 |
| 读取/写入所有者的日历 | Calendars.ReadWrite.Shared |
Editor |
使用方法
令牌管理
./scripts/outlook-token.sh refresh # 刷新过期的令牌
./scripts/outlook-token.sh test # 测试连接到两个账户
./scripts/outlook-token.sh get # 打印访问令牌
./scripts/outlook-token.sh info # 显示配置信息
读取所有者的电子邮件
./scripts/outlook-mail.sh inbox [count] # 所有者的收件箱
./scripts/outlook-mail.sh unread [count] # 所有者未读邮件
./scripts/outlook-mail.sh search "query" [count] # 搜索所有者的邮件
./scripts/outlook-mail.sh from <email> [count] # 来自发件人的邮件
./scripts/outlook-mail.sh read <id> # 阅读邮件内容
./scripts/outlook-mail.sh attachments <id> # 列出附件
管理所有者的电子邮件
./scripts/outlook-mail.sh mark-read <id> # 标记为已读
./scripts/outlook-mail.sh mark-unread <id> # 标记为未读
./scripts/outlook-mail.sh flag <id> # 标记为重要
./scripts/outlook-mail.sh unflag <id> # 移除标记
./scripts/outlook-mail.sh delete <id> # 移动到垃圾箱
./scripts/outlook-mail.sh archive <id> # 移动到归档
./scripts/outlook-mail.sh move <id> <folder> # 移动到文件夹
发送电子邮件
作为助手(自己):
./scripts/outlook-mail.sh send <to> <subject> <body>
./scripts/outlook-mail.sh reply <id> "body"
./scripts/outlook-mail.sh forward <id> <to> [message]
收件人看到:“From: AI Assistant assistant@domain.com”
作为所有者(Send As — 需要 SendAs 权限,无指示):
./scripts/outlook-mail.sh send-as <to> <subject> <body>
./scripts/outlook-mail.sh reply-as <id> "body"
./scripts/outlook-mail.sh forward-as <id> <to> [message]
收件人看到:“From: Owner owner@domain.com”
代表所有者(需要 SendOnBehalf 权限):
./scripts/outlook-mail.sh send-behalf <to> <subject> <body>
./scripts/outlook-mail.sh reply-behalf <id> "body"
./scripts/outlook-mail.sh forward-behalf <id> <to> [message]
收件人看到:“From: AI Assistant on behalf of Owner owner@domain.com”
草稿
./scripts/outlook-mail.sh draft <to> <subject> <body> # 在所有者的邮箱中创建草稿
./scripts/outlook-mail.sh drafts [count] # 列出所有者的草稿
./scripts/outlook-mail.sh send-draft <id> # 作为自己发送草稿
./scripts/outlook-mail.sh send-draft-as <id> # 作为所有者发送草稿
./scripts/outlook-mail.sh send-draft-behalf <id> # 代表所有者发送草稿
文件夹 & 统计
./scripts/outlook-mail.sh folders # 列出邮件文件夹
./scripts/outlook-mail.sh stats # 收件箱统计信息
./scripts/outlook-mail.sh whoami # 显示代理人信息
日历
查看事件:
./scripts/outlook-calendar.sh events [count] # 所有者即将发生的事件(仅限未来)
./scripts/outlook-calendar.sh today # 今天的事件(时区感知)
./scripts/outlook-calendar.sh week # 本周的事件
./scripts/outlook-calendar.sh read <id> # 事件详情
./scripts/outlook-calendar.sh calendars # 列出所有日历
./scripts/outlook-calendar.sh free <start> <end> # 检查可用性
创建事件:
./scripts/outlook-calendar.sh create <subject> <start> <end> [location]
./scripts/outlook-calendar.sh quick <subject> [time]
日期格式:YYYY-MM-DDTHH:MM(例如,2026-01-26T10:00)
管理事件:
./scripts/outlook-calendar.sh update <id> <field> <value>
./scripts/outlook-calendar.sh delete <id>
字段:subject, location, start, end
发送项目行为
发送副本保存的位置取决于使用的端点,而不是发送模式:
| 命令 | 使用的端点 | 保存到 |
|---|---|---|
send(作为自己) |
/users/{delegate}/sendMail |
代理人的已发送项目 |
send-as |
/users/{delegate}/sendMail |
代理人的已发送项目 * |
send-behalf |
/users/{delegate}/sendMail |
代理人的已发送项目 * |
| 所有草稿发送 | /users/{owner}/messages/{id}/send |
所有者的已发送项目 |
* 管理员可以配置 Exchange 以在所有者的已发送项目中也保存一份副本:
Set-Mailbox -Identity "owner@yourdomain.com" -MessageCopyForSentAsEnabled $true -MessageCopyForSendOnBehalfEnabled $true
故障排除
“访问被拒绝” 或 “403 禁止” → 检查助手是否对所有者的邮箱有 MailboxPermission
“ErrorSendAsDenied” → 缺少 SendAs 或 SendOnBehalf 权限。运行上述 PowerShell 命令。
电子邮件不显示 “on behalf of” → 你可能同时授予了 SendAs 和 SendOnBehalf。当两者都存在时,Exchange 总是使用 SendAs(隐藏代理人)。如果你想让 “on behalf of” 出现,请移除 SendAs 权限。
“找不到邮箱”
→ 验证 config.json 中的 owner_email 是否正确
“AADSTS90002: 找不到租户”
→ 检查 config.json 中的 tenant_id 是否与你 Microsoft Entra 租户匹配
“令牌过期”
→ 运行 outlook-token.sh refresh
日历时区错误
→ 更新 config.json 中的 timezone(使用 IANA 格式如 America/New_York)
安全考虑
- 凭证保护:
~/.outlook-mcp/目录自动设置为700,凭证文件设置为600 - 无进程泄露:令牌刷新和交换操作通过 stdin 传递秘密,而不是命令行参数
- 输入清理:所有用户输入通过
jqJSON 转义以防止注入 - 审计跟踪:所有操作都记录在所有者的邮箱审计日志中
- 范围限制:助手仅能访问明确授予的内容
- 撤销:所有者可以通过 Exchange PowerShell 或 Outlook 设置撤销访问权限
撤销访问权限
# 移除所有权限
Remove-MailboxPermission -Identity "owner@yourdomain.com" -User "assistant@yourdomain.com" -AccessRights FullAccess -Confirm:$false
# 移除 Send As(如果授予)
Remove-RecipientPermission -Identity "owner@yourdomain.com" -Trustee "assistant@yourdomain.com" -AccessRights SendAs -Confirm:$false
# 移除 Send on Behalf(如果授予)
Set-Mailbox -Identity "owner@yourdomain.com" -GrantSendOnBehalfTo @{Remove="assistant@yourdomain.com"}
# 移除日历访问权限
Remove-MailboxFolderPermission -Identity "owner@yourdomain.com:\Calendar" -User "assistant@yourdomain.com" -Confirm:$false
文件
~/.outlook-mcp/config.json— 配置(客户端 ID,租户 ID,电子邮件,时区)~/.outlook-mcp/credentials.json— OAuth 令牌(访问 + 刷新)
更新日志
v1.1.0
- FIXED: Reply 命令不再发送重复的电子邮件(移除发送垃圾邮件的死代码)
- FIXED: 所有回复/转发变体现在使用适当的 Graph API 线程(createReply/createForward → patch from → send)
- FIXED: send-as 和 send-behalf 现在正确记录 — 行为取决于 Exchange 权限,而不是 API 端点
- FIXED: send-draft-behalf 不再在发送前删除草稿(防止发送失败时数据丢失)
- FIXED: 所有用户输入现在通过
jqJSON 转义以防止注入和畸形负载 - FIXED: 每次写入时强制执行凭证文件权限(
chmod 600) - FIXED: 配置目录权限强制执行(
chmod 700) - FIXED: 客户端秘密不再在进程列表中可见(通过 stdin 发送)
- FIXED: 日历
events命令现在仅显示未来事件 - FIXED: 发送项目行为准确记录
- FIXED: 版本号更正
- 更新 Microsoft Entra ID 命名(以前称为 Azure Active Directory)
- 设置指南现在明确警告不要同时授予 SendAs 和 SendOnBehalf
- 撤销命令更新(GrantSendOnBehalfTo 使用 @{Remove=…} 语法)
v1.0.0
- 三种发送模式:作为自己、作为所有者(Send As)、代表所有者
- 租户特定的认证(没有
/common端点) - 可配置的日历操作时区
- 所有者和代理人的显示名称
- 保存到所有者邮箱的草稿
- 全面的 PowerShell 设置命令
- 基于 outlook v1.3.0 由 jotamed(https://clawhub.ai/jotamed/outlook)开发