Metadata-Version: 2.4
Name: ms365-toolkit
Version: 0.1.15
Summary: Local-first Microsoft 365 email, calendar, and Teams toolkit with MCP support and guarded write flows
Project-URL: Homepage, https://github.com/sadhiappan/ms365-toolkit
Project-URL: Repository, https://github.com/sadhiappan/ms365-toolkit
Project-URL: Issues, https://github.com/sadhiappan/ms365-toolkit/issues
Author-email: Shiv Adhiappan <shivaram1190@gmail.com>
Maintainer-email: Shiv Adhiappan <shivaram1190@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: calendar,email,mcp,microsoft-365,microsoft-graph,teams
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Communications :: Email
Classifier: Topic :: Office/Business
Requires-Python: >=3.11
Requires-Dist: beautifulsoup4<5,>=4.12
Requires-Dist: keyring<26,>=25.0
Requires-Dist: msal<2,>=1.31
Requires-Dist: openpyxl<4,>=3.1
Requires-Dist: packaging<26,>=24
Requires-Dist: pyjwt<3,>=2.8
Requires-Dist: pypdf<6,>=5.0
Requires-Dist: python-docx<2,>=1.1
Requires-Dist: python-pptx<2,>=1.0
Requires-Dist: structlog<26,>=24.4
Provides-Extra: dev
Requires-Dist: mypy<2,>=1.13; extra == 'dev'
Requires-Dist: pytest-asyncio<1,>=0.24; extra == 'dev'
Requires-Dist: pytest-cov<7,>=6.0; extra == 'dev'
Requires-Dist: pytest<9,>=8.3; extra == 'dev'
Requires-Dist: ruff<1,>=0.8; extra == 'dev'
Provides-Extra: mcp
Requires-Dist: fastmcp<4,>=3.0; extra == 'mcp'
Description-Content-Type: text/markdown

# ms365-toolkit

`ms365-toolkit` is a generic Microsoft 365 email, calendar, and Teams toolkit with:
- device-code auth
- Graph read clients and guarded email write flows
- local mailbox indexing
- triage and briefing workflows
- a local labeling workflow for improving per-user email intelligence

Public beta note: this is local-first tooling for delegated Microsoft 365 access.
Users configure their own Microsoft Entra public-client app and grant the delegated
Graph permissions required by the features they use.

See [CHANGELOG.md](CHANGELOG.md) for release notes.

## Security and Privacy

- Tokens, local profile config, mailbox indexes, labels, audit logs, and usage analytics stay on the user's machine.
- Device-code auth uses a public-client Entra app registration; no client secret is required.
- Usage analytics record coarse command/tool metadata, durations, sizes, and sanitized errors, not email bodies, recipients, subjects, transcript text, raw Graph IDs, or tokens.
- Guarded email writes require a write token, `user.endpoint = "me"`, configured allowlists, rate limits, confirmation/audit checks, and a content hash for send.
- Microsoft Graph access is delegated and tenant-controlled. Some Teams channel-message, file, transcript, and Copilot notes features require additional scopes, admin consent, licenses, or available tenant data.

## Self-Service Setup

### 1. Prerequisites

- Python 3.11+
- `uv`
- A Microsoft 365 account with access to the mailbox you want to use
- A Microsoft Entra app registration configured as a public client for device-code auth

This project uses device-code flow. Users do not need to put a client secret in local config.

### 2. Create a Microsoft Entra App Registration

Create an app registration in your tenant and capture these two values:

- `tenant_id`
- `client_id`

Configure the app as a public client so device-code auth is allowed.

For normal read-only use, the delegated Microsoft Graph permissions should include:

- `Mail.Read`
- `Calendars.Read`
- `Chat.Read`
- `Team.ReadBasic.All`
- `Channel.ReadBasic.All`
- `MailboxSettings.Read`

If you want to read Teams channel messages, also grant:

- `ChannelMessage.Read.All`

If you plan to add or use write operations later, also grant:

- `Mail.ReadWrite`
- `Mail.Send`
- `Calendars.ReadWrite`
- `MailboxSettings.ReadWrite`

If your organization requires admin consent for these scopes, an administrator must grant it before login will work.

Teams notes:

- Teams reads currently require `endpoint = "me"`.
- Channel message reads typically require admin-approved consent for `ChannelMessage.Read.All`.
- Users who need channel-message reads should run `auth login --teams-channel-messages`.

### 3. Create Local Config

Create a local profile config from the example:

```bash
mkdir -p ~/.ms365-toolkit/profiles/default
cp config.example.toml ~/.ms365-toolkit/profiles/default/config.toml
```

