Fleet-standard health probe: service/version/status plus two independent Graph probes.
``graph`` confirms Microsoft Graph is reachable at all (``GET /users``
with ``$top=1`` -- needs only ``User.Read.All``, the minimum permission
every deployment of this server needs anyway). ``signin_probe`` additionally confirms the current
credential can read sign-in logs -- the permission every other tool here
except ``get_user`` depends on. Both probes always run, independently of
each other: a tenant that has AuditLog.Read.All but not (yet) the
baseline User.Read.All would otherwise have this report "Graph
unreachable" -- a fabricated diagnosis, since Graph plainly *is*
reachable if the other probe succeeds. ``status`` is derived from the
two outcomes: ``healthy`` when both succeed, ``degraded`` when exactly
one does (Graph is reachable but some permission is missing), ``error``
only when neither does.
Read-only. Always returns the same keys regardless of outcome (``detail``
is null on success, a translated message on failure), so a caller never
has to branch on which keys are present.
``sites`` reports the optional ENTRAADM_SITES_INI table
(``configured``, ``ranges``, ``error``). A configured file that fails to
load is an ``error`` here and leaves ``status`` no better than
``degraded``: without it every internal IP would be reported as an
external spray suspect.
With a tenant file (``ENTRAADM_CONFIG``) every tenant is probed. The
top-level keys stay the DEFAULT tenant's, as in a single-tenant
deployment, plus ``tenant`` (the default's name) and ``tenants``:
``{name: {default, domains, auth_mode, status, graph, signin_probe}}``
for every tenant, the default included. Each tenant's probes run under an
even share of the call budget (``ENTRAADM_DEADLINE``), so a tenant that
does not answer is an ``error`` there instead of stalling the call.
Another tenant that is no
無需輸入參數。
One account's identity/lifecycle state -- the first thing to check on any triage report.
``account_enabled=false`` means the account itself is the whole story;
stop there. A stale ``last_password_change_date_time`` alongside a fresh
"wrong password" complaint (AADSTS50126 in ``signin_logs``) is the most
common on-the-ground pattern: the password changed or expired somewhere,
and a cached credential on one device is now stale.
``on_premises_sync_enabled=true`` means this account is synced from an
on-premises directory (Entra Connect) -- Entra is a downstream copy of
its password via Password Hash Sync, not the source of truth.
``licenses`` names are resolved from the tenant's SKU catalog
(``/subscribedSkus``, page budget from ``ENTRAADM_MAX_PAGES_DEFAULT``);
``licenses_capped: true`` appears only when that scan was cut short
before resolving one of this account's own licenses -- when present,
one or more ``licenses`` entries is a raw skuId rather than a friendly
name.
``sign_in_activity`` needs an additional Graph read (AuditLog.Read.All
application permission, or -- for azure-cli auth -- the Reports Reader
directory role) beyond what the rest of this tool needs. If that
permission is missing, every other field above still returns and
``sign_in_activity`` alone degrades to ``{"error": ...,
"missing_permission": "AuditLog.Read.All"}``.
A nonexistent account is a normal answer, not a tool failure: the result
is ``{"found": false, "user_principal_name": upn}`` rather than an
``error`` key, so a typo'd UPN in a triage report cannot be mistaken for
this tool being broken.
Read-only (User.Read.All application permission, or an equivalent
delegated read). Requires an exact userPrincipalName, not a display name
or partial match.
Args:
upn: The account's userPrincipalName, e.g. "[email protected]".
tenant: Search this tenant instead of the one cl
輸入參數upn必填tenant
One user's recent sign-in events, AADSTS-annotated.
The most direct answer to "why can't this person log in": each entry's
``error_code_meaning`` translates the raw AADSTS code (e.g. 50126 ->
"invalid credentials (wrong password)") so triage rarely needs a second
lookup. ``result`` filters client-side after the Graph fetch (Graph
cannot filter sign-ins on status/errorCode server-side): "failure" (the
default) keeps only failed attempts, "success" keeps only clean ones,
"all" keeps everything.
Because the filter is client-side, this walks pages until it has
collected ``top`` matching entries or exhausts ``max_pages`` -- a mostly-
successful user can otherwise mean paging through hundreds of rows to
find a handful of failures. ``capped=true`` means the page budget ran out
(or ``top`` was reached) before the whole window was scanned; a low match
count alongside ``capped=true`` is evidence of "no more found within the
budget", not "no more exist".
Read-only (AuditLog.Read.All application permission, or -- for azure-cli
auth -- the Reports Reader directory role). Entra ID P1 retains sign-in
logs for 30 days; ``hours`` beyond that returns an empty result, not an
error.
Args:
user: The account's userPrincipalName.
hours: How far back to look, clamped to [1, 720] (30 days).
result: "failure" (default), "success", or "all".
top: Maximum matching entries to return, clamped to [1, 500].
max_pages: Page budget for the client-side filter walk (default: ENTRAADM_MAX_PAGES_DEFAULT).
tenant: Search this tenant instead of the one claiming the UPN's domain (section name, domain or
tenant id): for a <tenant>.onmicrosoft.com or #EXT# UPN, or a B2B guest's sign-ins in the host tenant.
輸入參數topuser必填hoursresulttenantmax_pages
Every sign-in from one source IP: who got in from it, who was tried, and when.
The follow-up to a ``spray_suspects`` or ``shared_ips`` hit: Graph can
filter sign-ins on ``ipAddress`` server-side, so this is one cheap
query, not a log walk. ``users`` summarises the IP per account
(successes, failures, first/last seen, up to 50 accounts) over every row
fetched; ``events`` lists the newest ``top`` entries that match
``result`` ("all" by default, or "success" / "failure"), each carrying
the account name and the same AADSTS annotation as ``signin_logs``.
``capped=true`` means the page budget or the deadline ran out before the
window was fully read; ``events_truncated=true`` means more matching
rows were read than ``top`` returns (the ``users`` summary still counts
them).
Read-only (AuditLog.Read.All application permission, or -- for azure-cli
auth -- the Reports Reader directory role).
Args:
ip: The source IPv4 or IPv6 address, exactly as the sign-in log shows it.
hours: How far back to look, clamped to [1, 720] (30 days).
result: "all" (default), "success", or "failure" -- which events to list.
top: Maximum events to return, clamped to [1, 500].
max_pages: Page budget (default: ENTRAADM_MAX_PAGES_DEFAULT).
tenant: Tenant to read: a [tenant.<name>] section name or one of its domains (default: the default tenant).
輸入參數ip必填tophoursresulttenantmax_pages
Tenant-wide sign-in failure aggregation -- the Entra ID counterpart to the RADIUS failure patrol.
Time-bounded (ENTRAADM_DEADLINE, default 45 s): on a wide window or a busy day the scan
stops early and ``capped=true`` marks the counts as a lower bound; narrow ``hours`` for a
full count.
Aggregates failed sign-ins across the whole tenant into four views: top
AADSTS error codes (with the same meaning annotations as
``signin_logs``), top failing users, top applications, and top source
IPs. ``spray_suspects`` flags any IP with failed sign-ins against 5 or
more distinct users -- Entra's smart lockout is per-account, so a
low-and-slow password spray from one IP across many accounts does not
trip it the way a brute force against one account does; this is the
observation a per-account view cannot make on its own. This mirrors the
KeyCloak-side spray detection this fleet already relies on; neither the
official Microsoft MCP Server for Enterprise nor Graph itself offers this
aggregation.
When ENTRAADM_SITES_INI names the tenant's own address ranges, IPs inside
them get ``site`` (and ``site_name``) in ``top_ips``, and suspects inside
them move from ``spray_suspects`` to ``internal_spray_suspects``: a NAT
egress or a room of shared workstations fails for many users every day,
and would otherwise bury an outside source. Internal suspects are kept,
not dropped, because an internal host can be compromised too. ``sites``
reports whether the file loaded; an invalid CIDR is reported in
``sites.error`` while the valid ranges are still used, and a file that
cannot be read leaves every IP in ``spray_suspects``.
Read-only (AuditLog.Read.All application permission, or -- for azure-cli
auth -- the Reports Reader directory role). Graph cannot filter sign-ins
on status/errorCode server-side, so this walks up to ``max_pages`` of the
full sign-in log for the window and aggregates cli
輸入參數hourstenantmax_pages
Tenant-wide *successful* sign-in aggregation by source IP -- the view that finds a breach.
``signin_failure_stats`` shows who is being attacked; this shows whether
anyone got in. The breach signature is one source IP signing in
successfully as several different accounts, most often over a legacy
protocol (``clientAppUsed`` such as "Authenticated SMTP" or "IMAP4",
which carry no MFA). ``shared_ips`` lists the IPs with successes for
``min_distinct_users`` or more distinct accounts, most-shared first (up
to 50 IPs, ``shared_ips_capped`` when more qualified; account names up
to 25 per IP), with the client apps and the countries seen.
``legacy_auth_users`` lists the accounts that succeeded over a legacy
protocol at all, with how many IPs and countries they came from.
A campus NAT, a VDI farm or a shared proxy also puts many accounts
behind one IP, so a shared IP is a lead, not a verdict: the caller
excludes its own egress ranges and reads the client apps and countries
before calling anything a breach. Graph cannot filter sign-ins on
``status/errorCode`` server-side, so like ``signin_failure_stats`` this
walks the sign-in log for the window and aggregates client-side. The
walk covers interactive sign-ins only (Graph's default listing): every
legacy-protocol authentication is logged as interactive, so none is
missed, but non-interactive token refreshes are not counted;
``capped=true`` means the page budget or the deadline (ENTRAADM_DEADLINE,
default 45 s) ran out first and the counts are a lower bound -- narrow
``hours`` for a full count.
Read-only (AuditLog.Read.All application permission, or -- for azure-cli
auth -- the Reports Reader directory role).
Args:
hours: How far back to look, clamped to [1, 720] (30 days).
max_pages: Page budget (default: ENTRAADM_MAX_PAGES_DEFAULT).
min_distinct_users: Distinct accounts an IP needs to appear in
輸入參數hourstenantmax_pagesmin_distinct_users
Accounts that were probably taken over, from one walk of the sign-in log.
The success-side counterpart of ``signin_failure_stats``' spray suspects.
Two findings, both from EXTERNAL source IPs (outside the address ranges
in ENTRAADM_SITES_INI; without that file every IP counts as external):
- ``spray_breaches`` (severity ``high``): an IP with credential failures
(wrong password, unknown, locked or disabled account -- not MFA
prompts, session interrupts or Conditional Access blocks, which follow
a correct password) for ``min_spray_users`` or more distinct accounts in
the window AND a successful sign-in as at least one account. Without
internal ranges (ENTRAADM_SITES_INI unset or unreadable,
``internal_ranges_known: false``) these are ``medium`` with a ``note``:
the tenant's own NAT egress can look like a spray source. One row per account and IP:
``failed_distinct_users`` from that IP, first/last success, client
apps, countries, ``legacy_auth``. The strongest signal: a spray that
found a working password.
- ``foreign_successes``: successes from a country outside
ENTRAADM_HOME_COUNTRIES (assigned ISO 3166-1 alpha-2 codes, e.g. "JP";
one invalid code disables this list, see ``home_countries.error``), grouped per
account with countries, the most used IPs (10, with counts),
client apps, first/last, and ``home_success_in_window`` (the account
also signed in from a home country or an internal IP in the window;
null when not seen in a capped scan, since the unread part may hold
it). ``high`` when a legacy protocol was used (password only, no MFA),
the account is also a high ``spray_breaches`` entry, the success came
through Azure CLI / Azure PowerShell / Azure AD PowerShell
(``admin_tools``; how stolen passwords are tested and used), or the
successes span 3 or more countries within 24 hours (a rotating proxy
pool, not a trav
輸入參數hourstenantmax_pagesmin_spray_users
Who did what to the directory, and when -- the operator-side counterpart to signin_logs.
Every admin action against a user object (block/unblock, password reset,
role assignment, attribute edits) appears here, naming the actor
(``initiated_by``) and the affected object(s) (``target_resources``).
This is the record a manual "unblock and reset" intervention -- like the
one that closed the 2026-08-21 case this server exists to shorten --
leaves behind; it is how a later triage can tell "already handled by a
human" from "still open".
``user``, when given, matches audits where that account is either the
initiator or a target resource. Graph's directoryAudits endpoint only
supports server-side ``$filter`` on the *initiator*
(``initiatedBy/user/userPrincipalName``), not on ``targetResources``, so
this fetches the full time window and matches both sides client-side --
a window with many unrelated admin actions can need a larger
``max_pages`` budget than ``signin_logs``/``signin_failure_stats`` to
find one specific user's audits; ``capped=true`` warns when that budget
ran out before the window was fully scanned.
Read-only (AuditLog.Read.All application permission, or -- for azure-cli
auth -- the Reports Reader directory role). Entra ID retains directory
audit logs for 30 days, same as sign-in logs.
Time-bounded: the scan stops after ENTRAADM_DEADLINE seconds (default 45)
and returns what it has with ``capped=true``. Over several days the log is
dominated by device-registration noise ("Update device"), so pass
``category`` (for example ``UserManagement``, ``RoleManagement``,
``GroupManagement``, ``ApplicationManagement``) to have Graph filter
server-side; that keeps a multi-day window inside the budget.
Args:
category: Only this Graph audit category (letters only; default: all).
user: Restrict to audits naming this userPrincipalName as actor or target (
輸入參數topuserhourstenantcategorymax_pages
Registered authentication methods for one account -- is MFA actually set up?
``mfa_registered`` answers "would this account survive a password-spray
hit": True iff at least one non-password method is registered
(Authenticator app, phone, FIDO2 security key, Windows Hello, a
temporary access pass, software OATH token, or a platform
credential/passkey). ``password`` itself is excluded from that count --
every account has one, so its presence alone says nothing about MFA
coverage.
A nonexistent account is a normal answer, not a tool failure: the result
is ``{"found": false, "user_principal_name": upn}`` rather than an
``error`` key, matching ``get_user``'s contract.
Read-only (UserAuthenticationMethod.Read.All application permission).
This endpoint is app-only only: it is not exposed to delegated
(azure-cli) auth under this tenant's current role assignment, so it
degrades to a permission error under azure-cli auth even when other
tools work.
Args:
upn: The account's userPrincipalName.
tenant: Search this tenant instead of the one claiming the UPN's domain (section name, domain or
tenant id): for a <tenant>.onmicrosoft.com or #EXT# UPN, or a B2B guest's sign-ins in the host tenant.
輸入參數upn必填tenant
The Azure subscriptions this credential can see, as ARM lists them.
Azure Resource Manager in the DEFAULT tenant only, also when several tenants are
configured (``ENTRAADM_CONFIG``).
Read-only (``GET /subscriptions``; needs no RBAC beyond being able to see a
subscription at all). The discovery anchor for ``vm_backup_status`` and
``vm_backup_policies``: both accept a subscription id or display name from
this list. An empty list with ``auth`` working means no Reader role has
been granted anywhere yet. ``capped`` marks a listing cut short by the page
budget.
輸入參數max_pages
Which Azure VMs are backed up by Azure Backup, and whether the last backup succeeded.
Azure Resource Manager in the DEFAULT tenant only, also when several tenants are
configured (``ENTRAADM_CONFIG``).
Joins every VM of a subscription (``Microsoft.Compute/virtualMachines``)
with the Azure-VM protected items of its Recovery Services vaults, on the
VM's ARM id (case-insensitively; ARM casing differs between the two
services). Read-only; needs the Reader role on the subscription.
Per VM:
- ``protected``: ``true`` when a protected item exists with
``protectionState`` ``Protected`` or ``IRPending`` (first backup not done
yet, ``note`` says so); ``false`` when there is none, or it is
``ProtectionStopped`` / ``ProtectionPaused`` / ``ProtectionError`` (data
may be retained, backups are not running -- the state is shown);
``null`` (unknown) when no item was found but the vault scan was
incomplete (``capped``, a skipped vault, an unreadable or unreached
subscription -- in the global join, any subscription's) -- a partial
scan never produces a false "unprotected".
- ``backup_ok``: ``true`` for ``lastBackupStatus`` ``Completed`` /
``CompletedWithWarnings``, ``false`` for ``Failed``, ``null`` before any
backup ran or while one is running (``InProgress``, ``Warning``, an
unknown value). An item whose ``protectionState`` is missing is
``protected: null`` with a note. When two vaults hold an item for one
VM, the one still backing up wins (``duplicate_items`` counts them).
- ``stale``: no recovery point (``lastRecoveryPoint``, the proof of a
successful backup) within ``stale_hours``. The default 36 h suits daily
policies; raise it for weekly ones (``policy_name`` is in the row).
``subscription`` is an id or display name from ``list_subscriptions``;
``None`` scans every visible subscription and joins globally, so a VM
backed up to a vault i
輸入參數max_pagesstale_hourssubscription
Schedule and retention of the Azure VM backup policies in each Recovery Services vault.
Azure Resource Manager in the DEFAULT tenant only, also when several tenants are
configured (``ENTRAADM_CONFIG``).
Read-only; needs the Reader role on the subscription. Per policy:
``policy_type`` (``V1`` = ``SimpleSchedulePolicy``, ``V2`` =
``SimpleSchedulePolicyV2`` with an hourly / daily / weekly schedule),
``schedule`` (frequency, run times in UTC, weekdays, or the hourly window),
``retention`` (``LongTermRetentionPolicy``: daily / weekly / monthly /
yearly blocks with their day selection and duration), ``instant_rp_days``,
``timezone`` and ``protected_item_count`` (ARM's own count). A schedule or
retention type this server does not know is returned as
``{"type": <name>, "unparsed": true}``, never dropped.
``subscription`` is an id or display name from ``list_subscriptions``
(``None`` = every visible subscription); an unknown or ambiguous name
returns ``found: false``. Coverage flags as in ``vm_backup_status``:
``skipped_vaults`` per subscription, ``skipped`` subscriptions when the
budget ran out, ``capped`` overall.
輸入參數max_pagessubscription
What happened to an Azure VM in a time window: operations people ran, and events Azure raised.
Azure Resource Manager in the DEFAULT tenant only, also when several tenants are
configured (``ENTRAADM_CONFIG``).
Answers "did someone restart this VM, or did Azure?" from the Azure activity
log of the VM's subscription. Read-only; needs the Reader role on the
subscription. Two reads: the VM's resource group (server-side filter, so a
busy subscription cannot crowd the VM's own events out of the page budget)
feeds ``health`` and ``vm_operations``; the whole subscription feeds the rest.
- ``health``: Resource Health events of the VM (``cause``
``PlatformInitiated`` = Azure, e.g. ``Redeploying due to host failure``;
``UserInitiated`` = an operation on the VM, or ``Reboot initiated from
inside the machine`` for a restart from the guest OS).
- ``vm_operations``: Administrative events on the VM and its
sub-resources (restart, deallocate, redeploy, extension changes), with
``caller`` (a user principal name or an application id). One operation
usually logs several events (Started, Accepted, Succeeded) sharing a
``correlation_id``.
- ``other_operations``: Administrative events on any other resource of
the subscription (``count`` of events, ``operations`` = distinct
correlation ids, ``items`` = the newest 50). Zero here and in
``vm_operations`` means nobody operated the subscription through Azure
Resource Manager in the window; it says nothing about actions inside
the guest OS.
- ``service_health``: Azure incidents and planned maintenance announced
to the subscription.
- ``other_categories``: counts of the remaining categories (Alert,
Policy, other resources' health events, ...).
``vm`` is the VM name, matched case-insensitively across the
subscriptions this credential can see; narrow it with ``subscription``
(id or display name from ``
輸入參數vm必填endhoursstartmax_pagessubscriptionresource_group
Microsoft 365 service status per service, and the open incidents and advisories.
Two Graph reads (``ServiceHealth.Read.All``): ``healthOverviews`` (one row
per service with its current status) and the unresolved
``serviceAnnouncement/issues``, newest first. A sign-in failure spike reads
very differently while Entra or Exchange has an open incident, which is
why ``daily_brief`` carries this section too. Each section degrades on its
own (``{"error", "missing_permission"}``); ``summary`` is computed from
whatever came back. ``capped`` means either collection was cut short by
the page budget, so the counts are lower bounds. The message center
(``messages``) needs a different permission and is not read.
Args:
max_pages: Page budget for each collection (default: ENTRAADM_MAX_PAGES_DEFAULT).
tenant: Tenant to read: a [tenant.<name>] section name or one of its domains (default: the default tenant).
輸入參數tenantmax_pages
Start a Microsoft Purview audit log search: what was done in OneDrive/SharePoint, Exchange, Entra.
The follow-up to a confirmed takeover: the sign-in log shows which
resources a session obtained tokens for, this shows what it then did (files
opened or downloaded, mailbox rules and cmdlets, directory changes). A
search runs asynchronously in Microsoft 365 and usually takes several
minutes, so this only submits it (Graph ``POST /security/auditLog/queries``)
and returns ``query_id``; call ``audit_log_search_result`` with that id (and
the same ``tenant``) until ``done`` is true.
The call creates a saved audit search object in the tenant (listed in the
Purview audit search page); it changes no directory, mailbox or file data.
Each call counts against the tenant's audit search limits (searches per
day, searches running at once): a 429 comes back as ``rate_limited: true``
and is not retried. At least one filter besides the window is required, so
a call never searches the whole tenant's log. The filters combine with AND.
Needs the application permission ``AuditLogsQuery.Read.All``, or the
service-scoped ``AuditLogsQuery-OneDrive.Read.All`` /
``-SharePoint.Read.All`` / ``-Exchange.Read.All`` / ``-Entra.Read.All``
(then set ``service`` to match). A 403 returns ``missing_permission``.
``created`` is true, false (nothing was created), or null (the answer was
lost; a search may exist anyway).
Args:
user: Only records of this userPrincipalName.
hours: How far back from ``end`` (or now) to search, clamped to [1, 4320] (180 days). Ignored with ``start``.
start: Window start, ISO 8601 with an offset (e.g. 2026-10-21T22:00:00+09:00).
end: Window end, ISO 8601 with an offset (default: now).
record_types: auditLogRecordType values, e.g. ["sharePointFileOperation", "exchangeItem"].
operations: Operation names, e.g. ["FileDownloaded", "New-InboxRule", "MailI
輸入參數enduserhoursstarttenantkeywordserviceobject_idsoperationsip_addressesrecord_types
Status and, once finished, the summarised records of an audit search started by audit_log_search_start.
Reads Graph ``GET /security/auditLog/queries/{id}``. While ``status`` is
``notStarted`` or ``running`` the answer is the status only (``done:
false``); call again in a minute or two. ``failed``, ``cancelled`` and any
status this server does not know are ``done: true`` with an ``error``. Once ``succeeded``, the records are paged
(``/records``) and come back as:
- ``summary``: ``total_records``, ``first_seen``/``last_seen``, counts by
operation, service, record type and user, and per client IP the count with
first/last seen (top 25 of each list; ``distinct`` has the full counts);
- ``records``: the newest ``top`` records, each with time, operation,
service, record type, user, client IP, object id and a few ``details``
from ``auditData`` (client IP, user agent, result status, site URL, file
name, the names of cmdlet parameters -- never their values), every string
cut to 200 characters. The full ``auditData`` is not returned.
``capped=true`` means the counts are lower bounds: either the page budget or
the deadline ran out before every record was read (call again with a larger
``max_pages``), or ``query.record_count_limit_exceeded`` is true -- Graph
itself stopped collecting at its per-search record limit
(``query.record_count_limit``), so the search holds only part of the
matching records whatever ``max_pages`` is; narrow the window or the
filters and start a new search. ``records_truncated`` means more records were read than
``top`` returns (the summary counts them all). An unknown id is ``found:
false``: a search exists only in the tenant that created it, so pass the
``tenant`` its start call used. Permissions as audit_log_search_start; a
403 returns ``missing_permission``.
Args:
query_id: The ``query_id`` returned by audit_log_search_start (a
輸入參數toptenantquery_id必填max_pages
One-call morning-patrol summary: M365 service health, sign-in failures, spray suspects, admin actions.
Combines ``m365_service_health``, ``signin_failure_stats``,
``directory_audits`` and ``compromise_suspects`` (its counts and the top
5 of each list; ``summary.compromise_suspects`` has the high/medium
counts) into one result with a compact ``summary`` on top. The failure
stats and the compromise suspects come from one walk of the sign-in log.
The shape matches this fleet's other ``daily_brief`` tools. A
permission failure in one section degrades only that section's
contribution to ``summary`` -- the other sections still return in full.
Azure VM backup is deliberately not a section here: its ARM fan-out is
the one scan that could exhaust the shared budget, so call
``vm_backup_status`` separately.
With a tenant file (``ENTRAADM_CONFIG``) it covers every tenant: the
top-level keys stay the DEFAULT tenant's brief, exactly as in a
single-tenant deployment, plus ``tenant`` (the default's name) and
``tenants`` (``{name: the same brief}`` for each other tenant). The call
budget (``ENTRAADM_DEADLINE``) is split: default first, each tenant gets
an even share of what is left, so a busy tenant cannot starve the next
one (a tenant that finishes early leaves its rest to the others). With
``ENTRAADM_DEADLINE=0`` there is no budget and every tenant is scanned in
full, one after another, which can outlast a client's ~60 s cutoff.
Env-only results carry neither key.
Runs both sections synchronously in one tool call, unlike the sibling
gwsadm-mcp's job+poll ``daily_brief``. If this proves too slow for a
tenant's sign-in volume against the client's tool-call timeout, port
that job+poll pattern here (tracked in this repo's CLAUDE.md Roadmap).
Args:
hours: How far back to look, clamped to [1, 720] (30 days).
max_pages: Page budget passed to both sections (default:
輸入參數hourssamplesmax_pages