Tool Reference
The contract of every StratoLens MCP tool: permission, arguments, output fields and errors. Assistants read the schemas from tools/list on their own; this page is for checking what a tool can answer, writing your own MCP client, or understanding an error.
20 Tools
Listed in the order tools/list returns them.
whoamilist_scanssearch_resourcesget_resource_detailsget_role_assignmentsget_access_optimizationsget_scan_changesget_changes_summaryget_changesget_activity_logget_security_findingsget_defender_alerts_summaryget_policy_compliance_summaryget_cost_summaryget_cost_anomaliesget_orphaned_resourcesget_sizing_recommendationsget_service_retirementsget_commitmentsget_commitment_recommendations
Read-only, as the signed-in user
Every tool is read-only and runs with the signed-in user's StratoLens permissions and data scope. Fields can be added between releases, so clients should ignore fields they don't recognise.
Endpoint and Protocol
- Endpoint
POST https://<host>/api/mcp. Only POST is supported; other methods get 405.- Transport
- MCP Streamable HTTP, stateless (no sessions). Responses are JSON, framed as server-sent events when the client accepts
text/event-stream. - Methods
initialize,tools/list,tools/call- Server name
stratolens- Instructions
- The
initializeresult tells the assistant which tool to use for which question (see Tool Routing). - Result format
- A successful result is one text item holding JSON. A failed result is marked as an error and holds
{ "error": "<message>", "code": "<code>" }. A tool never returns an empty result in place of a failure.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_scan_changes",
"arguments": { "changeTypes": ["deleted"], "limit": 20 }
}
}Per-tool examples below show only the arguments object.
Conventions
- Strict arguments. Unknown arguments are rejected. Arguments that don't match the schema are rejected before StratoLens runs the tool, with a message starting "Input validation error:"; such calls are not counted against the rate limit.
tools/listpublishes each schema with enums and defaults. - Free-text arguments (IDs, names, search text, timestamps) are 1 to 1,024 characters.
- An empty array is treated as if the argument were not given.
- Links. Rows and results carry
link, an absolute URL to the matching StratoLens page, ornullwhen no public address is configured or no page can show the row. - Recommendation timestamps.
lastUpdatedScanIdandlastUpdatedAtname the latest scan that produced recommendations, not the scan that last changed a given row. - Check order for every call that passes argument validation: permission (
forbidden), data scope (scope_unresolved), rate limit (rate_limited), the tool's own checks, the 60 s time limit (timeout), the 200 KB result limit (result_too_large).
Paging
Every tool that takes offset pages the same way. limit is an integer 1-200, default 50; offset is an integer of 0 or more, default 0. The result has items plus totalCount (every match), offset, nextOffset (null on the last page) and truncated (true when this page doesn't hold every match, so also on later pages). Page with offset = nextOffset until it is null.
An offset past the end returns an empty page, not an error. Counts and totals cover every match and are the same on every page. Sort orders end in a stable ID tie-break, so paging the same data neither repeats nor skips rows (except get_activity_log, see its notes). Where a summary tool pages its per-subscription list, the paging fields sit inside subscriptions.
Hidden items
get_security_findings, get_cost_anomalies, get_orphaned_resources, get_access_optimizations, get_sizing_recommendations, get_service_retirements and get_commitment_recommendations leave out items an administrator hid on the page and count them (hiddenCount, hiddenResourceCount or hiddenPrincipalCount). With includeHidden true they return them with hidden: true.
Common errors
Any tool can return these. Per-tool Errors below list only the tool's own checks, in order.
Errors
- forbidden
Missing permission <id> - scope_unresolved
Your data scope could not be resolved, so no data can be returned. - rate_limited
Rate limit of <n> tool calls per minute reached. Retry in <s> s. - timeout
The tool did not finish within 60 s. Narrow the query with filters or a smaller limit and retry. - result_too_large
Result is <n> KB, over the 200 KB budget. Narrow the query with filters or a smaller limit. - failed
The tool failed unexpectedly. The server log has the detail; retry, or narrow the query.
Scan ID errors
Every tool that resolves a scanId:
Errors
- not_found
No completed scan exists yet. Run a scan in StratoLens first.(scanId omitted or latest, on a fresh install) - not_found
Scan not found: <id>. Use list_scans to find a valid scanId.(unknown ID)
Tool Routing
The server instructions steer the assistant like this:
- What changed:
get_scan_changesfirst.get_changes_summaryandget_changesfor exactly who and when.get_activity_logfor operation detail and role-assignment activity. - Who has access:
get_role_assignments. Unused, stale or over-privileged access:get_access_optimizations. - Savings:
get_sizing_recommendations,get_orphaned_resources,get_cost_anomaliesandget_commitment_recommendationstogether. - Reservations and savings plans:
get_commitments(inventory, spend, utilization, expiry) andget_commitment_recommendations(what to buy, underutilized or expiring alerts, cleared alerts). Commitment charges over time:get_cost_summary. - Upcoming Azure retirements:
get_service_retirements.
Permissions
The forbidden message names the permission ID, not the name shown in StratoLens.
| Permission | ID | Tools |
|---|---|---|
| none (any signed-in user) | whoami | |
| View Scans | scan.read | list_scans |
| View Resources | data.read | search_resources, get_resource_details, get_role_assignments, get_access_optimizations, get_scan_changes, get_security_findings, get_orphaned_resources, get_sizing_recommendations, get_service_retirements |
| View Resource Changes | change_tracking.read | get_changes_summary, get_changes |
| View Azure Activity Logs | activity_log.read | get_activity_log |
| View Defender Assessments | defender.read | get_defender_alerts_summary |
| View Policy Data | policy.read | get_policy_compliance_summary |
| View Costs | cost.read | get_cost_summary, get_cost_anomalies, get_commitments, get_commitment_recommendations |
Identity and Scans
whoami
Permission: none (any signed-in user)
Who the caller is, their permissions, data scope and the MCP client in use. Takes no arguments; any argument is rejected.
Output
| Field | Meaning |
|---|---|
userId | Your Entra object ID |
displayName | Your display name |
permissions | Your effective StratoLens permission IDs, e.g. data.read, cost.read |
grantingSources | How your access was granted (your user grant and/or group grants) |
scope.mode | all (every subscription) or include (limited) |
scope.subscriptionCount | Number of subscriptions you are limited to; null when mode is all |
clientId | Client ID of the MCP client app in use |
clientName | That client's display name in the Trusted clients list, or null |
Notes
- Write and manage permissions listed here apply only in the web app.
Example arguments
{}list_scans
Permission: View Scans
Finished scans, newest first. Use it to pick a scanId or see when the inventory was last refreshed. Arguments: limit, offset (paging).
Output (items)
| Field | Meaning |
|---|---|
scanId | Scan ID |
status | Completed, Failed, CompletedWithNoResults or Cancelled |
startedAt / completedAt | Timestamps (completedAt may be null) |
resourceCount | Resources found |
entityCounts | { managementGroups, subscriptions, resourceGroups, resources }, or null |
warningCount | Scan warnings (0 when none) |
statusMessage | Status text, or null |
Notes
- Counts are for the whole tenant, not your data scope.
- No
link: there is no scan-list deep link.
Example arguments
{ "limit": 5 }Inventory and Access
search_resources
Permission: View Resourcesaccepts scanId=latest (mode current)
Find subscriptions, resource groups, resources and management groups by name. This is how to get the entityId that get_resource_details needs.
Arguments
| Name | Required | Format / values | Default | Notes |
|---|---|---|---|---|
query | yes | text, at least 2 characters | Contained in the name, case-insensitive | |
mode | no | current, all | current | all searches every scan, so it also finds deleted entities |
entityType | no | all, subscription, resourceGroup, resource | all | all also returns management groups |
scanId | no | scan ID or latest | latest completed scan | Mode current only; ignored (not checked) in mode all |
limit | no | integer 1-50 | 50 | Lower maximum than other tools |
Output: mode, scanId (null in mode all), items, totalCount, truncated. No offset or nextOffset.
| Field | Meaning |
|---|---|
entityId | Full ARM ID |
name, resourceType, entityType | Entity name, ARM type, hierarchy level |
subscriptionId, subscriptionName, resourceGroup | Location in the hierarchy (resourceGroup may be null) |
lastSeenScanId, lastSeenAt | Mode all only: the last scan the entity was seen in |
link | Explorer at the row's scan |
totalCount | Matches the search returned (at most 50) |
truncated | True when more rows exist than returned, or the search hit its 50-match cap: refine the query |
Errors
- Scan ID errors (see Conventions)(mode current only)
Notes
- At most 50 matches, highest hierarchy level first. No paging; refine the query instead.
Example arguments
{ "query": "prod-sql", "entityType": "resource" }get_resource_details
Permission: View Resourcesaccepts scanId=latest
One entity as recorded in a scan, optionally with its scan-by-scan history.
Arguments
| Name | Required | Format / values | Default | Notes |
|---|---|---|---|---|
entityId | yes | full ARM ID | Case-insensitive; from search_resources | |
scanId | no | scan ID or latest | latest completed scan | |
entityType | no | managementGroup, subscription, resourceGroup, resource | Required for a management group when your access is limited to part of the tenant | |
includeHistory | no | boolean | false | Adds history |
Output
| Field | Meaning |
|---|---|
entityId, name, type, entityType | Identity |
location | Azure region, or null |
subscriptionId, subscriptionName, resourceGroup | Hierarchy (resourceGroup may be null) |
tags | Tag name/value object |
scanId | The scan read |
versionFirstSeenAt, versionLastSeenAt | When this version of the entity was first and last recorded |
rbac | { collectionStatus, directAssignmentCount, eligibleAssignmentCount }, or null |
link | Explorer at the scan |
history | With includeHistory only: { items, totalCount, truncated }; items newest first, at most 50: { scanId, scanTimestamp, status, hasChildChanges }, status created, present or deleted |
Errors
- Scan ID errors (see Conventions)
- not_found
Entity with ID '<id>' not found in scan '<scanId>'.(unknown entity, or outside your data scope) - not_found
This resource has not been discovered by any scans yet. …(with includeHistory, for an entity no scan has found)
Notes
- Resource properties are not returned; open
linkfor them.
Example arguments
{ "entityId": "/subscriptions/<guid>/resourceGroups/rg-prod/providers/Microsoft.Sql/servers/prod-sql", "includeHistory": true }get_role_assignments
Permission: View Resourcesaccepts scanId=latest
Who holds which Azure RBAC role where, matching the Role Assignments page. Two modes: principal list (default) or one principal's assignments (principalId set).
Arguments
| Name | Required | Format / values | Default | Notes |
|---|---|---|---|---|
scanId | no | scan ID or latest | latest completed scan | |
principalId | no | object ID | Switches to assignment mode | |
principalTypes | no | array of User, Group, ServicePrincipal | all | Principal list only; ignored with principalId |
roleCategories | no | array of critical, management, read, other | all | |
scopeTypes | no | array of managementGroup, subscription, resourceGroup, resource | all | Level the assignment was made at |
assignmentSources | no | array of direct, inherited | all | inherited = through group membership |
pimStatuses | no | array of permanent, active, eligible | all | active = PIM time-bound active; eligible = PIM eligibility |
roleNames | no | array of exact role names | all | availableRoleNames lists valid names |
search | no | text | Display name contains, case-insensitive; principal list only | |
limit, offset | no | paging | 50, 0 | Both modes |
Output, principal list mode: scanId, principalCount, assignmentCount, byRoleCategory { critical, management, read, other } (the page's filter-responsive badge counts, summed), availableRoleNames, paging fields, link, and items most privileged first:
| Field | Meaning |
|---|---|
principalId, displayName, principalType | The user, group or service principal |
assignmentCount | Matching assignments |
critical, management, read, other | Matching assignments per role category |
link | Role Assignments page with the same filters and the principal selected |
Output, assignment mode: scanId, principalId, paging fields, link, and items most privileged role and broadest scope first:
| Field | Meaning |
|---|---|
roleName, roleCategory | Role and its category |
scope, entityType, entityName | Where the role applies |
subscriptionId, subscriptionName, resourceGroup | Hierarchy, or null |
assignmentSource | direct or inherited |
grantedViaGroupId, grantedViaGroupName | The group an inherited assignment comes from, or null |
pimStatus, pimEndDateTime | PIM status and end time (or null) |
createdOn | Assignment date, or null |
link | Role Assignments page with the principal selected (same for every row) |
Errors
- Scan ID errors (see Conventions)
Notes
- Role categories follow the Role Assignments page: any role with "Owner" or "Administrator" in its name is critical, so Storage Blob Data Owner counts; roles containing Contributor, Manager or Operator are management; reader and monitoring roles are read.
- Group membership is expanded. Filters combine with AND.
- Links carry every filter except
searchand open with the page's default sort. - The principal list is reused for 2 minutes per scan.
Example arguments
{ "roleCategories": ["critical"], "principalTypes": ["ServicePrincipal"] }get_access_optimizations
Permission: View Resources
RBAC access that could be reduced, matching the Access Optimization page, principals with most optimizations first. Always reads the latest recommendations.
Arguments
| Name | Required | Format / values | Default | Notes |
|---|---|---|---|---|
timeWindowDays | no | 7, 30, 60, 90, 180, 365 | 30 | Activity window the optimizations are derived for |
optimizationTypes | no | array of overPrivileged, overScoped, unused, stale, excessiveSprawl, redundantAssignment, unusedEligibility | all | |
principalTypes | no | array of User, Group, ServicePrincipal | all | |
roleTypes | no | array of critical, management, read, other | all | Access Optimization categories (see notes) |
principalId | no | object ID | Only this principal | |
search | no | text | Display name contains, case-insensitive | |
includeHidden | no | boolean | false | |
limit, offset | no | paging | 50, 0 | Pages principals |
Output: lastUpdatedScanId, lastUpdatedAt, timeWindowDays, byOptimizationType (all seven types, over every match), principalCount, hiddenPrincipalCount, paging fields, link, and items:
| Field | Meaning |
|---|---|
principalId, displayName, principalType | The principal |
optimizationCount | Its optimizations after the type and role filters |
hidden | Principal hidden by an admin |
optimizations | First 25, each: type, roleName, scope, scopeType, entityName, subscriptionId, subscriptionName, resourceGroup, grantedViaGroupId, grantedViaGroupName, lastActivity, action, suggestedRole, suggestedScopeType, redundancyReason and redundancyDetails (set on redundantAssignment only, else null), pimEligible, hidden |
optimizationsTruncated | More than 25 optimizations exist |
link | Access Optimization page with the same window and filters and the principal selected |
Notes
- Type meanings:
overPrivilegedcritical role with no role-assignment changes;overScopedactivity in under 30% of its scopes;unusedcritical or management role with no activity;stalelast activity over half the window ago;excessiveSprawlsame role in 5+ subscriptions;redundantAssignmentduplicated by another grant;unusedEligibilityPIM eligible role not used. - Groups only get sprawl and redundancy.
- Role categories differ from
get_role_assignments: exactly Owner or User Access Administrator = critical; exactly Contributor = management; name containing Reader, Viewer or Monitor = read. - Links carry every filter except
search. - Each principal's optimizations are in derivation order, not the page's detail-panel order.
Example arguments
{ "optimizationTypes": ["unused", "overPrivileged"], "timeWindowDays": 90 }Changes and Activity
get_scan_changes
Permission: View Resources. "Changed by" data also needs View Resource Changes and/or View Azure Activity Logs.accepts latest (compareScanId only)
The "what changed" tool. Compares two scans exactly as the Changes page does with Show Ignored off: ignore rules applied, role-assignment changes included, with who made each change.
Arguments
| Name | Required | Format / values | Default | Notes |
|---|---|---|---|---|
compareScanId | no | Completed scan ID or latest | latest completed scan | The newer scan |
baseScanId | no | Completed scan ID older than compare | the completed scan right before compare | Not with since |
since | no | ISO 8601 UTC timestamp | Picks as base the newest completed scan at or before this time; not with baseScanId | |
changeTypes | no | array of added, deleted, modified | all | Filters items only; counts are not affected |
entityTypes | no | array of managementGroup, subscription, resourceGroup, resource | all | |
subscriptionId | no | GUID | Case-insensitive | |
resourceGroupName | no | name | Case-insensitive; requires subscriptionId | |
resourceType | no | exact ARM type | Case-insensitive, e.g. Microsoft.Compute/virtualMachines | |
includeAttribution | no | boolean | true | false skips the "changed by" lookup |
limit, offset | no | paging | 50, 0 |
Output
| Field | Meaning |
|---|---|
baseScan, compareScan | { scanId, scanTimestamp } of the pair compared |
sinceCoverage | Only with since: exact; startsLater (no scan that old, the oldest was used); endsBefore (no scan since then; use get_changes) |
byStatus | { added, deleted, modified }, after the scope filters, before changeTypes |
byEntityType | { managementGroup, subscription, resourceGroup, resource }, same basis |
hiddenByIgnoreRules | { typeRules, ignoredOnly }: typeRules is the page's Type Ignores tab total; ignoredOnly counts rows whose only changes are ignored (not a number the page shows) |
attribution | { available: true, sources: { arg, activity } } or { available: false, reason } |
topChangedBy | Only when attribution is available: top 10 { displayName, changeCount, rbacCount } over every match |
items | Sorted deleted → added → modified, then entity type, name, ID |
link | Changes page for the pair (with the subscription / resource group selected when filtered) |
Each item
| Field | Meaning |
|---|---|
entityId | ARM ID, lowercase |
name, type, entityType, changeStatus | Identity and added / deleted / modified |
subscriptionId, subscriptionName, resourceGroup | Hierarchy, or null |
changedAreas | What changed, e.g. properties, tags, rbacAssignments |
changedPropertyPaths, changedPropertyCount | First 5 changed property paths and the total (modified rows only) |
hasChildChanges, childChangeCounts | Changes below this entity |
changedBy | Only when attribution is available: top 3 { displayName, sources, changeCount, rbacCount, lastChangeTime }; empty = Unattributed |
changedByTruncated | More than 3 users |
link | Changes page for the pair with the entity selected (management groups: the pair only) |
Errors
- invalid_arguments
Pass baseScanId or since, not both. - invalid_arguments
resourceGroupName requires subscriptionId to be specified - invalid_arguments
since must be a valid ISO timestamp - Scan ID errors (see Conventions)(for an explicit compareScanId / baseScanId)
- not_found
No completed scan exists yet. Run a scan in StratoLens first. - invalid_arguments
Scan <id> is not a Completed scan; use list_scans to find a Completed scanId.(compare) - not_found
No completed scan is older than <id>; there is nothing to compare. - invalid_arguments
Scan <id> is not a Completed scan; use list_scans to find a Completed scanId.(base) - invalid_arguments
Base scan '<base>' must be older than compare scan '<compare>'. - not_found
… The scan holds no entities in your data scope.(a scan holds no entities in your data scope)
Notes
- Attribution
reasonvalues: "Requires change_tracking.read or activity_log.read permission"; "time window must not exceed 366 days" (scans more than 366 days apart); "Not requested (includeAttribution is false)." - Not visible: changes after
compareScan.scanTimestamp, and changes made and reverted between the two scans. rbacAssignmentsinchangedAreasdoes not say which assignments changed; useget_activity_log.- Before/after values and properties are never returned.
- Results are reused for 2 minutes per scan pair, so the first call on a very large tenant can be slow and later pages are fast.
- Do not pass
latestasbaseScanId.
Example arguments
{ "since": "2026-10-01T00:00:00Z", "changeTypes": ["deleted"] }get_changes_summary
Permission: View Resource Changes
Supporting evidence: totals of raw Azure Resource Graph change events (Create, Update, Delete) in a time window.
Arguments
| Name | Required | Format / values | Default | Notes |
|---|---|---|---|---|
start | yes | ISO 8601 timestamp | Inclusive, whole seconds; no time zone = UTC | |
end | yes | ISO 8601 timestamp | Inclusive, whole seconds; at most 366 days after start | |
subscriptionId | no | GUID | ||
resourceGroupName | no | name | Case-insensitive; requires subscriptionId | |
limit, offset | no | paging | 50, 0 | Page the subscription list |
Output
| Field | Meaning |
|---|---|
window | { start, end } as applied (start …:SS.0000000Z, end …:SSZ) |
totalChanges | Change events |
changedEntityCount | Entities with at least one change |
changeTypeCounts | Counts per Create / Update / Delete |
topChangedBy | Top 10 { changedBy, changeCount } |
subscriptions | { items, totalCount, offset, nextOffset, truncated }; items busiest first: { subscriptionId, subscriptionName, totalChanges, changedEntityCount, changeTypeCounts, link } (management group changes group under a null subscription) |
link | Resource Changes page for the window |
Errors
- invalid_arguments
resourceGroupName requires subscriptionId to be specified start must be a valid ISO timestampend must be a valid ISO timestampstart must be before endtime window must not exceed 366 days
Notes
- Not filtered by StratoLens ignore rules; no role-assignment changes.
Example arguments
{ "start": "2026-10-12T00:00:00Z", "end": "2026-10-19T00:00:00Z" }get_changes
Permission: View Resource Changes
Supporting evidence: individual raw Resource Graph change events, newest first.
Arguments
| Name | Required | Format / values | Default | Notes |
|---|---|---|---|---|
start, end | yes | as get_changes_summary | ||
subscriptionId | no | GUID | ||
resourceGroupName | no | name | Case-insensitive; does not require subscriptionId here | |
resourceName | no | name | Case-insensitive | |
resourceId | no | full ARM ID | Case-insensitive | |
changeTypes | no | array of Create, Update, Delete | all | |
limit, offset | no | paging | 50, 0 |
Output: window plus paging fields and items:
| Field | Meaning |
|---|---|
changeId, changeTime, changeType | The event |
resourceId, resourceName, resourceType, entityType | The entity |
subscriptionId, subscriptionName, resourceGroup | Hierarchy, or null |
changedBy, changedByType | Who made it (display name when known), or null |
changedPropertyCount, changedProperties | Number of changed properties and the first 5 paths |
link | Resource Changes page for the entity and window |
Errors
start must be a valid ISO timestampend must be a valid ISO timestampstart must be before endtime window must not exceed 366 days
Notes
- Before and after values are not returned; open
link. - Every page reloads the whole window, so a year-long window on a busy tenant can time out. Use
get_changes_summaryfirst and narrow the window.
Example arguments
{ "start": "2026-10-18T00:00:00Z", "end": "2026-10-19T00:00:00Z", "changeTypes": ["Delete"] }get_activity_log
Permission: View Azure Activity Logs
Azure Activity Log operations that changed something, newest first, including role assignment changes.
Arguments
| Name | Required | Format / values | Default | Notes |
|---|---|---|---|---|
start, end | yes | as get_changes_summary | ||
callers | no | array, at most 50 | UPN, service principal object or app ID, or a callerDisplayName value; case-insensitive exact match | |
resourceIds | no | array of ARM IDs, at most 50 | Prefix match, so child resources match too; case-insensitive | |
subscriptionIds | no | array of GUIDs, at most 50 | ||
includeFailedOperations | no | boolean | true | |
limit, offset | no | paging | 50, 0 |
Output: window plus paging fields and items:
| Field | Meaning |
|---|---|
timestamp, operationName, operationType, status | The operation |
caller, callerDisplayName | Who ran it |
resourceId, resourceName, resourceType, subscriptionId, resourceGroup | Target |
changeSummary | Short description, or null |
roleAssignment | Role assignment operations: { principalDisplayName, principalType, principalId, roleDefinitionName, roleDefinitionId, scope }; null on other rows |
correlationId | Azure correlation ID |
link | Activity Explorer for the subscription and window; null for management group operations |
Errors
start must be a valid ISO timestampend must be a valid ISO timestampstart must be before endtime window must not exceed 366 days
Notes
- For one person's activity, pass
callersrather than paging the whole window. - Rows with an identical timestamp have no guaranteed order, so paging can rarely repeat or skip one such row at a page edge.
- Deep offsets cost more.
Example arguments
{ "start": "2026-10-01T00:00:00Z", "end": "2026-10-19T00:00:00Z", "callers": ["alex@contoso.com"] }Security and Policy
get_security_findings
Permission: View Resources
StratoLens security findings (insecure configurations, public exposure), most severe first, matching the Security Findings page. Reads the latest scan's findings.
Arguments
| Name | Required | Format / values | Default |
|---|---|---|---|
severities | no | array of Critical, High, Medium, Low | all |
categories | no | array of PublicExposure, InsecureConfiguration, Hardening, Identity, Governance | all |
subscriptionId | no | GUID | |
includeHidden | no | boolean | false |
limit, offset | no | paging | 50, 0 |
Output: lastUpdatedScanId, lastUpdatedAt, bySeverity and byCategory (after the subscription and hidden filters, before the severity and category filters), hiddenCount, paging fields, link, and items:
| Field | Meaning |
|---|---|
checkId, checkTitle, category, severity | The check |
resourceId, resourceName, resourceType, subscriptionId, subscriptionName, resourceGroup | Affected resource |
instanceLabel | Which instance on the resource, or null |
currentValue, expectedValue, recommendation | What was found and the fix |
firstDetectedAt, isNew | First detection; isNew = within the last 7 days |
privateEndpointBypass | Finding concerns a private-endpoint bypass |
hidden | Hidden by an admin |
link | Security Findings page with the finding's entity selected |
Notes
- Order: severity, then New first, then private-endpoint bypass first. A finding can stop being New during paging and move.
Example arguments
{ "severities": ["Critical", "High"] }get_defender_alerts_summary
Permission: View Defender Assessmentsaccepts scanId=latest
Microsoft Defender for Cloud alerts present at a scan, with per-subscription rows. Arguments: scanId (scan ID or latest, default latest completed scan), limit, offset (page the subscription list).
Output
| Field | Meaning |
|---|---|
scanId | Scan read |
totalAlerts | All alerts |
openAlerts | Active or InProgress and not hidden (the Defender Alerts page's default view) |
severityCounts | By High, Medium, Low, Informational |
statusCounts | By Active, InProgress, Resolved, Dismissed |
openBySeverity | Open alerts per severity |
hiddenCount | Hidden by an admin |
subscriptions | Paged; items most open first: { subscriptionId, subscriptionName, totalAlerts, openAlerts, severityCounts, statusCounts, link } |
link | Defender Alerts page at the scan |
Errors
- Scan ID errors (see Conventions)
Example arguments
{}get_policy_compliance_summary
Permission: View Policy Dataaccepts scanId=latest
Azure Policy compliance at a scan. Counts are policy evaluations (one per resource and policy). Arguments: scanId (scan ID or latest, default latest completed scan), limit, offset (page the subscription list).
Output
| Field | Meaning |
|---|---|
scanId | Scan read |
totalEvaluations | Evaluations |
complianceStateCounts | By Compliant, NonCompliant, Exempt, Conflict, Unknown |
evaluatedEntityCount | Entities evaluated |
nonCompliantEntityCount | Entities with at least one NonCompliant evaluation |
topNonCompliantAssignments | Top 10 { assignmentId, displayName, policyDefinitionDisplayName, scope, nonCompliantCount, link } |
subscriptions | Paged; items most NonCompliant first: { subscriptionId, subscriptionName, totalEvaluations, nonCompliantEntityCount, complianceStateCounts, link } |
link | Policy Compliance page at the scan |
Errors
- Scan ID errors (see Conventions)
Example arguments
{ "limit": 10 }Cost and Savings
get_cost_summary
Permission: View Costs
Spend for a period: totals per currency, per-subscription rows and the top 10 resources.
Arguments
| Name | Required | Format / values | Default | Notes |
|---|---|---|---|---|
startDate | no | YYYY-MM-DD | current month to date | Used only together with endDate |
endDate | no | YYYY-MM-DD, inclusive | current month to date | Used only together with startDate; at most 366 days span |
limit, offset | no | paging | 50, 0 | Page the subscription list |
Output
| Field | Meaning |
|---|---|
period | { type: "range", startDate, endDate } or { type: "currentMonthToDate", month } |
totals | One row per currency: { currency, cost, amortizedCost }; currencies are never added together |
resourceCount | Resources with cost |
subscriptions | Paged; items keyed by subscription and currency, most expensive first: { subscriptionId, subscriptionName, currency, cost, amortizedCost, resourceCount, link } |
topResources | Top 10 by cost: { resourceId, resourceName, resourceType, subscriptionId, subscriptionName, resourceGroup, currency, cost, amortizedCost, link } (not paged) |
link | Cost Explorer for the same period |
Errors are checked only when both dates are given.
Errors
- invalid_arguments
Invalid date format. Use YYYY-MM-DD format. - invalid_arguments
startDate must not be after endDate - invalid_arguments
Invalid calendar date. - invalid_arguments
Date range exceeds maximum supported span of 366 days
Notes
- If only one date is given it is ignored and the default period applies;
periodsays which was used. - Amortized cost spreads reservation and savings plan purchases over the resources they cover.
- Costs are rounded to two decimals.
Example arguments
{ "startDate": "2026-09-01", "endDate": "2026-09-30" }get_cost_anomalies
Permission: View Costs
Cost anomalies (a day whose cost moved sharply from the 30-day baseline), largest absolute change first, matching the Anomaly Detection page defaults.
Arguments
| Name | Required | Format / values | Default | Notes |
|---|---|---|---|---|
timeWindowDays | no | 7, 30, 60, 90 | 30 | Anomalies on or after this many days ago |
minPercentChange | no | number ≥ 0 | 10 | Minimum absolute change in percent |
minCostChange | no | number ≥ 0 | 1 | Minimum absolute change in the billing currency |
direction | no | increase, decrease | both | |
subscriptionId | no | GUID | ||
includeHidden | no | boolean | false | |
includeOneTimeCharges | no | boolean | false | Reservation purchases, refunds, Marketplace and similar |
limit, offset | no | paging | 50, 0 |
Output: lastUpdatedScanId, lastUpdatedAt, resourcesAnalyzed, fromDate, bySeverity { high, medium, low } and byDirection { increase, decrease } (every filter except direction), hiddenResourceCount, paging fields, link, and one item per anomaly:
| Field | Meaning |
|---|---|
anomalyDate, anomalyType, severity, direction | anomalyType spike or pattern-shift; severity high / medium / low |
baselineCost, anomalyCost, costChange, percentChange, currency | Amounts (two decimals; percent one decimal) |
description | Text description |
resourceId, resourceName, resourceType, entityType, subscriptionId, subscriptionName, resourceGroup | Affected resource |
oneTimeCharge, hidden | Flags |
link | Anomaly Detection page with the resource selected and the same filters |
Example arguments
{ "timeWindowDays": 7, "direction": "increase" }get_orphaned_resources
Permission: View Resources
Orphaned, idle and empty resources, highest monthly cost first, matching the Orphaned Resources page.
Arguments
| Name | Required | Format / values | Default | Notes |
|---|---|---|---|---|
recommendationTypes | no | array of type strings, e.g. orphaned_managed_disk, orphaned_public_ip, idle_vm, stopped_vm_not_deallocated | all | byRecommendationType lists the types present |
subscriptionId | no | GUID | ||
metricsTimeframeDays | no | 1, 7, 14, 30, 60 | 30 | For activity-based checks, how long without activity counts as idle |
includeHidden | no | boolean | false | |
limit, offset | no | paging | 50, 0 |
Output: lastUpdatedScanId, lastUpdatedAt, totals { count, monthlyCost, annualCost, currencies } (every match), byRecommendationType ({ <type>: { count, monthlyCost } }, before the type filter), hiddenCount, paging fields, link, and items:
| Field | Meaning |
|---|---|
resourceId, resourceName, resourceType, entityType, subscriptionId, subscriptionName, resourceGroup | The resource |
recommendationType | e.g. orphaned_managed_disk |
status | Unattached, Idle, Stopped (Not Deallocated), Stale or Empty |
orphanedDays, orphanedSince | Age (calculated live) and start |
monthlyCost, annualCost, currency | Estimated cost in the resource's billing currency, or null |
detectionMethod, reason, lastMetricDate | inventory or metrics, the reason, and last activity date |
hidden | Hidden by an admin |
link | Orphaned Resources page with the resource selected |
Example arguments
{ "recommendationTypes": ["orphaned_managed_disk"] }get_sizing_recommendations
Permission: View Resources
VM and managed disk right-sizing, highest monthly savings first, as the Sizing page shows before any toggle is changed.
Arguments
| Name | Required | Format / values | Default | Notes |
|---|---|---|---|---|
types | no | array of vm (VMs and scale sets), disk (managed disks) | both | |
subscriptionId | no | GUID | ||
minimumMonthlySavings | no | number ≥ 0, USD | 10 | 0 shows all |
includeHidden | no | boolean | false | |
limit, offset | no | paging | 50, 0 |
Output: currency (always USD), minimumMonthlySavings, totals { count, monthlySavings, annualSavings }, byType (per selected type: { count, monthlySavings, annualSavings, hiddenCount, lastUpdatedScanId, lastUpdatedAt }), hiddenCount, paging fields, link, and items:
| Field | Meaning |
|---|---|
type | vm or disk |
resourceId, resourceName, resourceType, subscriptionId, subscriptionName, resourceGroup | The resource |
current, recommended | Current and recommended size |
monthlySavings, annualSavings | Savings in the page's default view |
bestMonthlySavings | Savings with the page's optional toggles on (other families, Standard HDD, Premium SSD v2); can be higher |
severity | high, medium or low |
evidence | VM: { analysisWindowDays, cpu { average, p95, maximum }, memoryP95 } (memoryP95 null when not collected). Disk: { analysisWindowDays, iops, throughputMBps, vmDeallocatedDays }, iops and throughputMBps as { avgP95, peakP95, maximum } or null |
hidden | Hidden by an admin |
link | Sizing page with the resource selected and the savings threshold at 0 |
Notes
- Savings are USD list prices, not billed cost.
hiddenCountcounts hidden resources that would otherwise match, so it can differ from the page's hidden count.
Example arguments
{ "types": ["vm"], "minimumMonthlySavings": 50 }get_service_retirements
Permission: View Resources
Azure service retirements affecting your resources, soonest deadline first (undated last), matching the Service Retirements page.
Arguments
| Name | Required | Format / values | Default | Notes |
|---|---|---|---|---|
deadline | no | all, overdue, 90, 180, 365 | all | Day windows include overdue findings; undated findings match only all |
impacts | no | array of High, Medium, Low | all | Any selection leaves out serviceHealth findings (they have no impact) |
includeTypeLevel | no | boolean | false | Include resourceType matches |
includeUndated | no | boolean | false | Undated serviceHealth findings are always included |
includeHidden | no | boolean | false | |
subscriptionId | no | GUID | ||
limit, offset | no | paging | 50, 0 |
Output: lastUpdatedScanId, lastUpdatedAt, catalogEntryCount, byUrgency { overdue, next90, next180, later, undated } (every match), hiddenCount, paging fields, link, and items:
| Field | Meaning |
|---|---|
title, retirementDate, urgency | urgency is overdue, next90, next180, later or undated |
tier | advisorResource (Advisor confirmed the resource), resourceType (every resource of a retiring type, not confirmed per resource) or serviceHealth (a Service Health event for a subscription) |
category, impact | Category; impact High / Medium / Low or null |
resourceId, resourceName, resourceType, subscriptionId, subscriptionName, resourceGroup | Affected resource (resource fields null for serviceHealth) |
impactedResourceCount | serviceHealth: resources affected |
solution | Advisor solution text |
learnMoreLink | Migration guidance link (http or https only), or null |
hidden | Hidden by an admin |
link | Service Retirements page with the resource selected (serviceHealth findings and findings without a resource group: the subscription) |
Notes
catalogEntryCount0 ornullmeans the retirement catalog or scan has not run, so no findings is not an all-clear.hiddenCountcounts hidden findings that would otherwise match, so it can be lower than the page's.
Example arguments
{ "deadline": "90" }get_commitments
Permission: View Costs
Reservations and savings plans as the Commitments dashboard shows them.
Arguments
| Name | Required | Format / values | Default | Notes |
|---|---|---|---|---|
commitmentType | no | reservation, savingsPlan | both | |
subscriptionId | no | GUID | Commitments applied to it, or consuming it when the commitment has no applied subscriptions | |
utilizationWindow | no | 1, 7, 30 (strings) | 30 | Days; 1 and 7 exist only in partial or enhanced data tier |
maxUtilizationPercent | no | number 0-100 | At or below this; unknown utilization never matches | |
expiringWithinDays | no | integer 1-1900 | Expiring in 0 to N days; base-tier rows never match | |
includeExpired | no | boolean | false | Include expired and cancelled |
sortBy | no | utilization (ascending, unknown last), expiry (soonest first, unknown last), monthlyCost (highest first), wastage (highest first), name | utilization | Ties by name, then commitment ID |
limit, offset | no | paging | 50, 0 |
Output
| Field | Meaning |
|---|---|
dataTier | base, partial or enhanced |
partialTierMissingSide | Partial tier: reservations or savingsPlans (the side without detail), else null |
utilizationAvailable | false in base tier |
utilizationWindow, currency, lastUpdatedScanId, lastUpdatedAt | Context |
totals | { reservations, savingsPlans, monthlyCost, monthlyWastage, overallUtilizationPercent (one decimal, or null), unknownUtilizationCount, unknownCostCount } over every match |
nearestExpiry | { name, expiryDate, daysUntilExpiry } of the soonest active expiry among the matches (no window cap), or null (always null in base tier) |
items, paging fields, link | Rows below; link opens the dashboard with the same filters |
Each item: commitmentId, name, commitmentType, sku, reservedResourceType, meterCategory, quantity, commitmentAmountPerHour, location, instanceFlexibility, term, billingPlan, scope, appliedScopes, subscriptions [{ id, name }], billingScopeId, benefitStartDate, expiryDate, daysUntilExpiry (negative only with includeExpired), provisioningState, active, utilizationPercent (selected window), utilization { day1, day7, day30 }, utilizationTrend (UP, DOWN, STEADY), monthlyCost, monthlyWastage, currency, link. Any absent optional field is null.
Notes
- Base tier is inventory and spend only: no SKU, term, scope, expiry, utilization or wastage. An empty underutilized or expiring result in base tier is not an all-clear; the Permissions & Data Tier drawer on the Commitments page explains how to get more.
- Links open the dashboard filtered, not a selected row.
- Commitment charges over time belong to
get_cost_summary.
Example arguments
{ "maxUtilizationPercent": 80, "sortBy": "wastage" }get_commitment_recommendations
Permission: View Costs
Commitment coverage recommendations as the Commitment Coverage page shows them, largest monthly impact first.
Arguments
| Name | Required | Format / values | Default | Notes |
|---|---|---|---|---|
subTypes | no | array of purchase, underutilized, expiring | all | |
subscriptionId | no | GUID, tenant-wide or shared-scope | A GUID also matches alerts whose commitment applies to or consumes that subscription | |
includeHidden | no | boolean | false | |
includeResolved | no | boolean | false | Adds resolved |
limit, offset | no | paging | 50, 0 |
Output
| Field | Meaning |
|---|---|
dataTier, currency, lastUpdatedScanId, lastUpdatedAt | Context |
currentSettings | { utilizationThresholdPercent, utilizationEvaluationWindowDays, expiryWarningDays, recommendationLookbackDays }: today's scanner settings (defaults 85, 30, 90, 30), not necessarily what the last scan used |
totals | { purchase { count, estimatedSavingsPerMonth }, underutilized { count, estimatedWastagePerMonth }, expiring { count, monthlyCostAtRisk } } over every match |
hiddenCount | Hidden recommendations that would otherwise match |
items, paging fields, link | Rows below |
resolved | null unless includeResolved: { totalCount, totalMonthlySavings, truncated, singleScopeOnly: true, items }, items newest first, at most 200: { recommendationType, resolutionType, commitmentName, subscriptionId, subscriptionName, estimatedMonthlySavings, currency, originalMonthlyCost, resolvedAt, resolvedByScanId } |
Each item
| Field | Meaning |
|---|---|
recommendationId, subType | ID and purchase / underutilized / expiring |
scopeBucket | subscription, tenant-wide (Shared-scope purchase recommendations), shared-scope (alerts on commitments shared across subscriptions) or unknown |
subscriptionId, subscriptionName | The bucket's subscription (or the bucket ID) |
subscriptionIds, scope | Subscriptions and scope of the commitment, or null |
hidden | Hidden by an admin |
impact | { amount, kind }, kind savings (purchase), wastage (underutilized) or at-risk (expiring) |
currency | Currency |
purchase | Purchase rows: { benefitType, armSkuName, recommendedQuantity, recommendedCommitmentAmountPerHour, term, lookBackPeriodDays, location, reservedResourceType, estimatedSavingsPerMonth, savingsPercentage, coveragePercentage, estimatedOnDemandCost }; else null |
alert | Underutilized and expiring rows: { commitmentId, commitmentName, commitmentType, currentMonthlyCost, currentUtilizationPercent, utilizationThresholdPercent, evaluationWindowDays, estimatedWastagePerMonth, expiryDate, daysUntilExpiry }; else null |
link | Commitment Coverage page with the row's sub-type and subscription |
Notes
- Purchase recommendations exist in every data tier; underutilized and expiring alerts only in partial or enhanced tier, so none in base tier is not an all-clear.
resolvedignoressubTypes,subscriptionIdandincludeHidden, as the page's Show Resolved view does, and only ever holds single-subscription commitments. ItsresolutionTypeiscommitment_utilization_improvedorcommitment_renewed.- Expiring ties sort by fewest days left.
- For a GUID filter the link shows only that subscription's bucket, so it can omit shared-scope alerts the tool matched.
Example arguments
{ "subTypes": ["underutilized", "expiring"], "includeResolved": true }See also
Scripts that need the same data without an AI assistant can use the External API.