Edit `~/.ms365-toolkit/profiles/default/config.toml` and set at least:

```toml
[auth]
tenant_id = "<your-entra-tenant-id>"
client_id = "<your-app-registration-client-id>"

[user]
endpoint = "me"
user_principal_name = ""
timezone = "America/Chicago"
```

Notes:

- Use `endpoint = "me"` for your own mailbox.
- Use `endpoint = "users"` only for delegated/shared mailbox access, and then set `user_principal_name` to the target mailbox UPN.
- `domain_allowlist = []` is acceptable for read-only use, but write operations will be blocked until you set it.

### 4. Authenticate

Run device-code login:

```bash
uvx --from ms365-toolkit==0.1.15 ms365-toolkit auth login
```

If you need guarded email draft/send support:

```bash
uvx --from ms365-toolkit==0.1.15 ms365-toolkit auth login --write
```

If you need Teams channel-message reads:

```bash
uvx --from ms365-toolkit==0.1.15 ms365-toolkit auth login --teams-channel-messages
```

The CLI will print a verification URL and user code. Complete that sign-in flow in a browser.

### 5. Verify It Works

Check auth status:

```bash
uvx --from ms365-toolkit==0.1.15 ms365-toolkit auth status
```

Check write-token status:

```bash
uvx --from ms365-toolkit==0.1.15 ms365-toolkit auth status --write
```

Run tests:

```bash
uv run --with '.[dev]' pytest tests/unit/ -q
```

Try a basic mailbox read:

```bash
uvx --from ms365-toolkit==0.1.15 ms365-toolkit inbox --top 5
```

Try a basic Teams read:

```bash
uvx --from ms365-toolkit==0.1.15 ms365-toolkit list-chats --top 5
uvx --from ms365-toolkit==0.1.15 ms365-toolkit search-chats "Sara Kamal" --top 3
uvx --from ms365-toolkit==0.1.15 ms365-toolkit list-teams
uvx --from ms365-toolkit==0.1.15 ms365-toolkit search-teams-messages "cost center" --top 5
uvx --from ms365-toolkit==0.1.15 ms365-toolkit search-channel-messages <team_id> <channel_id> "authorization rate" --top 5
uvx --from ms365-toolkit==0.1.15 ms365-toolkit read-channel-thread <team_id> <channel_id> <message_id> --top 10
```

Global Teams search returns ranked search hits and snippets, not hydrated full message bodies.

Try attachment download:

```bash
uvx --from ms365-toolkit==0.1.15 ms365-toolkit download-email-attachments <message_id> --output-dir ./downloads
uvx --from ms365-toolkit==0.1.15 ms365-toolkit list-email-attachments <message_id>
uvx --from ms365-toolkit==0.1.15 ms365-toolkit read-email-attachment <message_id> <attachment_id> --max-chars 50000
```

Try the guarded email write flow:

```bash
uvx --from ms365-toolkit==0.1.15 ms365-toolkit create-email-draft --to user@example.com --subject "Hello" --body "<p>Hello</p>"
uvx --from ms365-toolkit==0.1.15 ms365-toolkit create-reply-draft <message_id> --body "<p>Reply</p>"
uvx --from ms365-toolkit==0.1.15 ms365-toolkit send-email-draft <draft_id> <content_hash>
```

Inspect local usage analytics:

```bash
uvx --from ms365-toolkit==0.1.15 ms365-toolkit usage summary --days 7
uvx --from ms365-toolkit==0.1.15 ms365-toolkit usage tools --days 7
uvx --from ms365-toolkit==0.1.15 ms365-toolkit usage errors --days 7
uvx --from ms365-toolkit==0.1.15 ms365-toolkit usage slow --days 7 --top 10
```

Usage analytics are local-only JSONL files under the active profile. They record coarse
tool/command metadata, durations, result sizes, and sanitized error categories. They do not
record email bodies, subjects, recipients, message IDs, transcript text, or tokens.
Use `usage slow` to find slow tools and oversized MCP responses.

Check whether a local CLI/MCP install is stale:

```bash
uvx --from ms365-toolkit==0.1.15 ms365-toolkit version --check
uvx --from ms365-toolkit==0.1.15 ms365-toolkit doctor
```

`doctor` reports the installed version, latest PyPI version, profile/config status,
local version-status cache path, and Codex/Claude update commands when stale.

### 5b. Installable MCP Setup

The public install shape is a local stdio MCP launched from the published wrapper package.
These commands work after the corresponding package version has been published to PyPI.

