wteja

exchange-ai-connector

Community wteja
Updated

MCP server for human-gated Microsoft Graph email and calendar

exchange-ai-connector

An MCP server that lets an AI agent read and act on your Microsoft 365 / Outlookemail and calendar, with every state-changing action (sending mail, creatingan event) gated by your MCP client's confirmation prompt.

On PyPI: uvx exchange-ai-connectoror pipx install exchange-ai-connector. See Setup.

Tools

Tool Kind What it does
list_emails read-only List messages in a folder (default inbox)
read_email read-only Read one message in full
read_thread read-only Read a whole conversation, oldest → newest
send_email gated Send or reply — client confirms first
list_events read-only List upcoming calendar events
read_event read-only Read one event in full
check_availability read-only Free/busy for you (+ others) — work/school only
create_event gated Create an event, optionally inviting attendees

How the human-in-the-loop gate works

The read-only tools run freely. The two gated tools (send_email,create_event) are the only ones that change the outside world; they areannotated as destructive, so your MCP client (e.g. Claude Desktop) shows you theexact arguments — recipients, subject, body / event details — and waits for yourapproval before running. The draft you review is the agent's proposedarguments; nothing is stored half-sent. Reject and it vanishes.

Account-type support

Account type Email Calendar read/create check_availability
Work / school (Microsoft 365)
Personal (outlook.com / hotmail) ❌¹

¹ Graph's getSchedule (free/busy) is not available on personal Microsoftaccounts. check_availability returns a readable error there; every other toolworks.

Setup

1. Register an app in Microsoft Entra ID

  1. Go to https://entra.microsoft.comIdentity → Applications → AppregistrationsNew registration.
  2. Name: anything (e.g. exchange-ai-connector).
  3. Supported account types: Accounts in any organizational directory(multitenant) and personal Microsoft accounts.
  4. Redirect URI: platform Public client/native (mobile & desktop), valuehttp://localhost:8400.
  5. Click Register, then copy the Application (client) ID from theoverview page — you'll need it below.

2. Add Microsoft Graph permissions

  1. In your app → API permissionsAdd a permissionMicrosoftGraphDelegated permissions.
  2. Add: Mail.Read, Mail.Send, Calendars.ReadWrite.
  3. Personal account: nothing more — you consent in the browser on first run.Work/school account: a tenant admin may need to click Grant adminconsent.

Adding Calendars.ReadWrite later (e.g. after using email-only) triggers aone-time browser re-consent on the next run. See Re-consent below.

3. Install

Pick one:

uvx (recommended — no clone, no venv). Requires uv.Nothing to install ahead of time — uvx fetches and runs the command in athrowaway environment. It's used directly in the Claude Desktop config below, soyou can skip straight to that section.

pipx — puts the exchange-ai-connector command on your PATH globally:

pipx install exchange-ai-connector
# or an unreleased version straight from source:
pipx install git+https://github.com/wteja/exchange-ai-connector

From source (for development):

git clone https://github.com/wteja/exchange-ai-connector
cd exchange-ai-connector
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
which exchange-ai-connector   # note this path for the Claude Desktop config

4. Configure environment

export EXCHANGE_AI_CLIENT_ID="<your-app-client-id>"

# optional — pin to one tenant instead of the multi-tenant default:
# export EXCHANGE_AI_AUTHORITY="https://login.microsoftonline.com/<tenant-id>"

# optional — override the auto-detected timezone (default: /etc/localtime, else UTC):
# export EXCHANGE_AI_TIMEZONE="Asia/Bangkok"

EXCHANGE_AI_CLIENT_ID is required; the other two are optional.

Claude Desktop setup

Edit (on macOS) ~/Library/Application Support/Claude/claude_desktop_config.jsonand add an exchange-ai server under mcpServers. The uvx form needs no cloneor venv — it fetches exchange-ai-connector from PyPI and runs it:

{
  "mcpServers": {
    "exchange-ai": {
      "command": "/opt/homebrew/bin/uvx",
      "args": ["exchange-ai-connector"],
      "env": {
        "EXCHANGE_AI_CLIENT_ID": "<your-app-client-id>",
        "EXCHANGE_AI_TIMEZONE": "Asia/Bangkok"
      }
    }
  }
}

Use the absolute path to uvx (which uvx — e.g./opt/homebrew/bin/uvx on Apple-Silicon Homebrew). Claude Desktop is a GUI appand does not inherit your shell's PATH, so a bare "uvx" won't be found.

Useful variants for the args:

  • Pin a version (reproducible; uses uv's cache without re-resolving — handy ifyour network can't always reach PyPI): ["[email protected]"]
  • Run an unreleased version from GitHub:["--from", "git+https://github.com/wteja/exchange-ai-connector", "exchange-ai-connector"]

If you installed from source into a venv instead, point command at thebinary's absolute path (same PATH reason as above):

