Skip to main content
Glama
wa1939

outlook-classic-mcp-hardened

by wa1939

outlook-classic-mcp-hardened

License: MIT Platform Python MCP

A local MCP server that gives AI agents full, safe control of the classic Outlook desktop client on Windows — mail, calendar, contacts, tasks, categories, and rules — through the COM API, with no Microsoft Graph, no OAuth, no Azure/Entra app registration, and no cloud email API.

Built for the environment most agent integrations forget: locked-down corporate, government, and university Microsoft 365 tenants where Graph access is disabled, app registration is forbidden, and OAuth integrations are blocked by policy. If Outlook is installed on the machine, this works — because it talks to Outlook exactly the way Office add-ins and macros do.

Why this exists

Most "give my agent email access" projects are Graph API wrappers. That fails in restricted environments:

Usual approach

Restricted tenant reality

Microsoft Graph API

Disabled or unlicensed for apps; requires admin consent

OAuth / Azure app registration

Blocked by tenant policy; security review takes months

IMAP/SMTP

Frequently disabled tenant-wide

Outlook COM (this project)

Works. It is the same surface macros and add-ins use, on your own machine, under your own account

Authentication piggybacks on whatever account the Outlook desktop client is already signed into. Nothing leaves the machine except the emails your agent drafts and you approve.

The hardening: draft-first by design

An agent with an unsupervised Send() is a risk no security team should accept. This fork makes the safe path the default path:

  1. Draft-first defaultoutlook_send_mail, outlook_reply_mail, and outlook_forward_mail save to Drafts by default. Nothing is ever sent implicitly.

  2. Double gate for real sending — an actual send requires both the per-call confirm_send=True parameter and OUTLOOK_MCP_ALLOW_SEND=1 in the server environment. Either alone is not enough.

  3. Recipient-domain allowlist — set OUTLOOK_MCP_ALLOWED_DOMAINS=yourcompany.com,partner.com and outbound sends to any other domain are blocked at the COM layer. Draft creation still works everywhere.

  4. Safe rules only — the rule tools create triage rules (move by subject/sender) and deliberately cannot create auto-forward rules, a classic exfiltration path.

  5. No network surface — the server is stdio-only. It speaks JSON-RPC to the agent on stdin/stdout and COM to Outlook. It has no ports, no HTTP endpoint, no telemetry.

The intended workflow:

agent creates draft  →  you review recipients/subject/body  →  agent sends after your approval
outlook_create_draft →  outlook_get_mail (review)           →  outlook_send_draft(confirm_send=True)

Requirements

  • Windows 10 or 11

  • Outlook desktop (Classic) — the OUTLOOK.EXE from Microsoft 365 / Office. The "new Outlook" (olk.exe) does not work (no COM surface).

  • Python 3.10+

You do not need Outlook to be open — the server launches it on the first COM call.

Install

git clone https://github.com/wa1939/outlook-classic-mcp-hardened.git
cd outlook-classic-mcp-hardened
python -m venv .venv
.venv\Scripts\python -m pip install -U pip
.venv\Scripts\pip install -e .

Or install straight from GitHub:

pip install git+https://github.com/wa1939/outlook-classic-mcp-hardened.git

Smoke-test it (Ctrl+C to stop):

.venv\Scripts\python -m outlook_mcp

Windows + COM note: some launchers pass a minimal environment to MCP subprocesses, which can break Python's socket/asyncio modules (WinError 10106). If the server fails to connect from your agent, pass the standard Windows variables (SystemRoot, COMSPEC, USERPROFILE, APPDATA, LOCALAPPDATA, TEMP, TMP) in the server's env block — the configs below already include them where needed.

Connect your agent

Use the absolute path to the .venv\Scripts\python.exe you just created. Replace C:\path\to\outlook-classic-mcp-hardened everywhere below.

Hermes Agent

One command (accepts the registration prompt automatically):

printf 'Y\n' | hermes mcp add outlook \
  --command C:/path/to/outlook-classic-mcp-hardened/.venv/Scripts/python.exe \
  --env OUTLOOK_MCP_ALLOW_SEND=0 \
    SystemRoot=C:/Windows SYSTEMROOT=C:/Windows \
    windir=C:/Windows WINDIR=C:/Windows \
    COMSPEC=C:/Windows/system32/cmd.exe \
    USERPROFILE=C:/Users/YOURNAME \
    APPDATA=C:/Users/YOURNAME/AppData/Roaming \
    LOCALAPPDATA=C:/Users/YOURNAME/AppData/Local \
    TEMP=C:/Users/YOURNAME/AppData/Local/Temp \
    TMP=C:/Users/YOURNAME/AppData/Local/Temp \
  --args -m outlook_mcp