Recommended auto-latest registration:

```bash
codex mcp add ms365 --env MS365_TOOLKIT_PROFILE=default --env UV_CACHE_DIR=/tmp/ms365-toolkit-uv-cache --env UV_TOOL_DIR=/tmp/ms365-toolkit-uv-tools -- sh -lc 'cd /tmp && exec uvx ms365-toolkit-mcp@latest'
claude mcp add -s user ms365 -e MS365_TOOLKIT_PROFILE=default -e UV_CACHE_DIR=/tmp/ms365-toolkit-uv-cache -e UV_TOOL_DIR=/tmp/ms365-toolkit-uv-tools -- sh -lc 'cd /tmp && exec uvx ms365-toolkit-mcp@latest'
```

The `@latest` suffix asks `uvx` to refresh cached package metadata and resolve the
latest published MCP wrapper when a new MCP process starts.

To update an existing pinned registration, rerun the recommended registration command for
Codex and/or Claude. Then restart any active Codex or Claude sessions so they spawn a new
MCP process. Verify the active registration with:

```bash
codex mcp get ms365
claude mcp get ms365
```

Both should show:

```bash
sh -lc cd /tmp && exec uvx ms365-toolkit-mcp@latest
```

Pinned/reproducible registration:

```bash
codex mcp add ms365 --env MS365_TOOLKIT_PROFILE=default --env UV_CACHE_DIR=/tmp/ms365-toolkit-uv-cache --env UV_TOOL_DIR=/tmp/ms365-toolkit-uv-tools -- sh -lc 'cd /tmp && exec uvx --from ms365-toolkit-mcp==0.1.15 ms365-toolkit-mcp'
claude mcp add -s user ms365 -e MS365_TOOLKIT_PROFILE=default -e UV_CACHE_DIR=/tmp/ms365-toolkit-uv-cache -e UV_TOOL_DIR=/tmp/ms365-toolkit-uv-tools -- sh -lc 'cd /tmp && exec uvx --from ms365-toolkit-mcp==0.1.15 ms365-toolkit-mcp'
```

Pinned registrations are deterministic but can become stale. Use them only when you need
an exactly reproducible MCP version. To inspect MCP version state in a session, call
`get_toolkit_status` or run `ms365-toolkit doctor`. Set `MS365_TOOLKIT_WARN_STALE=1`
to print a cache-only startup warning when a prior explicit check has already found a
newer version.

Email, Teams, and calendar list/search MCP tools return compact summaries by default and accept
`max_chars` to cap response size. Use the corresponding read/get tool when you need the
full message, event, thread, attachment, transcript, or meeting-notes payload.
When a compact MCP response returns `truncated: true` with `next_offset`, repeat the same
tool call with `offset=<next_offset>` to continue through the current ordered result set.
If `next_offset` is `null`, increase `max_chars` because the next item did not fit.
Graph-backed MCP collection tools can also return `next_cursor`. Repeat the same tool
with `cursor=<next_cursor>` to continue with Microsoft Graph's next page. If both
`next_offset` and `next_cursor` are possible, consume `next_offset` first; `next_cursor`
is only returned after the current response page fits inside `max_chars`.
Cursor support covers `list_inbox`, `search_emails`, `list_events`, `search_events`,
`list_calendars`, `list_chats`, `list_chat_messages`, `search_teams_messages`,
`list_teams`, `list_channels`, `list_channel_messages`, and `list_channel_replies`.
Local derived searches such as `search_chats`, `search_chat_messages`, and
`search_channel_messages` remain bounded first-page convenience filters. Use
`search_teams_messages` for paged global Teams message search.

Supported v1 tools include guarded email writes:
- `get_toolkit_status`
- `list_inbox`
- `search_emails`
- `read_email`
- `list_email_attachments`
- `read_email_attachment`
- `create_email_draft`
- `create_reply_draft`
- `send_email_draft`
- `list_folders`
- `list_events`
- `get_event`
- `search_events`
- `list_calendars`
- `find_free_slots`
- `list_chats`
- `search_chats`
- `list_chat_messages`
- `search_chat_messages`
- `search_teams_messages`
- `read_chat_message`
- `list_teams`
- `list_channels`
- `list_channel_messages`
- `search_channel_messages`
- `list_channel_replies`
- `read_channel_message`
- `read_channel_thread`

All MCP datetime inputs must be timezone-aware ISO 8601 strings.
Email write operations require a write token, `user.endpoint = "me"`, and a configured domain allowlist.

### 5c. Repo-Local MCP Fallback

Install the MCP extra:

