Outlook (Microsoft 365) access
Talks to https://outlook.office.com/api/v2.0 with a bearer token captured
from the signed-in Outlook Web tab. The browser is needed only to mint the
token (~25h lifetime) — every actual request is a plain server-side curl.
Setup (once)
npm i -g @fetchproxy/cli # provides `fpx`
fpx profile add outlook --domain outlook.cloud.microsoft \
--domain outlook.office.com
fpx profile declare outlook \
--capture-header authorization@outlook.cloud.microsoft \
--capture-header authorization@outlook.office.com \
--allow-download
Declare all scopes before the first pairing — widening them later forces the
user to re-approve. The first fpx call prints a 6-digit pair code to approve in
the ContextMint Bridge extension popup; the grant then persists.
Requires the ContextMint Bridge extension, from
https://github.com/nullnet-app/contextmint-bridge/releases (Chrome: load the
chrome zip unpacked; Safari is not available yet — it will ship inside the
ContextMint app, which has no public download — so use Chrome for now). It is the fetchproxy extension under its new name,
same maintainer, with public source — build it yourself or verify a release zip
against its .sha256 (shasum -a 256 -c <zip>.sha256). The extension and fpx
are compatible by fetchproxy protocol number (currently protocol 4), not by
matching package versions. Keep the bridge and fpx current — a mismatch fails
with an error naming both protocol numbers.
Use
source references/outlook-env.sh
outlook_token_refresh # once per ~25h; needs an open, signed-in Outlook tab
outlook_get '/me' | jq .
outlook_get <path> [curl args]— GET, body on stdoutoutlook_get_text <path>— same plusPrefer: outlook.body-content-type="text"; use this for anything you will read (measured 9.4× smaller than HTML)outlook_post <path> <json>/outlook_patch <path> <json>outlook_token_expiry— prints time remaining, never the token
Paths are relative to /api/v2.0. Exit code 4 means non-2xx; a 401
prints run: outlook_token_refresh.
Common starting points — full verified set, including calendar, contacts, tasks
and write payloads, in references/api-recipes.md:
outlook_get '/me/mailfolders/inbox?$select=UnreadItemCount,TotalItemCount'
outlook_get '/me/mailfolders/inbox/messages?$top=20&$orderby=ReceivedDateTime%20desc&$select=Id,Subject,From,ReceivedDateTime,IsRead'
outlook_get '/me/calendarview?startDateTime=2026-09-20T00:00:00Z&endDateTime=2026-09-27T00:00:00Z&$select=Subject,Start,End,Location'
Rules that bite
- Fields are PascalCase (
ReceivedDateTime,IsRead). This is the Outlook REST API, not Graph — a camelCase Graph snippet silently returns nothing. - Always pass
$select. The default projection is ~40 fields per message including the full body. $searchand$filtercannot be combined./me/eventsdoes not expand recurring series — use/me/calendarviewwith a start/end window for "what's on my schedule".Prefer: outlook.timezonetakes Windows zone names (Eastern Standard Time), not IANA (America/New_York).@odata.nextLinkis an absolute URL — pass it tocurl -K ~/.outlook-fpx/curlrcdirectly, not tooutlook_get, which would prefix the base again./owa/0/service.svcis not this API and returns 500. Use/api/v2.0.- A 2xx is not proof a write persisted — re-read the resource to confirm.
- Never name a shell local
pathorstatus: in zshpathis tied toPATH(assigning it wipesPATHmid-function) andstatusis read-only. Both are harmless in bash, so this only breaks for zsh users.
Privacy
This reads the user's real mailbox. Surface only what was asked for; do not dump
message bodies, recipients or attachments into the transcript incidentally. The
token lives in ~/.outlook-fpx/curlrc (0600, its own directory — deliberately
not shared with any MCP's state) and is passed to curl via -K so it never
appears in argv or ps output.
