---
name: msgraph-api
description: Microsoft Graph via the `msgraph` tool — agent365 fallback for M365 data (mail/calendar/files/Teams, etc.); read + write
metadata:
  activation:
    keywords:
      [
        microsoft graph,
        graph api,
        msgraph tool,
        graph rest,
        agent365 fallback,
        agent365 down,
        msgraph,
      ]
    patterns:
      - "(?i)\\bgraph\\s+(api|tool|rest|endpoint|call)\\b"
      - "(?i)\\bagent365\\b.{0,25}\\b(down|fail(ed|ure)?|unavailable|outage|error)\\b"
      - "(?i)\\b(microsoft\\s+)?graph\\b.{0,20}\\bfallback\\b"
  requires:
    tools: [msgraph]
---

# Microsoft Graph (via the `msgraph` tool)

Direct `https://graph.microsoft.com` REST — the **fallback** for Microsoft 365
data when an agent365 MCP server is down. The tool gets + refreshes the token
silently (a one-time sign-in may be needed on the very first call); you just call
`msgraph(method, path)`.

## When to use

Use **only after** an agent365 MCP tool has actually failed (connection error /
server down) for the same data — prefer agent365 when healthy. On a failure the
runtime already names the graph path(s) to try; this skill is the full catalog
behind that hint.

## Interface

`msgraph(method, path, body?, max_items?)`

- `method` — `GET` · `POST` · `PATCH` · `PUT` · `DELETE`
- `path` — e.g. `/me/messages?$top=5` (v1.0 assumed; `/beta/…` honored). A full
  `@odata.nextLink` / `@odata.deltaLink` URL is accepted **verbatim** — paste it
  straight back to continue paging or a delta sync.
- `body` — JSON object for writes; **omit for GET** (a GET body is rejected)
- `max_items` — GET collections only: auto-follow `@odata.nextLink` and return up
  to N items (≤ 1000) in **one** call (vs hand-looping). Omit for a single page.

## Endpoint catalog → read the module you need

The curated, copy-pasteable catalog lives in **`references/`** beside this
SKILL.md (resolve from this skill's `path`). Read the ONE domain file you need
before calling an endpoint you're unsure about:

| Domain | File · agent365 server it stands in for |
|--------|-----------------------------------------|
| Mail | `mail.md` · mail |
| Calendar | `calendar.md` · calendar |
| Files & SharePoint | `files.md` · onedrive · sharepoint |
| Teams & presence | `teams.md` · teams |
| People & directory | `people.md` · user · m365copilot (search) |
| Tasks & notes | `productivity.md` · To Do · OneNote |

Everything there is scoped to what this token can actually do; anything not
listed is uncommon or unavailable here. **Don't invent paths** — Graph has ~28k
endpoints and a guessed one usually 404s or lacks a scope here; if a task isn't in
the catalog, prefer agent365 or tell the user.

## Coverage

Reads always work. **Writes and Teams chats / channels / meetings usually work
too — just try the call, don't pre-exclude them.** A surfaced 403 means it isn't
available with this token → fall back to agent365 (see Errors).

## Hard limits — do NOT attempt

- **Tenant-admin writes** — any non-GET on `/users`, `/groups`, `/applications`,
  `/servicePrincipals`, `/directory`, `/roleManagement`, `/deviceManagement`,
  `/policies`, `/identityGovernance`, `/security`, `/auditLogs` is rejected
  (risk 10). Those surfaces are **read-only** here.
- **`$batch`** — rejected.
- **Header-dependent queries** — the tool sends only its own `Authorization`;
  it cannot add request headers. So:
  - No `ConsistencyLevel: eventual` ⇒ **no advanced directory queries**
    (`$count`, `$search` on users/groups, `$filter` with `endsWith`/`not`).
    Use simple `$filter`/`$select` only.
  - No `Prefer: outlook.timezone` ⇒ calendar/mail times come back **UTC**;
    convert client-side.

## Querying & paging

- v1.0 unless a field is beta-only.
- **Paging — just pass `max_items=N`.** The tool follows `@odata.nextLink` for you
  and returns up to N items in one call (≤ 1000), with an `@odata.note` + the next
  link if there's more. To page by hand instead, take `@odata.nextLink` from a
  response and pass it back as `path` (a full URL — the tool normalizes it).
- **Delta — "what changed since last time".** `GET /me/messages/delta` (also
  `calendarView`, `contacts`, `drive/root` — each domain file lists its first
  call) returns changes plus an `@odata.deltaLink`. Persist that deltaLink (your
  memory / session); next run, pass it back as `path` to get **only** what changed.
  A removed item is `{"id":"…","@removed":{"reason":"deleted"}}`.
- **Always `$select` the fields you need** and cap with `max_items` (or `$top`,
  which a few Teams lists reject — see the ref) — Graph payloads are large; an
  unscoped list call wastes context. Plus `$orderby`,
  `$filter`. `$expand` pulls related resources in one call (e.g.
  `…/messages/{id}?$expand=attachments`, `…/lists/{id}/items?$expand=fields`).
- Mail/message `$search` works (no special header); directory `$search` doesn't
  (see Hard limits).
- Times are ISO-8601; pass UTC (`…Z`) and convert returned times yourself.

## Errors

| Status | Do |
|--------|----|
| 401 | the tool auto-refreshes the token once — no action |
| 403 | not available with this token → fall back to agent365 (or `wp msgraph add-scope`). The tool already did its one retry; **don't retry blindly**. |
| 404 | check the path against the domain file in `references/` |
| 429 | respect `Retry-After` |

After any write (POST/PATCH/PUT/DELETE), **read the resource back** and verify
before confirming success to the user. And before a write that's hard to undo —
**sending** mail/chat, **deleting**, or anything reaching **external recipients**
— confirm intent with the user first. (The risk gate blocks tenant-admin writes,
but it can't read intent.)
