{"slug": "how-to-sync-outlook-gmail-calendars-using-ai-mcp-amazon-quick", "title": "How to sync Outlook ↔ Gmail calendars using AI + MCP (Amazon Quick)", "summary": "This article provides a step-by-step guide for syncing Outlook and Gmail calendars using Amazon Quick, an AI assistant, and a custom MCP (Model Context Protocol) server. The system uses a Python script running on a Mac to bridge Amazon Quick with the Google Calendar API via OAuth 2.0, enabling automated two-way sync with smart rules that filter which events are shared. The setup is free to use, leveraging Google Calendar's generous free tier, and requires creating a Google Cloud project, enabling the Calendar API, and configuring OAuth credentials.", "body_md": "# How I Synced My Work Calendar (Outlook) with My Personal Calendar (Gmail) Using AI + MCP\n\n## The Problem\n\nTwo calendars. Work life in Outlook, personal life in Gmail. Neither knows the other exists.\n\n**Real scenarios that kept happening:**\n- I'd block \"Business Travel\" on my work calendar (3 days in NYC) but my husband had no idea I'd be gone until I told him\n- I'd schedule a doctor appointment for my kiddo on Gmail, then forget to block my work calendar. colleagues would book meetings over it\n- Weekend-only family trips didn't need a work block, but a Friday-through-Sunday trip did. I had to think about this manually every time\n\n**What I wanted:**\n1. When I add work travel to Outlook → automatically show it on our family Gmail calendar (and notify my husband)\n2. When I add a doctor appointment on Gmail → automatically block my work calendar as Out of Office\n3. Smart rules: don't sync routine daily blocks, skip weekend-only trips, only sync what matters\n\n## What I Built\n\nA daily sync agent that runs at 9 AM and keeps both calendars in sync, using:\n- **Amazon Quick** (AI assistant with calendar access)\n- **A lightweight MCP server** (Python script on my Mac that bridges to Google Calendar via OAuth)\n- **A scheduled agent** with custom sync rules\n\n## Architecture\n\n```\nOutlook (Work Calendar)\n    ↕  Amazon Quick reads/writes via built-in Outlook connector\nAmazon Quick (AI Agent)\n    ↕  Calls MCP tools over stdio\nGoogle Calendar MCP Server (Python on my Mac)\n    ↕  Google Calendar REST API (OAuth 2.0)\nGmail Calendar (Personal)\n```\n\n## Prerequisites\n\n- Amazon Quick with Outlook calendar connected\n- A Google account with Google Calendar\n- Python 3.10+ on your machine\n- `pip install google-auth google-auth-oauthlib google-api-python-client`\n\n## Step 1: Create a Google Cloud Project + OAuth Credentials\n\n1. Go to [console.cloud.google.com](https://console.cloud.google.com/)\n2. Create a new project (e.g., \"Calendar Sync\")\n3. Enable the **Google Calendar API** (APIs & Services → Library → search \"Google Calendar API\" → Enable)\n4. Configure the OAuth consent screen:\n   - Select **External**\n   - App name: \"Calendar Sync\"\n   - Add your email as a test user\n5. Create OAuth credentials:\n   - APIs & Services → Credentials → Create Credentials → OAuth client ID\n   - Application type: **Desktop app**\n   - Download the JSON file\n6. Save it to `~/.config/caldav/client_secret.json`:\n\n```bash\nmkdir -p ~/.config/caldav\nmv ~/Downloads/client_secret_*.json ~/.config/caldav/client_secret.json\n```\n\n**Cost:** Free. Google Calendar API has a massive free tier (1M queries/day). Personal sync uses ~10 calls/day.\n\n## Step 2: Set Up the MCP Server\n\nSave the following Python script to `~/mcp-servers/gcal_mcp_server.py`:\n\n```python\n#!/usr/bin/env python3\n\"\"\"\nGoogle Calendar MCP Server (OAuth 2.0)\nBridges AI assistants to Gmail Calendar via Google Calendar REST API.\n\"\"\"\n\nimport json\nimport sys\nfrom datetime import datetime, timedelta\nfrom pathlib import Path\n\nfrom google.auth.transport.requests import Request\nfrom google.oauth2.credentials import Credentials\nfrom google_auth_oauthlib.flow import InstalledAppFlow\nfrom googleapiclient.discovery import build\n\nCONFIG_DIR = Path.home() / \".config\" / \"caldav\"\nCLIENT_SECRET_PATH = CONFIG_DIR / \"client_secret.json\"\nTOKEN_PATH = CONFIG_DIR / \"token.json\"\nSCOPES = [\"https://www.googleapis.com/auth/calendar\"]\n\n\ndef get_calendar_service():\n    creds = None\n    if TOKEN_PATH.exists():\n        creds = Credentials.from_authorized_user_file(str(TOKEN_PATH), SCOPES)\n    if not creds or not creds.valid:\n        if creds and creds.expired and creds.refresh_token:\n            creds.refresh(Request())\n        else:\n            flow = InstalledAppFlow.from_client_secrets_file(str(CLIENT_SECRET_PATH), SCOPES)\n            creds = flow.run_local_server(port=0)\n        with open(TOKEN_PATH, \"w\") as f:\n            f.write(creds.to_json())\n    return build(\"calendar\", \"v3\", credentials=creds)\n\n\ndef list_events(start_date, end_date):\n    service = get_calendar_service()\n    events = service.events().list(\n        calendarId=\"primary\",\n        timeMin=f\"{start_date}T00:00:00Z\",\n        timeMax=f\"{end_date}T23:59:59Z\",\n        singleEvents=True,\n        orderBy=\"startTime\",\n    ).execute().get(\"items\", [])\n    return [{\"summary\": e.get(\"summary\", \"\"), \"start\": e[\"start\"].get(\"dateTime\", e[\"start\"].get(\"date\")),\n             \"end\": e[\"end\"].get(\"dateTime\", e[\"end\"].get(\"date\")), \"id\": e.get(\"id\"),\n             \"all_day\": \"date\" in e[\"start\"]} for e in events]\n\n\ndef create_event(summary, start_date, end_date, description=\"\", all_day=True, attendees=None):\n    service = get_calendar_service()\n    body = {\"summary\": summary, \"description\": description, \"status\": \"confirmed\"}\n    if all_day:\n        body[\"start\"] = {\"date\": start_date}\n        body[\"end\"] = {\"date\": end_date}\n    else:\n        body[\"start\"] = {\"dateTime\": f\"{start_date}T09:00:00\", \"timeZone\": \"America/Los_Angeles\"}\n        body[\"end\"] = {\"dateTime\": f\"{end_date}T17:00:00\", \"timeZone\": \"America/Los_Angeles\"}\n    if attendees:\n        body[\"attendees\"] = [{\"email\": email} for email in attendees]\n    event = service.events().insert(calendarId=\"primary\", body=body).execute()\n    return {\"success\": True, \"id\": event.get(\"id\"), \"link\": event.get(\"htmlLink\")}\n\n\ndef delete_event(event_id):\n    service = get_calendar_service()\n    service.events().delete(calendarId=\"primary\", eventId=event_id).execute()\n    return {\"success\": True}\n\n\n# --- MCP JSON-RPC Server (reads from stdin, writes to stdout) ---\n\nTOOLS = {\n    \"caldav_list_events\": {\"description\": \"List Gmail calendar events in a date range.\",\n        \"parameters\": {\"type\": \"object\", \"properties\": {\n            \"start_date\": {\"type\": \"string\"}, \"end_date\": {\"type\": \"string\"}}, \"required\": [\"start_date\", \"end_date\"]}},\n    \"caldav_create_event\": {\"description\": \"Create event on Gmail calendar. end_date is EXCLUSIVE for all-day events.\",\n        \"parameters\": {\"type\": \"object\", \"properties\": {\n            \"summary\": {\"type\": \"string\"}, \"start_date\": {\"type\": \"string\"}, \"end_date\": {\"type\": \"string\"},\n            \"description\": {\"type\": \"string\", \"default\": \"\"}, \"all_day\": {\"type\": \"boolean\", \"default\": True},\n            \"attendees\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}, \"default\": None}},\n            \"required\": [\"summary\", \"start_date\", \"end_date\"]}},\n    \"caldav_delete_event\": {\"description\": \"Delete event from Gmail calendar by ID.\",\n        \"parameters\": {\"type\": \"object\", \"properties\": {\"event_id\": {\"type\": \"string\"}}, \"required\": [\"event_id\"]}},\n}\n\n\ndef handle_request(request):\n    method, params, req_id = request.get(\"method\"), request.get(\"params\", {}), request.get(\"id\")\n    if method == \"initialize\":\n        return {\"jsonrpc\": \"2.0\", \"id\": req_id, \"result\": {\"protocolVersion\": \"2024-11-05\",\n            \"capabilities\": {\"tools\": {}}, \"serverInfo\": {\"name\": \"gcal-mcp\", \"version\": \"2.0.0\"}}}\n    elif method == \"notifications/initialized\":\n        return None\n    elif method == \"tools/list\":\n        return {\"jsonrpc\": \"2.0\", \"id\": req_id, \"result\": {\"tools\": [\n            {\"name\": n, \"description\": s[\"description\"], \"inputSchema\": s[\"parameters\"]} for n, s in TOOLS.items()]}}\n    elif method == \"tools/call\":\n        name, args = params.get(\"name\"), params.get(\"arguments\", {})\n        try:\n            result = {\"caldav_list_events\": list_events, \"caldav_create_event\": create_event,\n                      \"caldav_delete_event\": delete_event}[name](**args)\n            return {\"jsonrpc\": \"2.0\", \"id\": req_id, \"result\": {\"content\": [{\"type\": \"text\", \"text\": json.dumps(result, default=str)}]}}\n        except Exception as e:\n            return {\"jsonrpc\": \"2.0\", \"id\": req_id, \"result\": {\"content\": [{\"type\": \"text\", \"text\": json.dumps({\"error\": str(e)})}], \"isError\": True}}\n    return {\"jsonrpc\": \"2.0\", \"id\": req_id, \"error\": {\"code\": -32601, \"message\": f\"Unknown: {method}\"}}\n\n\nif __name__ == \"__main__\":\n    for line in sys.stdin:\n        if not line.strip():\n            continue\n        try:\n            resp = handle_request(json.loads(line))\n            if resp:\n                sys.stdout.write(json.dumps(resp) + \"\\n\")\n                sys.stdout.flush()\n        except json.JSONDecodeError:\n            continue\n```\n\n## Step 3: Authorize (One Time)\n\nRun this once to complete the OAuth flow:\n\n```bash\npython3 -c \"\nfrom google_auth_oauthlib.flow import InstalledAppFlow\nfrom pathlib import Path\n\nflow = InstalledAppFlow.from_client_secrets_file(\n    str(Path.home() / '.config/caldav/client_secret.json'),\n    ['https://www.googleapis.com/auth/calendar']\n)\ncreds = flow.run_local_server(port=8080)\n\ntoken_path = Path.home() / '.config/caldav/token.json'\nwith open(token_path, 'w') as f:\n    f.write(creds.to_json())\nprint('Token saved!')\n\"\n```\n\nA browser window will open. Sign in, click \"Advanced\" → \"Go to Calendar Sync (unsafe)\" → \"Continue\". Token is saved. You won't need to do this again.\n\n## Step 4: Register in Amazon Quick\n\nSettings → Capabilities → MCP Servers → Add:\n- **Name:** `calendar_integ` (or any name)\n- **Command:** `python3`\n- **Args:** `/Users/yourname/mcp-servers/gcal_mcp_server.py`\n\nToggle on. Should show \"Connected.\"\n\n## Step 5: Create the Scheduled Sync Agent\n\nIn Amazon Quick, create a scheduled agent with:\n- **Schedule:** Daily at 9:00 AM\n- **Tool access:** Outlook (read + create/delete with approval), Gmail MCP (full read/write)\n\n### My Sync Rules\n\n**Outlook → Gmail (work travel → family calendar):**\n| Trigger | Action |\n|---------|--------|\n| Title contains \"Aarthi: Business Travel\" | Sync + invite husband |\n| Title contains \"Aarthi: Team event\" | Sync + invite husband |\n| Category = \"Sync to Gmail\" | Sync + invite husband |\n| Event spans 2+ days | Sync + invite husband |\n\n**Gmail → Outlook (personal → work calendar):**\n| Trigger | Action |\n|---------|--------|\n| Title contains \"Kiddo doc\" or \"Kiddo doc appt\" | Block as OOF |\n| Event spans 2+ days (with a weekday) | Block as OOF |\n\n**Never sync:**\n- Recurring daily blocks (drop-off, pickup, commute). these live natively on both calendars\n- Weekend-only events (Sat-Sun trips)\n- Other people's OOO notifications\n- Workouts, birthdays, routine items\n\n**Deletion sync:** If a source event is deleted, the synced copy is removed too.\n\n## Gotchas & Lessons Learned\n\n1. **Google CalDAV requires OAuth 2.0.** App passwords don't work for CalDAV/Calendar API. Don't waste time trying.\n2. **Timezone matters.** Always include `-07:00` (or your offset) when creating timed events on Outlook. Bare datetimes get interpreted as UTC.\n3. **Recurring events don't belong in sync.** If an event repeats daily/weekly, create it natively on both calendars. Syncing individual occurrences creates approval fatigue.\n4. **Deduplication is essential.** Without it, the agent creates duplicate events on every run.\n5. **Outlook's create/delete tools require user approval.** You can't fully automate writes to Outlook. but it's just one tap per event.\n6. **end_date is EXCLUSIVE in Google Calendar.** A trip ending June 20 needs `end_date = 2026-06-21`.\n\n## What It Looks Like in Practice\n\n- I add \"Aarthi: Business Travel — NYC Summit\" to Outlook → my husband gets a Google Calendar invite automatically\n- I add \"Kiddo doc visit\" to Gmail → my Outlook shows OOF for that time\n- I delete the doctor appointment from Gmail → the OOF block disappears from Outlook\n- Weekend family trips don't clutter my work calendar\n- I do nothing except add/remove events where I normally would. the sync just happens.\n\n## License\n\nMIT. do whatever you want with this.\n", "url": "https://wpnews.pro/news/how-to-sync-outlook-gmail-calendars-using-ai-mcp-amazon-quick", "canonical_source": "https://gist.github.com/aartraju/cedca245d76894ebe44ba2322ab15682", "published_at": "2026-05-23 05:15:07+00:00", "updated_at": "2026-05-23 15:06:20.381302+00:00", "lang": "en", "topics": ["artificial-intelligence", "developer-tools", "products", "enterprise-software"], "entities": ["Outlook", "Gmail", "Amazon Quick", "MCP", "Google Calendar", "Python", "OAuth"], "alternates": {"html": "https://wpnews.pro/news/how-to-sync-outlook-gmail-calendars-using-ai-mcp-amazon-quick", "markdown": "https://wpnews.pro/news/how-to-sync-outlook-gmail-calendars-using-ai-mcp-amazon-quick.md", "text": "https://wpnews.pro/news/how-to-sync-outlook-gmail-calendars-using-ai-mcp-amazon-quick.txt", "jsonld": "https://wpnews.pro/news/how-to-sync-outlook-gmail-calendars-using-ai-mcp-amazon-quick.jsonld"}}