{"slug": "claude-code-to-outlook-via-pywin32-no-mcp-no-permission-no-problem", "title": "Claude Code to Outlook via pywin32 — no MCP, no permission, no problem.", "summary": "A developer working on a locked-down enterprise Windows machine built a Python bridge using pywin32 to let Claude Code read and draft Outlook emails without MCP servers or Azure app registration. The single-file tool exposes Outlook COM commands as CLI subcommands returning JSON, enabling Claude to chain operations like reading threads and drafting replies directly from the conversation. The approach avoids COM object lifetime issues, API rate limits, and protocol overhead compared to MCP servers.", "body_md": "I work on a locked-down enterprise Windows machine. Can't register an Azure app. Can't install browser extensions. Managed device — IT controls what's approved.\n\nI still needed Claude Code to read and draft my emails.\n\nThe fix turned out to be simpler than I expected.\n\nOutlook Desktop on Windows exposes a COM interface. It's been there since the 90s. Any program running on the same machine can connect to it, provided Outlook is open and signed in.\n\nThat's the whole authentication model: if Outlook is running, you're in.\n\n`pywin32`\n\nwraps this interface in Python:\n\n``` python\nimport win32com.client\noutlook = win32com.client.Dispatch(\"Outlook.Application\")\ninbox = outlook.GetNamespace(\"MAPI\").GetDefaultFolder(6)\n```\n\nOne import. One dispatch call. You're talking to Outlook and you have access to almost anything you can do with a mouse and keyboard.\n\nClaude wrote a single Python file — `outlook_bridge.py`\n\n— that exposes Outlook commands as CLI subcommands and returns JSON.\n\nClaude Code calls it via Bash:\n\n```\npython outlook_bridge.py list --count 10 --pretty\npython outlook_bridge.py search \"renewal\" --folder inbox\npython outlook_bridge.py reply <entry_id> --body \"Noted, will revert.\" --draft\n```\n\nOutput:\n\n```\n[\n  {\n    \"Subject\": \"Q2 renewal — action required\",\n    \"SenderName\": \"Alice Tan\",\n    \"ReceivedTime\": \"2026-06-12T09:14:00\",\n    \"Unread\": true,\n    \"BodyPreview\": \"Following up on the renewal terms we discussed...\"\n  }\n]\n```\n\nClaude processes the JSON, reasons over it, and calls the next command in the chain. Read a thread for context → draft a reply → hand it back for review. All without leaving the Claude Code conversation.\n\nI considered it. MCP is worth it when you need a long-running server process and cross-client compatibility.\n\nFor this use case, it wasn't worth it — for three reasons.\n\n**COM object lifetime.** A long-running MCP server has to maintain the COM connection across calls, handle Outlook restarts, and manage session state. A direct Bash call has none of that: Claude shells out, Python grabs the running Outlook process, JSON comes back, process exits cleanly.\n\n**Rate limits.** MCP servers that wrap an API (like Microsoft Graph) inherit that API's throttling. Broad-spectrum queries — pulling a large folder, searching across multiple mailboxes — hit those limits fast. The bridge talks directly to the local COM interface. No API in the middle, no rate limit.\n\n**Token efficiency.** The bridge returns exactly what I define: a JSON object with the fields I want. MCP tool responses carry protocol overhead on top of the payload. For high-frequency operations inside a single conversation, that adds up.\n\nOne Python file. No daemon. No protocol overhead.\n\nIf the constraints above don't apply to your setup, an MCP server is a reasonable alternative. For a local single-user workflow on Windows, this is the shorter path.\n\nGraph works cross-platform and headless. The trade-off: Azure app registration, OAuth consent flow, ongoing token refresh. On a managed enterprise device, that registration process may not be available to you.\n\nCOM requires none of it. You're riding the session that's already open on your machine.\n\nOne entry in `.claude/settings.json`\n\n:\n\n```\n{\n  \"permissions\": {\n    \"allow\": [\"Bash(python *outlook_bridge.py*:*)\"]\n  }\n}\n```\n\nClaude can call the bridge and nothing else. No open-ended Bash access required.\n\n`send`\n\n, `reply`\n\n, and `forward`\n\nall work. My default is `--draft`\n\n— Claude saves to Drafts and I review before sending. Accidental sends are still a real risk when you're chaining operations inside a conversation. The CLAUDE_INTEGRATION.md in the repo has a CLAUDE.md snippet that sets draft-by-default as a standing instruction.\n\n`list`\n\n— recent emails in any folder`search`\n\n— by keyword, sender, subject`read`\n\n— full body + attachment list`send`\n\n/ `reply`\n\n/ `reply-all`\n\n/ `forward`\n\n— with `--draft`\n\nsupport`move`\n\n/ `delete`\n\n`cal-list`\n\n— calendar events in a date range, including shared calendarsWorth stating plainly:\n\nIf any of those constraints rule you out, use Microsoft Graph.\n\n[github.com/ChiefStarKid/claude-outlook-bridge](https://github.com/ChiefStarKid/claude-outlook-bridge)\n\nAGENTS.md and llms.txt are in there for anyone who wants to wire this into a coding agent automatically.", "url": "https://wpnews.pro/news/claude-code-to-outlook-via-pywin32-no-mcp-no-permission-no-problem", "canonical_source": "https://dev.to/joseph_solomon_20a1569494/claude-code-to-outlook-via-pywin32-no-mcp-no-permission-no-problem-113b", "published_at": "2026-06-29 15:08:22+00:00", "updated_at": "2026-06-29 15:19:34.639464+00:00", "lang": "en", "topics": ["developer-tools", "artificial-intelligence", "large-language-models", "ai-agents"], "entities": ["Claude Code", "Outlook", "pywin32", "Microsoft Graph", "Azure", "COM", "MCP"], "alternates": {"html": "https://wpnews.pro/news/claude-code-to-outlook-via-pywin32-no-mcp-no-permission-no-problem", "markdown": "https://wpnews.pro/news/claude-code-to-outlook-via-pywin32-no-mcp-no-permission-no-problem.md", "text": "https://wpnews.pro/news/claude-code-to-outlook-via-pywin32-no-mcp-no-permission-no-problem.txt", "jsonld": "https://wpnews.pro/news/claude-code-to-outlook-via-pywin32-no-mcp-no-permission-no-problem.jsonld"}}