Skip to content
CloudOps
Newsletter
All guides
AI for Microsoft Teams By James Joyner IV · · 10 min read

Write Microsoft Graph Automation Scripts for Teams With AI

Graph's API surface is huge and the docs are a maze. Use an LLM to draft Teams automation scripts against Graph, then verify permissions and test in a sandbox tenant.

  • #microsoft-teams
  • #graph-api
  • #automation
  • #ai
  • #powershell

Microsoft Graph is the API you use for everything Teams-adjacent — provisioning channels, reading messages, managing memberships, sending activity feed notifications — and it is enormous. The endpoint you need is in there somewhere, alongside ten thousand others, behind documentation that sends you in circles between the v1.0 and beta references. I’ve burned plenty of time just figuring out which call does the thing I want, before writing a single line.

This is where I lean on an LLM hardest in Teams work. The model knows the Graph surface well, it can draft the call and the surrounding script, and it can tell you which permission scope you need. The catch is that Graph permissions are the whole security story, and the model gets them subtly wrong often enough that you must verify every one. The model is a fast junior engineer drafting scripts; you verify the permissions, test in a sandbox, and never hand it real tenant credentials.

Ask for the endpoint, the scope, and the script together

The most valuable thing the model does is collapse the “which endpoint and which permission” research into one answer. Ask for all three: the call, the least-privilege scope, and a runnable script.

I need to add a user to a Teams team by email, using Microsoft Graph,
from a daemon (app-only auth, no signed-in user). Give me:
1. The exact Graph endpoint and HTTP method.
2. The least-privilege APPLICATION permission required.
3. A Python script using msal for app-only auth + requests.
Flag if the operation needs the beta endpoint.

A typical answer drafts something like this:

import msal, requests

app = msal.ConfidentialClientApplication(
    client_id=CLIENT_ID,
    authority=f"https://login.microsoftonline.com/{TENANT_ID}",
    client_credential=CLIENT_SECRET,   # from your secrets manager, never inline
)
token = app.acquire_token_for_client(
    scopes=["https://graph.microsoft.com/.default"]
)["access_token"]

# Resolve user, then add to team
user = requests.get(
    f"https://graph.microsoft.com/v1.0/users/{email}",
    headers={"Authorization": f"Bearer {token}"},
).json()

resp = requests.post(
    f"https://graph.microsoft.com/v1.0/teams/{team_id}/members",
    headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
    json={
        "@odata.type": "#microsoft.graph.aadUserConversationMember",
        "roles": [],
        "user@odata.bind":
            f"https://graph.microsoft.com/v1.0/users('{user['id']}')",
    },
)

That user@odata.bind syntax is exactly the kind of thing you’d spend twenty minutes finding in the docs and the model produces instantly. It’s also the kind of thing that’s easy to get slightly wrong, which is why you test it.

Verify the permission scope yourself — every time

The model will name a permission. Sometimes it’s right, sometimes it names a delegated permission when you asked for application, and sometimes it suggests an over-broad scope because that’s the one it saw most. For the example above it should be TeamMember.ReadWrite.All. Verify against the official Graph permissions reference before you grant anything, and grant the narrowest scope that works.

Over-permissioning is the most common and most dangerous mistake here. A daemon with Directory.ReadWrite.All when it only needed TeamMember.ReadWrite.All is a serious liability if its secret ever leaks. The model optimizes for “the call works,” not “least privilege” — that judgment is yours.

Pro Tip: Keep a running list of which scopes each automation service principal actually uses, and audit it quarterly. AI-drafted scripts tend to accrete permissions because each new script “needs just one more scope.” Periodically prune back to least privilege.

Test in a sandbox tenant, not production

Graph operations are real and often irreversible — you can’t easily un-delete a channel or un-remove a member without disruption. Microsoft offers developer tenants for exactly this. Run every AI-drafted script against a sandbox first, confirm it does what you expect, and check the side effects (did it add the right person, to the right team, with the right role?).

The model has no idea what your production tenant looks like and can’t test against it, which is correct — it should never have that access. Its scripts are drafts that run in your hands, in a safe environment, before they touch anything real.

Handle throttling and pagination — ask the model

Graph throttles, and it paginates. Scripts that work for ten items fail at ten thousand because they ignore @odata.nextLink or get a 429. The model knows these patterns; ask it to add them.

def get_all(url, headers):
    items = []
    while url:
        r = requests.get(url, headers=headers)
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "10")))
            continue
        r.raise_for_status()
        data = r.json()
        items += data.get("value", [])
        url = data.get("@odata.nextLink")
    return items

Honoring Retry-After rather than guessing a backoff is the detail people miss, and the model gets it right when you remind it to handle 429s explicitly.

Keep credentials and tenant identifiers out of the prompt

The script references CLIENT_SECRET, TENANT_ID, and CLIENT_ID — none of those values go to the model. You describe what you need, the model writes code with placeholders, and you wire in the real values from your secrets manager locally. Pasting a real client secret or tenant ID into a hosted model is exactly the leak you don’t want; the secret authenticates an app that can act across your tenant.

The script the model writes runs under your service principal’s identity, which is a powerful credential. Review the code before you run it, especially anything that writes or deletes, and make sure it can’t do more than the task requires.

The honest summary

AI-assisted Graph scripting is a massive time-saver on a genuinely sprawling API — it finds the endpoint, drafts the auth, and handles the boilerplate you’d otherwise dig out of the docs. What it does not do reliably is pick the least-privilege permission, and it cannot test against your tenant. Those are yours.

The discipline is constant: the model is a fast junior engineer that drafts scripts, a human reviews and verifies every permission scope before granting it, you test in a sandbox before production, and you never hand the model real tenant credentials. For more Graph and Teams automation, see the Microsoft Teams category. The code-review dashboard is built for reviewing scripts like these, the prompt library has Graph-scripting prompts to adapt, and for the drafting itself Claude and Cursor both handle the Graph SDK patterns well.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.