---
name: msgraph
description: "Use this skill whenever a task requires direct interaction with Microsoft 365 services via the Microsoft Graph API — including sending emails, uploading or downloading files to/from OneDrive, managing calendar events (create, read, update, delete), or reading the authenticated user's profile. Trigger this skill when the workflow involves file I/O that must pass through Microsoft 365 (e.g. building a spreadsheet and uploading it to OneDrive, or emailing a generated file as an attachment). Authentication is handled transparently by a sidecar proxy — no token management is required. Do NOT use this skill for SharePoint list operations, Teams messaging, or any Graph endpoint not covered by the four scripts below; use the Microsoft Graph sub-agent for those instead."
license: Proprietary
---

# Microsoft Graph Skill

## Overview

This skill provides four ready-to-import Python modules for calling the Microsoft Graph API
from the sandbox. Authentication is handled by a transparent proxy sidecar — simply import
and call; no tokens or credentials are needed.

```
scripts/
  __init__.py      # sets REQUESTS_CA_BUNDLE for proxy SSL verification
  graph.py         # core HTTP client
  send_email.py    # send email via /me/sendMail
  upload_file.py   # upload files to OneDrive (small + chunked)
  calendar.py      # list / get / create / update / delete calendar events
```

---

## Setup — always add this to sys.path first

```python
import sys
sys.path.insert(0, '/home/sandbox/skills/msgraph')
from scripts import *  # sets REQUESTS_CA_BUNDLE
```

---

## scripts/graph.py — Core client

```python
from scripts.graph import graph, get_me, get_all_pages

# Authenticated GET/POST/PATCH/DELETE — returns parsed JSON or None
result = graph("GET", "/me")
result = graph("POST", "/me/events", json={"subject": "Sync"})
result = graph("DELETE", "/me/events/EVENT_ID")  # returns None

# Convenience
me = get_me()                       # GET /me
items = get_all_pages("/me/messages")  # auto-follows @odata.nextLink
```

---

## scripts/send_email.py — Send email

```python
from scripts.send_email import send_email

send_email(
    to="recipient@example.com",
    subject="Subject line",
    body_html="<h1>Hello</h1><p>Body text.</p>",
    cc=["cc@example.com"],           # optional
    bcc=["bcc@example.com"],         # optional
    attachments=[                    # optional
        {
            "name": "report.xlsx",
            "path": "/tmp/report.xlsx",
            "contentType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        }
    ],
    save_to_sent=True,               # default True
)
```

**Attachment content types:**

| Extension | contentType |
|-----------|-------------|
| `.pdf`    | `application/pdf` |
| `.xlsx`   | `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` |
| `.docx`   | `application/vnd.openxmlformats-officedocument.wordprocessingml.document` |
| `.csv`    | `text/csv` |
| `.txt`    | `text/plain` |
| other     | `application/octet-stream` (default) |

---

## scripts/upload_file.py — Upload to OneDrive

```python
from scripts.upload_file import upload_file

# Auto-routes: small (<= 4 MB) uses PUT, large uses resumable session upload
result = upload_file("/tmp/output.xlsx")                   # remote name = basename
result = upload_file("/tmp/output.xlsx", "reports/Q1.xlsx")  # custom remote path

print(result["webUrl"])   # SharePoint URL
print(result["name"])     # filename
print(result["size"])     # bytes
```

**Limits:**
- Files ≤ 4 MB → single PUT to `/me/drive/root:/{name}:/content`
- Files > 4 MB → chunked upload via `/me/drive/root:/{name}:/createUploadSession` (5 MB chunks)

---

## scripts/calendar.py — Calendar management

```python
from scripts.calendar import list_events, get_event, create_event, update_event, delete_event

# List upcoming events (default: 7 days, UTC)
events = list_events(days=14, timezone_name="America/New_York")
for e in events:
    print(e["subject"], e["start"]["dateTime"])

# Get a single event
event = get_event("EVENT_ID")

# Create an event
new_event = create_event(
    subject="Team Sync",
    start="2026-04-10T10:00:00",
    end="2026-04-10T11:00:00",
    timezone="America/New_York",
    attendees=["alice@example.com", "bob@example.com"],
    body_html="<p>Agenda: Q2 planning</p>",
    is_online=True,
)
event_id = new_event["id"]

# Update an event
update_event(event_id, subject="Updated Subject")

# Delete an event
delete_event(event_id)
```

---

## Running the unit tests

```bash
cd /home/sandbox/skills/msgraph
python3 -m unittest tests.test_graph_tools -v
# → 39 tests, all OK
```

## Directory layout

```
msgraph/
  SKILL.md              # this file
  scripts/              # runtime modules — import from here
    __init__.py
    graph.py
    send_email.py
    upload_file.py
    calendar.py
  tests/                # unit tests (all HTTP mocked, safe to run offline)
    test_graph_tools.py
```

> **Note:** `.pytest_cache/` and `__pycache__/` are build artifacts — delete them if they reappear. They are never part of the distributed skill.

---

## ⚠️ Critical: Never use `python3 -c` with dict key lookups in f-strings

**The problem:** When calling `python3 -c "..."` with double-quoted outer strings, any
f-string that uses `["key"]` syntax inside it causes a `SyntaxError` at the shell level
due to conflicting quotes:

```bash
# ❌ WRONG — SyntaxError at shell level
python3 -c "
for e in events:
    print(f'  - {e[\"subject\"]}')   # backslash escapes don't work inside -c strings
"
```

**The fix:** Always write code that accesses dict keys or uses complex f-strings to a
`.py` file, then execute it:

```python
# ✅ CORRECT — write to a file and run it
script = """
import sys
sys.path.insert(0, '/home/sandbox/skills/msgraph')
from scripts import *
from scripts.calendar import list_events

events = list_events(days=7)
for e in events:
    print(f'  - {e["subject"]} | Start: {e["start"]["dateTime"]}')
"""
with open("/tmp/run_me.py", "w") as f:
    f.write(script)
# then: bash("python3 /tmp/run_me.py")
```

Or simply use the Write tool to create the script file, then run it with Bash.

**Rule of thumb:** If your inline Python snippet contains `["key"]`, `.get("key")`,
or multi-line logic — write it to a file first.

---

## Common Patterns

### Build a file and email it as an attachment

```python
import sys
sys.path.insert(0, '/home/sandbox/skills/msgraph')
from scripts import *
from scripts.send_email import send_email

# (build your file at /tmp/output.xlsx using the xlsx skill first)

send_email(
    to="recipient@example.com",
    subject="Your Report",
    body_html="<p>Please find the attached report.</p>",
    attachments=[{
        "name": "report.xlsx",
        "path": "/tmp/output.xlsx",
        "contentType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    }],
)
```

### Build a file, upload to OneDrive, and email the link

```python
from scripts.upload_file import upload_file
from scripts.send_email import send_email

result = upload_file("/tmp/output.xlsx", "Reports/output.xlsx")
url = result["webUrl"]

send_email(
    to="recipient@example.com",
    subject="Report ready on OneDrive",
    body_html=f'<p>Your report is ready: <a href="{url}">Open in OneDrive</a></p>',
)
```