Verify and restart:

hermes mcp list
hermes mcp test outlook
# then start a new Hermes session (or /reload-mcp) — tools appear as mcp_outlook_*

The Windows environment variables are not optional decoration: without them, Python COM subprocesses can fail to start under Hermes' filtered environment.

Claude Code

claude mcp add outlook \
  -e OUTLOOK_MCP_ALLOW_SEND=0 \
  -- C:/path/to/outlook-classic-mcp-hardened/.venv/Scripts/python.exe -m outlook_mcp

Add -s user to register it for all projects instead of the current one. If the server fails to connect, append the Windows env vars the same way (-e SystemRoot=C:/Windows -e COMSPEC=...).

Codex CLI

codex mcp add outlook -- \
  C:/path/to/outlook-classic-mcp-hardened/.venv/Scripts/python.exe -m outlook_mcp

Add environment variables with repeated --env KEY=VALUE flags if needed.

Claude Desktop / Cursor / Windsurf / any mcpServers config

{
  "mcpServers": {
    "outlook": {
      "command": "C:/path/to/outlook-classic-mcp-hardened/.venv/Scripts/python.exe",
      "args": ["-m", "outlook_mcp"],
      "env": {
        "OUTLOOK_MCP_ALLOW_SEND": "0",
        "OUTLOOK_MCP_ALLOWED_DOMAINS": "yourcompany.com",
        "SystemRoot": "C:/Windows",
        "SYSTEMROOT": "C:/Windows",
        "windir": "C:/Windows",
        "WINDIR": "C:/Windows",
        "COMSPEC": "C:/Windows/system32/cmd.exe",
        "USERPROFILE": "C:/Users/YOURNAME",
        "APPDATA": "C:/Users/YOURNAME/AppData/Roaming",
        "LOCALAPPDATA": "C:/Users/YOURNAME/AppData/Local",
        "TEMP": "C:/Users/YOURNAME/AppData/Local/Temp",
        "TMP": "C:/Users/YOURNAME/AppData/Local/Temp"
      }
    }
  }
}

A ready-to-edit copy lives at examples/mcp-config-example.json.

Security configuration

Variable

Default

Effect

OUTLOOK_MCP_ALLOW_SEND

0 (off)

Master send gate. Must be 1 for any real send, and the tool call must also pass confirm_send=true.

OUTLOOK_MCP_ALLOWED_DOMAINS (or OUTLOOK_MCP_ALLOWED_RECIPIENT_DOMAINS)

unset

Comma-separated allowlist of recipient domains. Sends outside the list are blocked; drafts are unaffected.

OUTLOOK_MCP_ALLOW_UNRESOLVED_RECIPIENTS

0 (off)

Set to 1 to allow sending to recipients Outlook cannot resolve to a real SMTP address. Leave it off.

OUTLOOK_MCP_LOG

INFO

Log level (logs go to stderr only — never stdout, which carries the MCP stream).

Check the live policy any time with the outlook_get_send_policy tool.

Tools (38)

Area

Tools

Mail

outlook_list_mails, outlook_get_mail, outlook_search_mails, outlook_create_draft, outlook_send_draft, outlook_send_mail, outlook_reply_mail, outlook_forward_mail, outlook_mark_mail, outlook_move_mail, outlook_delete_mail, outlook_save_attachments, outlook_get_send_policy

Folders

outlook_list_folders, outlook_create_folder

Calendar

outlook_list_events, outlook_get_event, outlook_create_event, outlook_update_event, outlook_delete_event, outlook_respond_event

Contacts

outlook_list_contacts, outlook_get_contact, outlook_search_contacts, outlook_resolve_name

Tasks

outlook_list_tasks, outlook_create_task, outlook_update_task, outlook_complete_task, outlook_delete_task

Categories

outlook_list_categories, outlook_set_category

Rules

outlook_list_rules, outlook_create_move_rule, outlook_toggle_rule, outlook_delete_rule

Account

outlook_whoami, outlook_get_out_of_office

Destructive tools (outlook_delete_task, outlook_delete_rule) require an explicit confirm_delete=true on every call.

Put this in your agent's instructions:

Use Outlook read/search tools freely. For outbound email, always create a draft first, show me the exact recipients, subject, and body, and only call outlook_send_draft(..., confirm_send=true) after I approve.

Documentation

Credits & license

Based on the MIT-licensed outlook-classic-mcp by Anas Shaikh. This fork hardens the outbound-mail flow (draft-first defaults, send gating, domain allowlist) and extends tasks and rules; see docs/HARDENING.md.

Released under the MIT License with attribution to both authors. Microsoft Outlook is a trademark of Microsoft Corporation; this project is not affiliated with or endorsed by Microsoft.