Skip to main content

Local-first Microsoft 365 email, calendar, and Teams toolkit with MCP support and guarded write flows

Project description

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 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:

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:

[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:

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

If you need guarded email draft/send support:

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

If you need Teams channel-message reads:

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:

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

Check write-token status:

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

Run tests:

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

Try a basic mailbox read:

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

Try a basic Teams read:

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:

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:

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:

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:

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:

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:

codex mcp get ms365
claude mcp get ms365

Both should show:

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

Pinned/reproducible registration:

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:

uv sync --extra mcp

Run the local stdio MCP server:

uv run --extra mcp ms365-toolkit-mcp

Or run it directly from the module entrypoint:

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:

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:

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:

./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:

./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:

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:

/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:

./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:

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:
uvx --from ms365-toolkit==0.1.15 ms365-toolkit auth login
  1. Build a local mailbox index:
uv run --with '.[dev]' ms365-toolkit sync-mail-index
  1. Export thread candidates for labeling:
uv run --with '.[dev]' ms365-toolkit export-label-candidates --top 30 --sync-index
  1. Review and label locally:
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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ms365_toolkit-0.1.15.tar.gz (268.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ms365_toolkit-0.1.15-py3-none-any.whl (107.5 kB view details)

Uploaded Python 3

File details

Details for the file ms365_toolkit-0.1.15.tar.gz.

File metadata

  • Download URL: ms365_toolkit-0.1.15.tar.gz
  • Upload date:
  • Size: 268.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for ms365_toolkit-0.1.15.tar.gz
Algorithm Hash digest
SHA256 937ee278a96ee6bbe79693ce3753d149c4a95fa477c107aacd115307981c35e1
MD5 8a430efb7a415000ec3ca5421a8e26aa
BLAKE2b-256 f84d49d436b3b7f03eee3e66f44a44a803ddd5a5d5da3859b01464f076690376

See more details on using hashes here.

File details

Details for the file ms365_toolkit-0.1.15-py3-none-any.whl.

File metadata

  • Download URL: ms365_toolkit-0.1.15-py3-none-any.whl
  • Upload date:
  • Size: 107.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for ms365_toolkit-0.1.15-py3-none-any.whl
Algorithm Hash digest
SHA256 08a89fd90d838a5ce2030fe058428aadd1795f972e1d5870b0ea258afe4e54f7
MD5 49b41ce5ce5253895e1ffc6b30f4a611
BLAKE2b-256 f3405529148ffcf687e9493764d15bcf183bdf86b3820c8819ad54ee9720ecc9

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page