Skip to main content
Glama
PhamHoang16

outlook-bridge

by PhamHoang16
README.md
# outlook-bridge

MCP server để Claude tìm kiếm và đọc mail trong **Outlook classic** (Windows,
qua COM) — `list_folders`, `search_mail`, `read_mail`, `get_thread`,
`read_attachment`. Xem thiết kế đầy đủ ở
[`docs/superpowers/specs/2026-08-24-outlook-bridge-design.md`](docs/superpowers/specs/2026-08-24-outlook-bridge-design.md)
và kế hoạch triển khai ở
[`docs/superpowers/plans/2026-08-24-outlook-bridge-a.md`](docs/superpowers/plans/2026-08-24-outlook-bridge-a.md).

## Read-only — không có tool ghi

Server này **không** có `send_mail`, `delete_mail`, `move_mail`,
`mark_as_read`, hay bất kỳ tool nào sửa/xoá/gửi mail. Không có method nào
trong `Win32MailStore` gọi `.Send()`, `.Save()`, `.Delete()`, `.Move()`,
`.MarkAsRead()`, hay gán giá trị vào một thuộc tính Outlook nào. **Ngoại lệ
duy nhất**: `read_attachment` ghi tệp đính kèm ra một thư mục tạm do server tự
tạo (`tempfile.TemporaryDirectory`) để trích text, rồi xoá thư mục đó ngay sau
— `SaveAsFile` là một API đọc, không sửa gì trong Outlook.

## Chạy trên Linux (máy dev) — `FakeMailStore`

Không cần Outlook, không cần Windows. `FakeMailStore` nạp một fixture YAML
(~50 mail tổng hợp: nhiều folder, nhiều store, luồng hội thoại, tệp đính kèm
đủ loại, mail tiếng Việt, mail body dài, mail rỗng, item không phải mail) và
implement cùng interface `MailStore` mà `Win32MailStore` implement.

```
python -m venv .venv
.venv/bin/pip install -e ".[dev]"

# Chạy server với store giả (không cần Outlook)
OUTLOOK_BRIDGE_FAKE=1 OUTLOOK_BRIDGE_FIXTURE=tests/fixtures/mailbox.yaml \
  .venv/bin/python server.py
```

## Chạy test

**Linux** (phần lớn logic — pure Python + `FakeMailStore`):
```
.venv/bin/pytest
```

**Windows** (bộ contract test dùng CHUNG cho cả hai store — đây là cách duy
nhất chứng minh `Win32MailStore` thật sự khớp hành vi với `FakeMailStore`,
không chỉ "chạy được"):
```
set OUTLOOK_BRIDGE_CONTRACT_WIN32=1
.venv\Scripts\pytest tests/contract -v
```
Vài case trong bộ này SKIP có chủ đích trên mailbox thật (dữ liệu chỉ có
trong fixture của `FakeMailStore`) — xem mục "Test SKIP có chủ đích" trong
`docs/WINDOWS-RUNBOOK.md` trước khi coi một SKIP là dấu hiệu có vấn đề.

Xem [`docs/INSTALL-windows.md`](docs/INSTALL-windows.md) để cài đặt và đăng
ký MCP server trên Windows, và
[`docs/WINDOWS-RUNBOOK.md`](docs/WINDOWS-RUNBOOK.md) cho trình tự việc cần
làm khi ngồi trước máy Windows thật lần đầu.

## Chẩn đoán môi trường — `doctor.py`

```
python doctor.py            # trên Linux: báo "không phải Windows", degrade an toàn
python doctor.py --verbose  # trên Windows: thêm chi tiết root.Name, cây folder, giá trị raw
```

| Kết quả | Nghĩa | Hành động |
|---|---|---|
| `com_ok: True`, `inbox_count` ra số | Đường COM thông | Sẵn sàng đăng ký MCP server |
| `com_ok: False` + nói tới New Outlook / GPO | Kiến trúc này không dùng được trên máy đó | Dừng — không có phương án dự phòng (rủi ro R2 bên dưới) |
| `com_ok: True`, `index_ok: False` | Vẫn dùng được, nhưng search theo nội dung body sẽ tự fallback duyệt tuần tự (chậm hơn, có cảnh báo trong kết quả) | Dùng bình thường |

## Hai rủi ro chưa ai giải quyết được (spec §13)

**R2 — New Outlook sẽ dần thay thế Outlook classic, và COM biến mất theo.**
Microsoft đang force-migrate người dùng sang New Outlook, vốn không đăng ký
COM server (`doctor.py` báo `com_ok: False` khi việc này xảy ra). Đây là thứ
**có hạn sử dụng**, không phải một lỗi có thể vá. Đường thoát duy nhất khi nó
xảy ra là viết lại `Win32MailStore` trên nền Microsoft Graph API — kiến trúc
của project (toàn bộ chi tiết COM chỉ nằm trong đúng hai file, `win32_store.py`
và `doctor.py`) giới hạn thiệt hại vào việc viết lại đúng hai file đó, không
phải toàn bộ hệ thống.

**R3 — Nội dung mail nội bộ công ty đi qua API của Anthropic.** COM chạy hoàn
toàn local (không có gì rời máy để đọc mail), nhưng để Claude *trả lời* dựa
trên nội dung mail, nội dung đó phải được gửi lên API. Đây là đánh đổi cố hữu
của việc dùng một trợ lý AI để đọc mail — không server nào giải quyết được nó
thay bạn.

**Biện pháp kiểm soát duy nhất**: `folder_allowlist` trong `config.toml`.
Đặt nó thành danh sách folder cụ thể (store-qualified, ví dụ
`"Mailbox - Nguyen Van A/Inbox"`) để giới hạn phạm vi mail server này *có thể
thấy* — mail ngoài allowlist không được đọc bởi bất kỳ tool nào, dưới bất kỳ
đường gọi nào (search, đọc theo id, đọc thread, tải tệp đính kèm), kể cả khi
một luồng hội thoại bắc qua ranh giới allowlist.

**Khớp là CHÍNH XÁC, không phải theo tiền tố.** Cho phép `"Inbox"` KHÔNG kéo
theo `"Inbox/Bao cao"` — mỗi folder muốn cho phép phải được liệt kê riêng.
Đây là lựa chọn thiết kế có chủ đích, không phải một giới hạn: một allowlist
theo tiền tố rất dễ vô tình cho qua một folder con nhạy cảm mà người cấu hình
không nghĩ tới. Nếu không cấu hình `folder_allowlist` (mặc định, danh sách
rỗng), mọi folder trong mailbox đều đọc được — hãy cấu hình nó trước khi trỏ
server này vào một mailbox công ty thật. Xem `docs/WINDOWS-RUNBOOK.md` bước 2
để biết cách lấy đúng tên folder store-qualified từ `list_folders`.

Nếu cần siết hơn nữa: thu hẹp `folder_allowlist` xuống một vài folder cụ thể,
hoặc — nếu vẫn chưa đủ — cân nhắc việc chỉ trả metadata + snippet thay vì toàn
bộ nội dung mail (hiện chưa có tool nào làm việc này; sẽ cần một tool mới).