Microsoft 365 Graph for OpenClaw Skill
1. Quick prerequisites
- 1. Python 3 with
requests installed. - Default auth values:
- Client ID (personal-account default):
952d1b34-682e-48ce-9c54-bac5a96cbd42
- Tenant (personal-account default):
consumers
- Default scopes:
Mail.ReadWrite Mail.Send Calendars.ReadWrite Files.ReadWrite.All Contacts.ReadWrite offline_access
- For work/school accounts, use
--tenant-id organizations (or tenant GUID) and a tenant-approved
--client-id.
- The public default client ID is for quick testing. For production, prefer your own App Registration.
- 3. Tokens are stored in
state/graph_auth.json (ignored by git). - Push-mode runtime values (service-level):
- These values are loaded by systemd services from
/etc/default/graph-mail-webhook (usually written by setup scripts).
-
OPENCLAW_HOOK_URL (required)
-
OPENCLAW_HOOK_TOKEN (required)
-
GRAPH_WEBHOOK_CLIENT_STATE (required; auto-generated in minimal e2e setup when omitted)
-
OPENCLAW_SESSION_KEY (optional; default
hook:graph-mail)
Permission profiles (least privilege by use case) are documented in docs/permission-profiles.md.
2. Assisted OAuth flow (Device Code)
- 1. Run:
python scripts/graph_auth.py device-login \
--client-id 952d1b34-682e-48ce-9c54-bac5a96cbd42 \
--tenant-id consumers
- 2. The script prints a URL and device code.
- Open
https://microsoft.com/devicelogin, enter the code, and approve with the target account. - Check and manage auth state:
-
python scripts/graph_auth.py status
-
python scripts/graph_auth.py refresh
-
python scripts/graph_auth.py clear
- 5. Other scripts call
utils.get_access_token(), which refreshes tokens automatically when needed. - Scope override is disabled in
graph_auth.py; the skill always uses DEFAULT_SCOPES.
Detailed reference: references/auth.md.
3. Email operations
- - List/filter: INLINECODE22
- Fetch specific message: INLINECODE23
- Move message: add
--move-to <folderId> to the command above. - Send email (
saveToSentItems enabled by default):
python scripts/mail_send.py \
--to user@example.com \
--subject "Update" \
--body-file replies/thais.html --html \
--cc teammate@example.com \
--attachment docs/proposal.pdf
- - Use
--no-save-copy only when you intentionally do not want Sent Items storage.
More examples and filters: references/mail.md.
4. Calendar operations
- - List custom date window:
python scripts/calendar_sync.py list \
--start 2026-03-03T00:00Z --end 2026-03-05T23:59Z --top 50
- - Create Teams or in-person event: use
create; add --online for Teams link. - For personal Microsoft accounts (
tenant=consumers), Teams meeting provisioning via Graph might not return a join URL; create the Teams meeting in Outlook/Teams first and add the resulting link to the event body when needed. - Update/cancel events by
event_id returned in JSON output.
Full examples: references/calendar.md.
5. OneDrive / Files
- - List folders/files: INLINECODE33
- Upload: INLINECODE34
- Download: INLINECODE35
- Move / share links: use
move and share subcommands. - The script resolves localized/special-folder aliases (for example
Documents and Documentos).
More details: references/drive.md.
6. Contacts
- - List/search: INLINECODE41
- Create: INLINECODE42
- Update/Delete:
... update <contactId> ... / INLINECODE44 - Contacts are part of the default scope set and supported as a first-class workflow.
More details: references/contacts.md.
7. Mail push mode (Webhook Adapter)
- - Adapter server (Graph handshake +
clientState validation + enqueue):
python scripts/mail_webhook_adapter.py serve \
--host 0.0.0.0 --port 8789 --path /graph/mail \
--client-state "$GRAPH_WEBHOOK_CLIENT_STATE"
- - Subscription lifecycle (
create/status/renew/delete/list):
python scripts/mail_subscriptions.py create \
--notification-url "https://graph-hook.example.com/graph/mail" \
--client-state "$GRAPH_WEBHOOK_CLIENT_STATE" \
--minutes 4200
- Default resource is
me/messages (recommended for better delivery coverage). Override with
--resource only for advanced/scoped scenarios.
- - Async worker (dedupe + default wake signal to OpenClaw
/hooks/wake):
python scripts/mail_webhook_worker.py loop \
--session-key "$OPENCLAW_SESSION_KEY" \
--hook-url "$OPENCLAW_HOOK_URL" \
--hook-token "$OPENCLAW_HOOK_TOKEN"
- Default mode is
wake (
/hooks/wake,
mode=now). Use
--hook-action agent only when you explicitly need per-message rich payload delivery.
-
state/mail_webhook_queue.jsonl
-
state/mail_webhook_dedupe.json
- - Automated EC2 bootstrap (Caddy + systemd + renew timer):
sudo bash scripts/setup_mail_webhook_ec2.sh \
--domain graphhook.example.com \
--hook-url http://127.0.0.1:18789/hooks/wake \
--hook-token "<OPENCLAW_HOOK_TOKEN>" \
--session-key "hook:graph-mail" \
--client-state "<GRAPH_WEBHOOK_CLIENT_STATE>" \
--repo-root "$(pwd)"
- Use
--dry-run to preview all privileged writes and service actions before applying changes.
- - One-command setup (steps 2..6):
sudo bash scripts/run_mail_webhook_e2e_setup.sh \
--domain graphhook.example.com \
--hook-token "<OPENCLAW_HOOK_TOKEN>" \
--hook-url "http://127.0.0.1:18789/hooks/wake" \
--session-key "hook:graph-mail" \
--test-email "tar.alitar@outlook.com"
- Use
--dry-run for a no-mutation execution plan (no
/etc writes, no
systemctl, no subscription create, no email send).
- Output ends with
READY_FOR_PUSH: YES when setup is fully validated.
- - Include OpenClaw hook config in automation:
sudo bash scripts/run_mail_webhook_e2e_setup.sh \
--domain graphhook.example.com \
--hook-token "<OPENCLAW_HOOK_TOKEN>" \
--configure-openclaw-hooks \
--openclaw-config "/home/ubuntu/.openclaw/openclaw.json" \
--openclaw-service-name "auto" \
--openclaw-hooks-path "/hooks" \
--openclaw-allow-request-session-key true \
--test-email "tar.alitar@outlook.com"
- - Minimal-input smoke tests:
sudo bash scripts/run_mail_webhook_smoke_tests.sh \
--domain graphhook.example.com \
--create-subscription \
--test-email tar.alitar@outlook.com
- Output ends with
READINESS VERDICT: READY_FOR_PUSH only after all critical checks pass.
8. Privileged operations boundary
The core Graph scripts are unprivileged (graph_auth.py, mail_fetch.py, mail_send.py, calendar_sync.py, drive_ops.py, contacts_ops.py).
The setup scripts below are privileged and should be manually reviewed before execution:
- - INLINECODE70
- INLINECODE71
When run without --dry-run, they can:
- - Write INLINECODE73
- Write INLINECODE74
- Write
/etc/systemd/system/*.service and INLINECODE76 - Enable/restart services via INLINECODE77
- Optionally patch OpenClaw config and restart OpenClaw services
Recommended safety sequence:
- 1. Run with INLINECODE78
- Review emitted actions and target files
- Run on a non-production host first
- Apply to production only after approval
9. Logging and conventions
- - Each script appends one JSON line to
state/graph_ops.log with timestamp, action, and key parameters. - Tokens and logs must never be committed.
- Commands assume execution from the repository root. Adjust paths if running elsewhere.
10. Troubleshooting
| Symptom | Action |
|---|
| 401/invalid_grant | Run graph_auth.py refresh; if it fails, run clear and repeat device login. |
| 403/AccessDenied |
Missing scope or licensing/policy issue. Re-run device login with required scope(s). |
| 429/Throttled | Scripts do basic retry; wait a few seconds and retry. |
|
requests.exceptions.SSLError | Verify local system date/time and TLS trust chain. |
This skill provides OAuth-driven workflows for email, calendar, files, contacts, and push-based mail automation via Microsoft Graph.
Microsoft 365 Graph for OpenClaw 技能
1. 快速前提条件
- 1. 已安装 requests 库的 Python 3 环境。
- 默认认证值:
- 客户端 ID(个人账户默认值):952d1b34-682e-48ce-9c54-bac5a96cbd42
- 租户(个人账户默认值):consumers
- 默认作用域:Mail.ReadWrite Mail.Send Calendars.ReadWrite Files.ReadWrite.All Contacts.ReadWrite offline_access
- 对于工作/学校账户,请使用 --tenant-id organizations(或租户 GUID)以及租户批准的 --client-id。
- 公共默认客户端 ID 仅用于快速测试。生产环境建议使用您自己的应用注册。
- 3. 令牌存储在 state/graph_auth.json 中(已被 git 忽略)。
- 推送模式运行时值(服务级别):
- 这些值由 systemd 服务从 /etc/default/graph-mail-webhook 加载(通常由设置脚本写入)。
- OPENCLAW
HOOKURL(必需)
- OPENCLAW
HOOKTOKEN(必需)
- GRAPH
WEBHOOKCLIENT_STATE(必需;在最小化端到端设置中省略时会自动生成)
- OPENCLAW
SESSIONKEY(可选;默认为 hook:graph-mail)
权限配置文件(按用例最小权限)记录在 docs/permission-profiles.md 中。
2. 辅助 OAuth 流程(设备代码)
- 1. 运行:
bash
python scripts/graph_auth.py device-login \
--client-id 952d1b34-682e-48ce-9c54-bac5a96cbd42 \
--tenant-id consumers
- 2. 脚本会打印一个 URL 和 设备代码。
- 打开 https://microsoft.com/devicelogin,输入代码,并使用目标账户批准。
- 检查和管理认证状态:
- python scripts/graph_auth.py status
- python scripts/graph_auth.py refresh
- python scripts/graph_auth.py clear
- 5. 其他脚本调用 utils.getaccesstoken(),该函数在需要时自动刷新令牌。
- graphauth.py 中禁用了作用域覆盖;该技能始终使用 DEFAULTSCOPES。
详细参考:references/auth.md。
3. 邮件操作
- - 列出/筛选:python scripts/mail_fetch.py --folder Inbox --top 20 --unread
- 获取特定消息:... --id --include-body --mark-read
- 移动消息:在上述命令中添加 --move-to
- 发送邮件(默认启用 saveToSentItems):
bash
python scripts/mail_send.py \
--to user@example.com \
--subject 更新 \
--body-file replies/thais.html --html \
--cc teammate@example.com \
--attachment docs/proposal.pdf
- - 仅在您有意不存储已发送邮件时使用 --no-save-copy。
更多示例和筛选条件:references/mail.md。
4. 日历操作
bash
python scripts/calendar_sync.py list \
--start 2026-03-03T00:00Z --end 2026-03-05T23:59Z --top 50
- - 创建 Teams 或线下事件:使用 create;添加 --online 以获取 Teams 链接。
- 对于个人 Microsoft 账户(tenant=consumers),通过 Graph 预配 Teams 会议可能不会返回加入 URL;请先在 Outlook/Teams 中创建 Teams 会议,然后在需要时将生成的链接添加到事件正文中。
- 更新/取消事件:使用 JSON 输出中返回的 event_id。
完整示例:references/calendar.md。
5. OneDrive / 文件
- - 列出文件夹/文件:python scripts/drive_ops.py list --path /
- 上传:... upload --local notes/briefing.docx --remote /Clients/briefing.docx
- 下载:... download --remote /Clients/briefing.docx --local /tmp/briefing.docx
- 移动/共享链接:使用 move 和 share 子命令。
- 该脚本可解析本地化/特殊文件夹别名(例如 Documents 和 Documentos)。
更多详情:references/drive.md。
6. 联系人
- - 列出/搜索:python scripts/contacts_ops.py list --top 20
- 创建:... create --given-name 简 --surname 多伊 --email jane.doe@example.com
- 更新/删除:... update ... / ... delete
- 联系人是默认作用域集的一部分,并作为一级工作流得到支持。
更多详情:references/contacts.md。
7. 邮件推送模式(Webhook 适配器)
- - 适配器服务器(Graph 握手 + clientState 验证 + 入队):
bash
python scripts/mail
webhookadapter.py serve \
--host 0.0.0.0 --port 8789 --path /graph/mail \
--client-state $GRAPH
WEBHOOKCLIENT_STATE
- - 订阅生命周期(create/status/renew/delete/list):
bash
python scripts/mail_subscriptions.py create \
--notification-url https://graph-hook.example.com/graph/mail \
--client-state $GRAPH
WEBHOOKCLIENT_STATE \
--minutes 4200
- 默认资源是 me/messages(推荐以获得更好的投递覆盖)。仅在高级/限定范围场景下使用 --resource 覆盖。
- - 异步工作器(去重 + 默认唤醒信号发送至 OpenClaw /hooks/wake):
bash
python scripts/mail
webhookworker.py loop \
--session-key $OPENCLAW
SESSIONKEY \
--hook-url $OPENCLAW
HOOKURL \
--hook-token $OPENCLAW
HOOKTOKEN
- 默认模式为 wake(/hooks/wake,mode=now)。仅在您明确需要每条消息的丰富负载投递时使用 --hook-action agent。
- state/mail
webhookqueue.jsonl
- state/mail
webhookdedupe.json
- - 自动化 EC2 引导(Caddy + systemd + 续订定时器):
bash
sudo bash scripts/setup
mailwebhook_ec2.sh \
--domain graphhook.example.com \
--hook-url http://127.0.0.1:18789/hooks/wake \
--hook-token
HOOKTOKEN> \
--session-key hook:graph-mail \
--client-state WEBHOOKCLIENT_STATE> \
--repo-root $(pwd)
- 使用 --dry-run 在应用更改前预览所有特权写入和服务操作。
bash
sudo bash scripts/runmailwebhooke2esetup.sh \
--domain graphhook.example.com \
--hook-token HOOKTOKEN> \
--hook-url http://127.0.0.1:18789/hooks/wake \
--session-key hook:graph-mail \
--test-email tar.alitar@outlook.com
- 使用 --dry-run 获取无变更执行计划(不写入 /etc,不执行 systemctl,不创建订阅,不发送邮件)。
- 当设置完全验证通过时,输出以 READYFORPUSH: YES 结束。
bash
sudo bash scripts/runmailwebhooke2esetup.sh \
--domain graphhook.example.com \
--hook-token HOOKTOKEN> \
--configure-openclaw-hooks \
--openclaw-config /home/ubuntu/.openclaw/openclaw.json \
--openclaw-service-name auto \
--openclaw-hooks-path /hooks \
--openclaw-allow-request-session-key true \
--test-