
A lot of MSPs have asked me how I have Grok Bot watching my Microsoft 365 tenants every morning. The short answer is: not by handing it the keys to the kingdom. I wrote earlier about how Grok Bot just gets things done. This post is the practical follow-up. It is a step-by-step walk through the way I connected it to Microsoft 365, why I chose scripts over broad direct connectors, and how the same pattern scales to as many tenants as you look after.
Why scripts, not a big direct connector
The tempting option is to give an AI agent a delegated admin connection and let it go wild. I think that is the wrong default for an MSP. Here is why the script approach wins:
- Least privilege. Each tenant gets its own app registration with only the read permissions the checks actually need, such as reading sign-in logs, audit logs, security alerts and Intune device state. Nothing more.
- Read-only by design. The monitoring app cannot change settings, reset passwords or close incidents. If the bot gets confused, the worst it can do is read.
- Certificate authentication. The scripts sign in app-only with a certificate rather than a client secret or a user password. No MFA prompts, no browser, and no shared admin account sitting in a vault.
- No standing admin access. Nobody, human or bot, holds a permanent Global Admin session for monitoring. Anything that writes, like tidying up Sentinel incidents, is a separate, deliberate step that I trigger myself.
- Auditable. The scripts are plain code I can read, version and diff. When the bot runs a check, I know exactly which API calls it made, and the tenant logs show them as that one app.
In other words, the AI decides when to look and how to explain what it found. The script decides what it is allowed to touch.
Step 1: Create a read-only app registration in each tenant
- In the Entra admin centre, create a new app registration for monitoring, named so it is obvious what it is (for example “MSP Monitor – Read Only”).
- Add application permissions for Microsoft Graph that are read only, for example AuditLog.Read.All, Directory.Read.All, SecurityAlert.Read.All, SecurityIncident.Read.All, ThreatHunting.Read.All, DeviceManagementManagedDevices.Read.All, Policy.Read.All and ServiceHealth.Read.All. Start small and only add what a check needs.
- Grant admin consent.
- Create a certificate, upload the public part to the app registration, and keep the private key (PFX) protected on the box where the bot runs, with its password in a separate file that only the scripts read.
- If you use Microsoft Sentinel or Defender for Cloud, give the app a Reader role on that subscription or workspace, not Contributor.
I covered the certificate-based, headless sign-in pattern in more detail in Headless SharePoint and Exchange Online connections on Linux, built for agents.
Step 2: Write the check scripts
My checks are simple scripts that sign in with the certificate (MSAL in Python, or PowerShell with the Graph module), call read-only endpoints and return a summary. Mine cover things like sign-ins and risky users, audit events, new guests, Conditional Access changes, app credential changes, Sentinel and Defender incidents, Intune compliance and service health. Keep these scripts in a GitHub repo so you have history, review and rollback. Many of my own scripts are public in my Office365 repository if you want a starting point.
Step 3: Keep tenant details in one config file
Rather than hard-coding anything, I keep a single config file listing each tenant: its domain, a short name, and the path to its certificate. Each tenant gets its own folder for output. The scripts take the short name as an argument, so the same code runs against any tenant. Never paste tenant IDs, client IDs, thumbprints or keys into a chat with the bot. Put them in the config and tell the bot where it lives.
A structure like this works well:
tenants.json– one entry per tenant (domain, short name, certificate path, password file path)tenant_check.py– the core read-only daily checksextra_check.py– additional checks you add over time<tenant>/– one output folder per tenant for reports and history
Step 4: Tell Grok Bot the rules
Now the bot gets its instructions. Be explicit about what it may and may not do. For example:
Example prompt: “You are a read-only security monitor for the Microsoft 365 tenant contoso.com. Only use the scripts in /home/box/shared/msp-security and the certificate config in tenants.json. Never change tenant settings, close incidents, or print or ask for passwords, keys or certificate details. If a check needs a permission the app does not have, tell me which one rather than working around it.”
Step 5: Create a daily routine
Grok Bot can run work on a schedule. I have a morning routine per tenant that runs the checks and writes a plain-English report.
Example prompt: “Create a daily routine at 8:45am Sydney time that runs python3 tenant_check.py contoso, then gives me a short report: status, sign-ins and risky users, directory and guest changes, devices and Intune, Sentinel and Defender incidents, and anything that needs my action. Treat sign-ins from our office IPs as known benign. Keep a delivery log line for each run.”
Over time, tell the bot what is normal. When it flags something that turns out to be you, say so, and have it add that to the routine’s known-benign list. The reports get quieter and more useful every week.
Step 6: Scale to as many tenants as you like
This is where it gets interesting for an MSP. Because the scripts are tenant-agnostic, adding a customer is just a new app registration, a new certificate and a new line in the config. Then pick one of two patterns:
- One bot per tenant. This is what I do. Each bot has its own memory, its own known-benign list and its own routine. It keeps context clean and stops one customer’s quirks bleeding into another’s report.
- A single tenants bot. One bot loops through every entry in the config and produces a summary table, then drills into any tenant with issues. Good for a quick “how is everyone today?” view.
On top of that, I have a Chief of Staff bot that asks each of my other bots for a one-line daily update and rolls them into a single digest. That is how one person can keep an eye on many tenants without reading many reports.
Example prompt: “Add a new tenant: fabrikam.com, short name fabrikam. The app registration and certificate are already in tenants.json. Run the read-only checks once to confirm access, tell me any permission that is missing, then create the same 8:45am daily routine you have for contoso.”
Example prompt: “Every day at 9:30am, run the read-only checks for every tenant in tenants.json and give me one table: tenant, overall status, open high-severity incidents, risky users, and non-compliant devices. Only expand on tenants that need action.”
Example prompt: “Each afternoon, send Chief of Staff one line on the last 24 hours for each tenant: what was checked, any errors, and anything waiting on me.”
Step 7: Keep write actions separate and deliberate
There will be times you want the bot to fix something, like closing a backlog of noisy Sentinel incidents. I keep that in a separate script, with its own permissions, run by a separate PowerShell bot only when I explicitly ask. Monitoring stays read only. Changes stay human-approved.
Example prompt: “Only when I say so, run the cleanup script for contoso to close Sentinel incidents older than 30 days with the comment “Closed by automation”. Show me the list first and wait for my go-ahead.”
The takeaway
Connecting AI to Microsoft 365 is not about giving it more access. It is about giving it the right access, through code you own and can audit. Build the scripts once, give each tenant a read-only, certificate-backed app, and let Grok Bot do the watching and the explaining. That is how a small MSP can monitor a lot of tenants properly, every single morning.




