Microsoft 365 superpowers for Python and AI agents
Project description
office-connect
Microsoft 365 superpowers for Python and AI agents.
office-connect is a Python library and stdio MCP server that gives both human developers and AI agents structured access to Microsoft 365 — mail, calendar, Teams, chats, files, directory, profile — through the Microsoft Graph API, behind a simple token-based authentication flow.
Features
- MCP Server -- Stdio-based MCP server for seamless integration with AI assistants
- Mail -- List messages, read bodies and attachments, send, draft, reply, manage categories
- Calendar -- List calendars, query events, create events, check free/busy schedules
- Teams -- List joined teams, channels, channel messages, and team members
- Chat -- List recent 1:1, group, and meeting chats with message history
- Files / OneDrive -- Browse drives, list folders, download file content, search by name
- SharePoint -- Search sites and list document libraries
- Directory -- List organization users, resolve managers, fetch profile photos
- Profile -- Retrieve the authenticated user's profile details
- Mock Transport -- Full synthetic data layer for testing without a real O365 account
Quick Start
Installation
poetry add office-connect
MCP Server
office-connect --keyfile path/to/token.json
Token File Format
{
"app": "MyApp",
"email": "user@example.com",
"access_token": "eyJ...",
"refresh_token": "1.AUs...",
"client_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"client_secret": "...",
"tenant_id": "common"
}
Tokens are automatically refreshed (proactively when within 15 min of expiry, reactively on a 401 from Graph) and persisted back to the file (0600 permissions).
Signing in for the first time (office-connect login)
When you don't have any tokens yet, use the device-code login. The first run takes your Azure AD app credentials once and saves them; every subsequent re-auth needs no arguments at all.
# Cold start — supply the Azure AD app once
office-connect login --client-id <APP_ID> --tenant-id <TENANT_ID> [--client-secret <SECRET>]
# From there on — re-auth with zero arguments
office-connect login
# Narrow scopes (default is all eight groups)
office-connect login --scope mail --scope calendar
office-connect login prints a microsoft.com/devicelogin URL plus a short code, blocks polling Microsoft until you finish signing in, then writes two files:
~/.config/office-connect/token.json— the keyfile (access + refresh tokens), 0600 permissions~/.config/office-connect/config.json— the app config: client_id / tenant_id / optional client_secret, 0600 permissions
Both paths are configurable (--keyfile and --app-config, or env var OFFICE_CONNECT_APP_CONFIG). When login resolves credentials it tries, in order: explicit CLI args → existing keyfile → O365_CLIENT_ID etc. → app-config file. You can also pre-create the app-config file by hand — it's just a JSON object with client_id, tenant_id, and optional client_secret.
Available scope groups: profile, directory, mail, calendar, chat, teams, drive, tasks.
Your Azure AD app registration must have "Allow public client flows" enabled in its authentication manifest for device-code flow to work.
Refreshing tokens during a running session
You don't have to. The MCP keeps tokens fresh on its own — within a session via in-process refresh, across restarts via the keyfile. Manual top-ups only matter if even the refresh token has been invalidated (typically after ~90 days of inactivity, or by tenant policy).
If a host application exports tokens through an admin endpoint, drop the exported JSON at the canonical keyfile location with:
office-connect import-token ~/Downloads/token_export.json
# override destination with --dest /custom/path/token.json
Both login and import-token write atomically with 0600 permissions. The MCP server compares the keyfile's mtime before each tool call; once the file changes, the next tool invocation rebuilds the Graph instance from the new contents — Claude Desktop, Cursor, etc. do not need to be restarted.
Permission tiers — what Claude is allowed to do
Three tiers, from most to least restrictive:
| tier | what it allows |
|---|---|
read_only |
list/get/search/peek — no mutation of Microsoft 365 state |
drafts (default) |
everything read_only does, plus creating and updating draft emails. No sending. |
all |
everything drafts does, plus sending mail, moving/deleting mail, flagging read, creating calendar events |
Three places can set the tier — the most restrictive of the ones that are set wins:
-
MCP launcher CLI flag:
--permission-level read_only|drafts|allin the args list passed by Claude Desktop / Cursor / etc. -
Environment variable:
OFFICE_CONNECT_PERMISSION_LEVEL=read_only -
Global policy file: a JSON object at
~/.config/office-connect/policy.json(overridable via--policy-file PATHor$OFFICE_CONNECT_POLICY):{ "permission_level": "drafts" }
The policy file acts as a host-wide ceiling: regardless of how any individual MCP launcher is configured, the resolver clamps the effective tier down to the most restrictive value found. A launcher can always tighten further on top.
Tools above the effective tier are removed from list_tools and refused by call_tool (defense in depth, fail-closed for unknown tool names).
Mock Transport
A full mock layer for development and testing — no real O365 account needed.
from office_con.msgraph.ms_graph_handler import MsGraphInstance
from office_con.testing.fixtures import default_mock_profile
profile = default_mock_profile()
graph = MsGraphInstance(endpoint="https://graph.microsoft.com/v1.0/")
graph.enable_mock(profile)
# Now use graph exactly like the real thing
mail = graph.get_mail()
inbox = await mail.email_index_async(limit=10)
The mock provides:
- 18+ inbox messages (rich HTML with signatures, newsletters, notifications)
- 9 mail folders with subfolders, fake downloadable attachments
- 127 calendar events across 3 months (OOF, Teams calls, tentative, free blocks)
- 25 directory users with org hierarchy, departments, and profile photos
- Teams, chats, categories, OneDrive stubs
- Synthetic JWT tokens
Face photos can be loaded from JPEG files via set_faces_dir() or the FACES_DIR env var. Falls back to generated SVG initials.
Safety: mock is automatically blocked on Azure App Service and production URLs.
Project Structure
office-connect/
├── office_con/
│ ├── auth/ # Azure AD OAuth, scopes, background refresh
│ ├── db/ # Company directory builder and storage
│ ├── msgraph/ # MS Graph API handlers
│ │ ├── ms_graph_handler.py # Central class: MsGraphInstance
│ │ ├── mail_handler.py # Send, draft, reply, list, categories
│ │ ├── calendar_handler.py # Events, schedules, timezones
│ │ ├── directory_handler.py # Users, managers, photos
│ │ ├── teams_handler.py # Teams, channels, messages
│ │ ├── chat_handler.py # 1:1, group, meeting chats
│ │ ├── files_handler.py # OneDrive, SharePoint
│ │ └── profile_handler.py # /me profile
│ ├── testing/ # Mock transport, fixtures, synthetic tokens
│ ├── utils/ # Excel parser, health check
│ └── mcp_server.py # MCP server entry point + CLI
├── tests/
├── docs/
├── pyproject.toml
└── LICENSE
Development
poetry install
poetry run pytest # tests (skips integration tests without token file)
poetry run ruff check # lint
MCP Tools
| Tool | Description |
|---|---|
o365_get_profile |
Current user's profile |
o365_list_mail |
List recent inbox emails |
o365_get_mail |
Single email with full body and attachments |
o365_get_mail_categories |
Outlook mail categories |
o365_list_calendars |
User's calendars |
o365_get_events |
Calendar events in a date range |
o365_get_schedule |
Free/busy availability |
o365_list_teams |
Joined Microsoft Teams |
o365_list_channels |
Channels in a team |
o365_get_channel_messages |
Channel messages |
o365_get_team_members |
Team members |
o365_list_chats |
Recent chats |
o365_get_chat_messages |
Chat messages |
o365_get_chat_members |
Chat members |
o365_get_my_drive |
Default OneDrive info |
o365_list_drive_items |
Files and folders |
o365_get_file_content |
Download file content |
o365_search_files |
Search OneDrive |
o365_search_sites |
Search SharePoint sites |
o365_get_site_drives |
Site document libraries |
o365_list_users |
Organization directory |
o365_get_user_manager |
User's manager |
License
MIT -- Copyright (c) 2026 Michael Ikemann
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file office_connect-0.1.8.tar.gz.
File metadata
- Download URL: office_connect-0.1.8.tar.gz
- Upload date:
- Size: 140.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.1.1 CPython/3.13.11 Darwin/25.2.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
08438243a95ace02a1056212bdce2ce7c1eb436e018af32dbb5151bc5bb8aadf
|
|
| MD5 |
c6dd5dfaf84ea00f109d37059429c3cb
|
|
| BLAKE2b-256 |
e69527471be726aca2da64a88e8fd13cad772ae748cf9c305e24b9aaaf2ca669
|
File details
Details for the file office_connect-0.1.8-py3-none-any.whl.
File metadata
- Download URL: office_connect-0.1.8-py3-none-any.whl
- Upload date:
- Size: 156.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.1.1 CPython/3.13.11 Darwin/25.2.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ea76147545c12ea1351e2867c5db1a19e528e5ff432b8260ab94a45b6c40632f
|
|
| MD5 |
17338fe2a6476c238de80f697e9c6099
|
|
| BLAKE2b-256 |
44ebd74fad455c11ad67f80d276591831ac541b02a02be2892a624eab3642556
|