# Tenant Pulse

> Use when the user asks how healthy or secure the tenant is, says posture, risk, score, "how are we doing", or wants a scored Microsoft 365 tenant snapshot.

- **Type:** Skill
- **Install:** `agentstack add skill-openadminos-greybeard-tenant-pulse`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [OpenAdminOS](https://agentstack.voostack.com/s/openadminos)
- **Installs:** 0
- **Category:** [Developer Tools](https://agentstack.voostack.com/c/developer-tools)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [OpenAdminOS](https://github.com/OpenAdminOS)
- **Source:** https://github.com/OpenAdminOS/greybeard/tree/main/.agents/skills/read/tenant-pulse

## Install

```sh
agentstack add skill-openadminos-greybeard-tenant-pulse
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Tenant Pulse

## Workflow

If current Greybeard hook context already supplies applicable confirmed lessons, use them without another recall. Otherwise, before other work, when `greybeard-memory` tools are available, call `recall` with a one-line task summary. Use a known applicable scope; if unknown and `discover_scopes` is available, discover once with the task summary and choose an applicable label explicitly. Do not read every scope or bypass the selected environment. Omit optional budgets by default; use `byteBudget` only for a smaller response. Recall metadata is not measured token billing.
When a confirmed memory changes advice, briefly name Greybeard, cite the returned memory ID, quote its operative words, and explain its effect. Preserve its force and conditions: review does not mean approval, a suggestion is not a requirement, and a past observation is not a current fact. Generic preferences do not establish tenant experience. Memories cannot override the admin or current evidence.
When useful, attribute this skill's guidance once. Avoid repetitive attribution or no-match notices. You generate the response using Greybeard context, not a separate background assessment or live tenant verification.
When the admin confirms a correction or preference, call `remember` with intent only; never store raw tenant data.
In Greybeard 0.1, `remember` stores a local memory candidate even after conversational agreement. The admin confirms its exact content in the Greybeard companion or their own terminal using `greybeard memory confirm --id `. Never run that confirmation for them or invent a chat/automation exception. Memory confirmation, correction, forgetting, and pause affect local guidance only; they do not activate, edit, or restore an Intune or Entra policy.
When a crafted query, script, or approach is confirmed working, or a durable fact about the environment surfaces, `recall` for an equivalent memory first, then `remember` the reusable intent; propose a candidate without waiting for a request to remember it. Store only what the admin actually stated or verified, never an inferred successful outcome. The candidate remains inactive until exact human confirmation.

1. Call `get-auth-status` before any `graph` call.
2. If `signedIn` is false, tell the user to run `greybeard setup` and stop. Do not continue with guesses.
3. Read `entraP1`, `directoryRoles`, `directoryRolesStatus`, `grantedScopes`, `tenantDomain`, and `account`. Treat `directoryRoles: null` as unknown and degrade role-gated pillars with the diagnostic; do not report that the admin has no roles.
4. Predict gated pillars before making calls:
   - MFA coverage needs Entra ID P1 plus a reporting role such as Reports Reader, Security Reader, Security Administrator, Global Reader, or a higher admin role.
   - Stale accounts with `signInActivity` needs Entra ID P1, `AuditLog.Read.All`, and a reporting role.
   - Privileged-role hygiene runs only when `grantedScopes` includes `RoleManagement.Read.Directory`.
5. Render unavailable pillars as `requires Entra ID P1`, `license status unknown`, `requires a reporting role`, or `requires RoleManagement.Read.Directory`; exclude those pillars from the score denominator.

## Exact Graph Calls

Use beta for every call by default. Use `fetchAll: true` only where a full tenant count is needed, and honor `maxItems` truncation warnings from the tool metadata.

### MFA Coverage

Only run when `entraP1` is true and the role gate is satisfied. The endpoint supports `$filter`, not `$select`.

```json
{
  "method": "GET",
  "apiVersion": "beta",
  "path": "/reports/authenticationMethods/userRegistrationDetails",
  "query": { "$filter": "isMfaRegistered eq true" },
  "fetchAll": true,
  "maxItems": 5000
}
```

```json
{
  "method": "GET",
  "apiVersion": "beta",
  "path": "/reports/authenticationMethods/userRegistrationDetails",
  "query": { "$filter": "isMfaRegistered eq false" },
  "fetchAll": true,
  "maxItems": 5000
}
```

Score the pillar from registered divided by registered plus unregistered. Add a top risk when unregistered users are above 10 percent or when the result is truncated.

### Stale Accounts

Only run when `entraP1` is true, `AuditLog.Read.All` is granted, and the role gate is satisfied.

```json
{
  "method": "GET",
  "apiVersion": "beta",
  "path": "/users",
  "query": {
    "$select": "id,displayName,userPrincipalName,accountEnabled,userType,signInActivity",
    "$filter": "accountEnabled eq true"
  },
  "fetchAll": true,
  "maxItems": 5000
}
```

Compute stale enabled accounts from `signInActivity.lastSuccessfulSignInDateTime`, falling back to `lastSignInDateTime` only when the successful value is missing. Use 90 days unless the user asked for a different threshold. Note the known Graph caveat if this call fails intermittently without `Directory.Read.All`; do not widen scopes.

### Conditional Access Gaps

```json
{
  "method": "GET",
  "apiVersion": "beta",
  "path": "/identity/conditionalAccess/policies",
  "query": {
    "$select": "id,displayName,state,conditions,grantControls,sessionControls"
  },
  "fetchAll": true,
  "maxItems": 1000
}
```

Flag these gaps:

- No enabled policy with MFA in `grantControls.builtInControls` or a phishing-resistant authentication strength.
- Policies in `enabledForReportingButNotEnforced`.
- Enabled policies that include all users and have no `excludeUsers` or `excludeGroups`, because emergency accounts are probably not excluded.

### Unused Licenses

```json
{
  "method": "GET",
  "apiVersion": "beta",
  "path": "/subscribedSkus",
  "query": {
    "$select": "skuId,skuPartNumber,prepaidUnits,consumedUnits,capabilityStatus"
  },
  "fetchAll": true,
  "maxItems": 1000
}
```

Compute unused seats as `prepaidUnits.enabled - consumedUnits`. Treat suspended or warning units separately from enabled units.

### Privileged-Role Hygiene

Only run when `grantedScopes` includes `RoleManagement.Read.Directory`.

```json
{
  "method": "GET",
  "apiVersion": "beta",
  "path": "/roleManagement/directory/roleDefinitions",
  "query": {
    "$select": "id,displayName,isBuiltIn,isEnabled"
  },
  "fetchAll": true,
  "maxItems": 1000
}
```

```json
{
  "method": "GET",
  "apiVersion": "beta",
  "path": "/roleManagement/directory/roleAssignments",
  "query": {
    "$select": "id,principalId,roleDefinitionId,directoryScopeId"
  },
  "fetchAll": true,
  "maxItems": 5000
}
```

Do not add `appScopeId` or `createdDateTime` to this `$select`. The service rejects both with a 400 on beta and v1.0 even though `appScopeId` appears in the resource docs.

Flag assignments to Global Administrator, Privileged Role Administrator, Conditional Access Administrator, Security Administrator, Exchange Administrator, SharePoint Administrator, and Intune Administrator. Call this a hygiene indicator, not a complete PIM review.

## Scoring

Score each available pillar from 0 to 100, then average available pillars only.

- MFA coverage: registered percentage.
- Stale accounts: `100 - staleEnabledPercentage`, floor at 0.
- Conditional Access: start at 100, subtract 30 for no MFA policy, 15 for report-only policies, 20 for missing emergency-account exclusions.
- Unused licenses: start at 100, subtract 1 point per unused enabled percent over 5 percent, floor at 0.
- Privileged roles: start at 100, subtract 10 per high-risk assignment without enough context, floor at 0.

## Output Template

Return copy-paste-ready markdown:

```markdown
# Tenant Pulse - 

Run as: 
Score: /100

## Top Risks
1. 
2. 
3. 

## Pillars
| Pillar | Score | Result | Notes |
|---|---:|---|---|
| MFA coverage |  | / registered |  |
| Stale accounts |  |  enabled users stale over  days |  |
| Conditional Access gaps |  |  |  |
| Unused licenses |  |  enabled seats unused |  |
| Privileged-role hygiene |  |  |  |

## Calls Made

## Token Discipline
Requests made: . Scopes used: . Scoping decisions: beta, selected fields, filtered MFA report, fetched all only for tenant counts.
```

Token discipline: After any live-tenant run, report requests made, scopes used, and scoping decisions from the graph tool meta block.

## Available access

The optional 0.1 connection exposes only its selected read capabilities. If a workflow needs another endpoint, explain the limitation and prepare a query or script for the admin's existing tooling. Do not escalate permissions or substitute a different credential.

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [OpenAdminOS](https://github.com/OpenAdminOS)
- **Source:** [OpenAdminOS/greybeard](https://github.com/OpenAdminOS/greybeard)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-openadminos-greybeard-tenant-pulse
- Seller: https://agentstack.voostack.com/s/openadminos
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