```bash
uv sync --extra mcp
```

Run the local stdio MCP server:

```bash
uv run --extra mcp ms365-toolkit-mcp
```

Or run it directly from the module entrypoint:

```bash
uv run --extra mcp python -m ms365_toolkit.mcp
```

### 5d. Real MCP Smoke Test

Run the opt-in real-world MCP smoke test from the local checkout:

```bash
uv run --extra mcp python scripts/smoke_mcp.py --profile default
```

The smoke test launches the local stdio MCP server and calls read-only tools
against real Microsoft Graph data. Email, calendar, toolkit-status, compact
response, and continuation-shape checks are required. Teams, channel messages,
files, transcripts, and meeting notes are tiered optional checks and report
`SKIP` when tenant data or optional delegated scopes are unavailable.

The output is redacted by design. It prints only tool names, pass/fail/skip
status, counts, booleans, and hashed ID prefixes. It does not print subjects,
senders, recipients, message previews, event bodies, Teams message text,
transcript text, meeting notes, tokens, or raw Graph IDs.

Useful variants:

```bash
uv run --extra mcp python scripts/smoke_mcp.py --profile work
uv run --extra mcp python scripts/smoke_mcp.py --timeout 60
```

### 5e. Repo-Local One-Command Setup

Use the installer script to wire everything up for both tools:

```bash
./scripts/setup_mcp.sh
```

This is the developer fallback, not the primary public install path.
It runs the MCP server from the local checkout and does not use the published
`ms365-toolkit-mcp@latest` package.

What it does:
- installs the MCP extra with `uv`
- checks that your selected MS365 profile is already authenticated
- registers the `ms365` server with Codex
- registers the `ms365` server with Claude using `user` scope so it is available across sessions

Useful variants:

```bash
./scripts/setup_mcp.sh --target codex
./scripts/setup_mcp.sh --target claude
./scripts/setup_mcp.sh --profile default
./scripts/setup_mcp.sh --dry-run
```

Equivalent Make targets:

```bash
make mcp-setup
make mcp-setup-codex
make mcp-setup-claude
```

### 5f. Claude Review Workflow

This repo includes project-local Claude review commands and a shell workflow.

In an interactive `claude` session, use:

```text
/review-critical
/review-critical teams changes
/review-commit f9c3079
/consult Should we expose participant names in 1:1 chats?
```

For terminal or Codex-driven runs, use:

```bash
./scripts/claude_workflow.sh review
./scripts/claude_workflow.sh review-commit f9c3079
./scripts/claude_workflow.sh consult "Should Teams support user.endpoint=users?"
```

Equivalent Make targets:

```bash
make claude-review
make claude-review-commit COMMIT=f9c3079
make claude-consult PROMPT="Should Teams support user.endpoint=users?"
```

### 6. Where Secrets and Local State Live

Do not put secrets or mailbox data into the repo.

Local config:

- `~/.ms365-toolkit/profiles/<profile>/config.toml`

Token caches:

- stored in the OS keychain via `keyring`
- read cache key: `ms365-read-token-<profile>`
- write cache key: `ms365-write-token-<profile>`

Mailbox-derived local state:

- `~/.ms365-toolkit/profiles/<profile>/mail_index.db`
- `~/.ms365-toolkit/profiles/<profile>/thread_labels.jsonl`

## Shareable Code vs Private Local State

The repository is intended to be generic and shareable.

Generic, shareable parts:
- source code under `src/`
- tests under `tests/`
- build and dependency config
- generic CLI commands and labeling UI

Private, per-user local state:
- `~/.ms365-toolkit/profiles/<profile>/config.toml`
- token caches
- local mailbox index databases
- exported label datasets such as `thread_labels.jsonl`
- any mailbox-derived training or evaluation data

Do not commit or share profile directories, token caches, mailbox indexes, or label datasets unless you explicitly want to share mailbox-derived data.

## Local Workflow

1. Configure a local profile under `~/.ms365-toolkit/profiles/<profile>/config.toml`
2. Authenticate with:

```bash
uvx --from ms365-toolkit==0.1.15 ms365-toolkit auth login
```

3. Build a local mailbox index:

```bash
uv run --with '.[dev]' ms365-toolkit sync-mail-index
```

4. Export thread candidates for labeling:

```bash
uv run --with '.[dev]' ms365-toolkit export-label-candidates --top 30 --sync-index
```

5. Review and label locally:

```bash
uv run --with '.[dev]' ms365-toolkit label-ui
```

The learned behavior from triage is driven by each user’s local labeled dataset, not by checked-in project state.