"command": "/ABSOLUTE/PATH/TO/.venv/bin/exchange-ai-connector"

Claude Code (.mcp.json)

For Claude Code, put the same server under mcpServers in a .mcp.json at yourproject root (Claude Code expands ${VAR} from your environment):

{
  "mcpServers": {
    "exchange-ai": {
      "command": "uvx",
      "args": ["exchange-ai-connector"],
      "env": {
        "EXCHANGE_AI_CLIENT_ID": "${EXCHANGE_AI_CLIENT_ID}",
        "EXCHANGE_AI_TIMEZONE": "Asia/Bangkok"
      }
    }
  }
}

EXCHANGE_AI_TIMEZONE is optional — omit it to auto-detect from the system(/etc/localtime, falling back to UTC). command can be a bare uvx herebecause Claude Code runs from your shell and inherits its PATH (unlike theClaude Desktop GUI, which needs the absolute path).

Then fully quit Claude Desktop (Cmd+Q) and reopen it. The exchange-aiserver and its tools should appear in the tools/connector list.

On the first tool call a browser opens for sign-in and consent; the token iscached in your OS keychain and refreshed silently afterward.

Suggested approvals: allow the read-only tools (list_emails, read_email,read_thread, list_events, read_event, check_availability) to run withoutasking, but leave send_email and create_event on ask every time — that'sthe human-in-the-loop gate doing its job.

Run standalone (without a client)

exchange-ai-connector

It's a stdio MCP server, so it waits silently for a client to connect — there'sno interactive output. This is mainly useful for confirming it starts.

Example usage (sample prompts)

Once it's wired into Claude Desktop, drive it in plain language. Examples:

Reading email

  • "List my latest 10 emails."
  • "Show me unread emails from this week."
  • "Read the full email from Alice about the invoice."
  • "Show me the whole thread for that conversation."

Sending email (gated — you'll approve the draft)

  • "Reply to Alice's email saying I'll have the report by Friday."
  • "Send an email to [email protected], subject 'Lunch?', asking if he's free Thursday."
  • "Forward the invoice email to [email protected] with a short note."

Reading the calendar

  • "What's on my calendar this week?"
  • "Read the details of my 2pm meeting tomorrow."
  • "Am I free tomorrow 2–3pm?" (work/school accounts only)
  • "When are [email protected] and I both free Thursday afternoon?" (work/school only)

Creating events (gated — you'll approve the details)

  • "Create a 30-minute event tomorrow at 2pm titled 'Project sync'."
  • "Schedule a 1-hour meeting Friday 10am called 'Design review', invite [email protected] and [email protected]."
  • "Block 9–11am Monday for focus time."

For the gated actions, Claude Desktop shows the exact send_email(...) /create_event(...) arguments and waits for your Approve / Deny. Want achange? Tell the agent ("make it 45 minutes", "cc my manager") and it re-proposes.

Audit log

Every gated action appends one JSON line to~/.exchange-ai-connector/audit.log:

  • Sends: {ts, to, subject[, reply_to_id]}
  • Events: {ts, kind:"event", subject, start[, attendees]}

Append-only JSONL, grep-able:

grep '"kind": "event"' ~/.exchange-ai-connector/audit.log

Re-consent / token cache

The OAuth token is cached in your OS keychain (serviceexchange-ai-connector, account msal-token-cache) and refreshed silently. Thecached token only carries the scopes you consented to. If you add a scope(e.g. enabling calendar after email-only), clear the cache so the next runre-prompts the browser with the new scopes:

python -c "import keyring; keyring.delete_password('exchange-ai-connector','msal-token-cache')"

("Not found" just means there was no cache to clear.) Then restart your client.

Scope

  • v1: email — list/read/thread + gated send.
  • v2 (this release): calendar — list/read events, free/busy availability,and gated create_event.

A standalone web approval UI, app-only auth, and multi-account approval remaindeliberately out of scope; see the design specs under docs/superpowers/specs/.

MCP Server · Populars

MCP Server · New

    abskrj

    velane

    Code Runtime and iPaaS for AI Agent — execute Bun/Python snippets at scale via POST API + integrate with 800+ tools (N8N for AI Agents)

    Community abskrj
    jean-technologies

    Jean Memory

    next-generation AI memory infrastructure (powered by mem0 and graphiti)

    Community jean-technologies
    PascaleBeier

    HitKeep

    HitKeep is privacy-first analytics for humans and AI agents, self-hosted or in managed EU/US cloud regions.

    Community PascaleBeier
    prometheus

    Prometheus MCP Server

    MCP server for LLMs to interact with Prometheus

    Community prometheus
    TencentEdgeOne

    EdgeOne Makers MCP

    An MCP service designed for deploying HTML content to EdgeOne Pages and obtaining an accessible public URL.

    Community TencentEdgeOne