azure-query-mcp
Based on the provided schema, this is a read-only MCP server for querying Azure Log Analytics and Azure Resource Graph with your Azure identity.
List Log Analytics workspaces in a subscription.
Search workspace tables and inspect a table's schema and retention metadata.
Run bounded, read-only KQL against one Log Analytics workspace with a required timespan.
Query Azure Resource Graph across explicit subscription IDs for resource inventory/configuration, with paging via
skipTokenandmaxResults.All exposed tools are read-only, idempotent, and non-destructive; the schema shows no write operations.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@azure-query-mcp Count resources by type in subscription 00000000-0000-4000-8000-000000000000"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Azure Query MCP
A read-only Model Context Protocol (MCP) server that lets an AI assistant query Azure Log Analytics, Microsoft Sentinel and Azure Resource Graph (ARG). Beyond raw KQL, it has purpose-built SOC tools for incident triage, entity timelines, ingestion health and threat hunting.
How it works
Runs on your machine. The server is the npm package
@akapetaniou/azure-query-mcp, started by your MCP client withnpxover stdio. There is nothing to clone, build or deploy into Azure.Uses your own Azure identity. It signs in through
DefaultAzureCredential(for example youraz loginsession), so it can read exactly what your Azure RBAC allows, and nothing is shared between users.Talks only to Azure. It connects to Microsoft Entra ID, Azure Resource Manager (including Resource Graph) and the Log Analytics query API of the selected cloud, plus the local managed identity endpoint when
DefaultAzureCredentialtries it.npxdownloads the package from the npm registry on first use. There is no telemetry, and query results are not stored.Covers two data planes. Log Analytics holds logs, events and security data; Resource Graph holds resource inventory and configuration.
Related MCP server: azure-compliance-mcp
Choose the right tool
Need | MCP tool | Data source |
Logs, events, telemetry, metrics, incidents, or time-series analysis |
| Log Analytics workspace |
Azure resource inventory, configuration, tags, policy, health, or cross-subscription discovery |
| Azure Resource Graph |
Find a workspace or inspect its tables and schemas |
| Azure Resource Manager |
Find and triage Microsoft Sentinel incidents |
|
|
Everything one account, IP or host did across log sources |
| Sign-in, audit, Office, endpoint, firewall and alert tables |
Is security data still arriving? Stale tables, volume drops, latency |
|
|
Run curated threat hunts |
| Built-in library plus your own JSON hunts |
ARG describes Azure control-plane resources and can be slightly delayed. It does not contain workspace log records. Log Analytics tools take the workspace ID (GUID) or its full ARM resource ID, and a timespan: an ISO 8601 duration back from now (P7D) or an absolute interval (2026-10-01T00:00:00Z/2026-10-02T00:00:00Z).
Security properties
All tools are marked read-only, non-destructive, and idempotent.
ARG calls the selected cloud's fixed Resource Manager endpoint with API version
2024-04-01; users cannot control the URL.ARG requires explicit subscription scope and caps each page at 1,000 rows and 5 MiB.
Log Analytics requires bounded KQL and caps output at 1,000 rows and 2,000 characters per cell.
The read-only query policy ignores string literals and comments, so hunting filters such as
where OperationName has "delete"are allowed while real management commands,setstatements andexternaldataare rejected. Malformed (unterminated) strings fail closed.Each Log Analytics query reads only the workspace it is sent to. The cross-resource functions
workspace(),app(),resource(),adx(),arg(),cluster()anddatabase()are rejected unlessAZURE_QUERY_MCP_ALLOW_CROSS_RESOURCE=true. Azure RBAC still applies either way; this keeps a query from reaching data the user did not point it at.evaluateis limited to plugins that only reshape returned rows:autocluster,bag_unpack,basket,diffpatterns,ipv4_lookup,ipv6_lookup,narrow,pivotandpreview. Plugins that reach outside the workspace (http_request,sql_request,python,r, ...) are blocked.Releases are published from GitHub Actions with npm provenance, so each package version is traceable to the commit and workflow that built it.
Requests time out, a cancelled tool call aborts its Azure requests, and access tokens are never logged.
When Azure rejects a query (HTTP 400), the tool returns the service's explanation, such as
SEM0100: Failed to resolve column, so the client can fix the query. Every other Azure failure returns only the HTTP status, the Azure error code and a fix hint, never the upstream message, which can contain tenant IDs, object IDs and scopes.Authentication and authorization are delegated to Microsoft Entra ID and Azure RBAC.
See SECURITY.md before production deployment.
Prerequisites
Node.js 22 or later (provides
npx)Azure CLI, for signing in interactively with
az login, or another identity thatDefaultAzureCredentialsupports, such as a managed identityAzure RBAC on the subscriptions and workspaces to be queried, and nothing more
Recommended least-privilege roles:
Capability | Suggested role and scope |
Query ARG and discover workspaces |
|
Read Log Analytics schemas and data, and use the SOC tools |
|
Custom roles can be narrower. The effective permissions must include the relevant Azure Resource Manager read operations and Log Analytics query access.
Quick start
Sign in to the tenant that contains the target subscriptions and workspaces.
az login --tenant <tenant-id> az account list --output tableAdd the server to your MCP client.
Claude Code (recommended): install the plugin, which adds the MCP server and the agent skill in one step. In Claude Code v2.1.275 or later:
/plugin install azure-query --marketplace kapetanios55/azure-query-mcpOn earlier versions, add the marketplace first, then install from it:
/plugin marketplace add kapetanios55/azure-query-mcp /plugin install azure-query@azure-query-mcpClaude Code asks for optional settings: the Entra tenant ID (leave empty to use your
az logintenant), a hunting-query folder, the Azure cloud, and whether to allow cross-resource queries (see Configuration). Change them later with/plugin configure azure-query@azure-query-mcp. Each plugin version runs the exact npm release it was tested with; get new releases with/plugin update azure-query@azure-query-mcp.To add only the MCP server, without the skill:
claude mcp add azure-query --env AZURE_TENANT_ID=<tenant-id> -- npx -y @akapetaniou/azure-query-mcp@0.6.1VS Code (
.vscode/mcp.json):{ "inputs": [ { "id": "azureTenantId", "type": "promptString", "description": "Microsoft Entra tenant ID" } ], "servers": { "azure-query": { "type": "stdio", "command": "npx", "args": ["-y", "@akapetaniou/azure-query-mcp@0.6.1"], "env": { "AZURE_TENANT_ID": "${input:azureTenantId}" } } } }Claude Desktop (
claude_desktop_config.json) and Cursor (~/.cursor/mcp.json):{ "mcpServers": { "azure-query": { "command": "npx", "args": ["-y", "@akapetaniou/azure-query-mcp@0.6.1"], "env": { "AZURE_TENANT_ID": "<tenant-id>" } } } }These examples pin the version so every machine runs the same release. Change
@0.6.1to the latest version when you upgrade. Without a version,npxkeeps using whichever release it cached first.Verify both paths with prompts such as:
List the Log Analytics workspaces in subscription <subscription-id>. Show the schema of the Heartbeat table in workspace <workspace-resource-id>. Use Azure Resource Graph to count resources by type in subscription <subscription-id>.
For production, use managed identity or workload identity. Avoid client secrets when the hosting platform supports federation.
Never place AZURE_CLIENT_SECRET directly in a committed MCP configuration. Use the host's secret store or managed identity.
Configuration
All settings are environment variables on the MCP server process. The Claude Code plugin sets the first four from its settings.
Variable | Default | Effect |
| Tenant of the signed-in identity | Microsoft Entra tenant to authenticate against. |
| None | Folder of your own hunting queries; see Your own hunting queries. |
|
|
|
|
|
|
| All credentials |
|
Azure Government and Azure China
Set AZURE_QUERY_MCP_CLOUD and point Azure CLI at the same cloud before signing in, because the CLI credential uses the CLI's own cloud setting:
az cloud set --name AzureUSGovernment
az login --tenant <tenant-id>Resource Manager, Resource Graph, Log Analytics and the token authority then all use that cloud's endpoints.
Agent skill
plugin/skills/azure-query is an Agent Skills skill that teaches an agent when and how to use this server. It covers which tool fits which question, the two workspace IDs, the KQL rules the server enforces, what each error means, SOC workflows, and safety rules: treat log content as untrusted, stay in the requested scope, and pull only the data needed. Its reference files contain tested example queries for Log Analytics and Resource Graph.
The skill does not replace the MCP server; install both. In Claude Code the plugin installs both together. To add the skill by hand instead, copy the folder into your personal or project skills directory:
git clone --depth 1 https://github.com/kapetanios55/azure-query-mcp.git
cp -r azure-query-mcp/plugin/skills/azure-query ~/.claude/skills/ # or <project>/.claude/skills/Other agents that support Agent Skills load the same folder from their own skills directory. The test suite validates the skill's frontmatter, checks that it documents every tool and prompt, and runs each example query through the read-only policy (and, for Log Analytics, Microsoft's Kusto analyzer against the official table schemas).
SOC tools
These tools generate their own KQL, so the client does not need to know table schemas. Every generated query goes through the same read-only policy as query_workspace, results are capped at 1,000 rows, and all of them need only Log Analytics Reader. Incidents are read from the SecurityIncident and SecurityAlert tables, so Microsoft Sentinel must be enabled on the workspace for list_incidents and get_incident.
Tool | What it returns |
| Latest state of each incident, newest first, filterable by status and severity. |
| One incident with its alerts and the account, IP and host entities involved ( |
| A cross-source timeline for a UPN, IP or host, plus complete per-source event counts. Tables that are not ingested are skipped with |
| Per-table last-seen time, last-24-hour volume versus the previous daily average, |
| Built-in hunts (password spray, MFA fatigue, privileged role assignment, inbox forwarding rules, Office apps spawning shells) mapped to MITRE ATT&CK. |
Entity values are validated against strict UPN, IP and hostname formats before they are placed in a query, because the Log Analytics API has no parameter binding. Values containing quotes, backslashes or whitespace are rejected.
Your own hunting queries
Set AZURE_QUERY_MCP_HUNTING_DIR to a folder of JSON hunting queries in the SentinelCBContent format (id, name, description, tactics, techniques, query). Each file must pass the read-only policy; files that do not are reported by list_hunting_queries and not loaded. An unreadable directory is reported the same way and the built-in hunts still load.
"env": {
"AZURE_TENANT_ID": "<tenant-id>",
"AZURE_QUERY_MCP_HUNTING_DIR": "/path/to/SentinelCBContent/Hunting"
}Prompts
Prompt | Workflow |
|
|
|
|
Query correctness
The test suite parses and semantically analyses every built-in query with Microsoft's Kusto language service against a snapshot of the official Azure Monitor table schemas (test/fixtures/table-schemas.json, refreshed with node scripts/update-table-schemas.mjs). An unknown table, column or function fails CI.
Tools
query_azure_resources
Runs read-only ARG KQL against one or more explicitly supplied subscription IDs. The query must begin with an ARG table such as Resources, ResourceContainers, ResourceChanges or AuthorizationResources.
subscriptionIds: ["00000000-0000-4000-8000-000000000000"]
query: Resources | where type =~ 'microsoft.compute/virtualmachines' | project id, name, resourceGroup, location | order by name asc | limit 50
maxResults: 50Use the returned skipToken with the identical query and subscription scope to request another page.
query_workspace
Runs bounded read-only KQL against one Log Analytics workspace. A result-limiting operator and timespan are mandatory.
workspaceId: 00000000-0000-4000-8000-000000000000
query: Heartbeat | project TimeGenerated, Computer | take 50
timespan: PT1HworkspaceId also accepts the workspace's full ARM resource ID, which the server resolves to its workspace ID once. timespan also accepts an absolute interval, for example 2026-10-01T08:00:00Z/PT6H or 2026-10-01T00:00:00Z/2026-10-02T00:00:00Z; times need Z or an offset.
list_workspaces returns both IDs for each workspace in a subscription. search_tables and describe_table list a workspace's tables and their columns; use them before writing KQL against an unfamiliar table.
Log Analytics examples
The examples below use query_workspace. Supply the workspace and an explicit timespan with every request. Table availability depends on the workspace configuration.
Agent heartbeat recency
Prompt:
For the last 24 hours, show the 25 computers with the most recent heartbeat.KQL:
Heartbeat
| summarize LastHeartbeat=max(TimeGenerated) by Computer
| top 25 by LastHeartbeat descTimespan: P1D
Most common failed sign-ins
Prompt:
For the last seven days, show the 20 users with the most failed sign-ins.KQL:
SigninLogs
| where ResultType != "0"
| summarize FailedSignIns=count() by UserPrincipalName
| top 20 by FailedSignIns descTimespan: P7D
Recent Sentinel incidents
Prompt:
Show the 20 most recently updated Microsoft Sentinel incidents from the last seven days.KQL:
SecurityIncident
| summarize arg_max(TimeGenerated, *) by IncidentNumber
| project TimeGenerated, IncidentNumber, Title, Severity, Status, Owner
| top 20 by TimeGenerated descTimespan: P7D
Use search_tables when the table name is unknown, then use describe_table to retrieve the canonical column names and types before writing KQL.
Azure Resource Graph examples
The examples below use query_azure_resources and one or more explicit subscription IDs. ARG reports Azure control-plane state and can be slightly delayed.
Count resources by type
Resources
| summarize ResourceCount=count() by type
| top 25 by ResourceCount descList virtual machines
Resources
| where type =~ 'microsoft.compute/virtualmachines'
| project id, name, resourceGroup, subscriptionId, location
| order by name asc
| limit 100Find resources missing an owner tag
Resources
| where isempty(tags.owner)
| project id, name, type, resourceGroup, subscriptionId
| order by type asc, name asc
| limit 100Count NSGs with an inbound port allowed from the internet
Set targetPort to the port to investigate. This query handles a single destination port, destination port arrays, ranges such as 5000-5100, and the * wildcard. It also handles single and array source prefixes.
Resources
| where type =~ 'microsoft.network/networksecuritygroups'
| mv-expand rule = properties.securityRules
| where tostring(rule.properties.direction) =~ 'Inbound'
and tostring(rule.properties.access) =~ 'Allow'
| extend sourcePrefixes = iif(
array_length(rule.properties.sourceAddressPrefixes) > 0,
rule.properties.sourceAddressPrefixes,
pack_array(rule.properties.sourceAddressPrefix)
), destinationPorts = iif(
array_length(rule.properties.destinationPortRanges) > 0,
rule.properties.destinationPortRanges,
pack_array(rule.properties.destinationPortRange)
)
| mv-expand sourcePrefix = sourcePrefixes
| where tostring(sourcePrefix) in~ ('*', 'Internet', '0.0.0.0/0', '::/0')
| mv-expand destinationPort = destinationPorts
| extend portText=tostring(destinationPort), targetPort=22
| extend rangeStart=toint(split(portText, '-')[0]),
rangeEnd=toint(split(portText, '-')[1])
| where portText == '*'
or targetPort == toint(portText)
or (rangeStart <= targetPort and rangeEnd >= targetPort)
| summarize MatchingRules=count(),
NsgCount=dcount(id),
Nsgs=make_set(name, 100)This query shape was tested against Azure Resource Graph with both positive and zero-result ports.
Important: this identifies candidate exposure in configured custom NSG rules. It does not calculate effective packet reachability, rule priority conflicts, subnet and NIC associations, public IP presence, routes, Azure Firewall behavior, or application listeners. Treat the result as an investigation starting point, not proof that a service is reachable from the internet.
Troubleshooting
Symptom | Action |
Wrong-token-issuer or tenant error | Run |
| Verify Reader access at the requested subscription or resource scope. |
Workspace metadata works but queries fail | Verify Log Analytics Reader access to the workspace, and pass the workspace ID or resource ID from |
Table not found | Run |
| Check that the path exists and is readable by the user running the MCP client, then restart the server. |
| Query one workspace per call, or set |
| Use a duration ( |
Sign-in or 401 errors in Azure Government or Azure China | Check that |
Query rejected as unbounded | Add |
Server is not visible in VS Code | Check that |
| Only row-reshaping plugins are allowed; see Security properties for the list. |
Development
Run from source
To work on the server itself (needs Git), clone and build it, then point your MCP client at dist/index.js instead of npx:
git clone https://github.com/kapetanios55/azure-query-mcp.git
cd azure-query-mcp
npm ci
npm run check"azure-query": {
"type": "stdio",
"command": "node",
"args": ["/absolute/path/to/azure-query-mcp/dist/index.js"]
}Before a pull request, run the same checks as CI:
npm run check
npm audit --omit=devLive smoke test
With az login done and the server built, exercise every tool against a real workspace. Output contains only counts, statuses and timings.
node scripts/live-smoke-test.mjs . <subscription-id> <workspace-resource-id> <workspace-customer-id>Releasing
Bump
versioninpackage.json(for examplenpm version minor --no-git-tag-version) and update CHANGELOG.md. Set the same version inplugin/.claude-plugin/plugin.json, both itsversionand the@<version>pin in the MCP server'snpxarguments, and in the install examples of this README andplugin/README.md; the test suite fails until they all match. Plugin users receive the new server version only when the plugin version changes.Merge to
main, then start the release in one of two ways:Run it by hand: Actions > Release > Run workflow on
main, and type the version (for example0.6.0). The run fails unless it matchespackage.jsononmain. After publishing, it creates thev0.6.0tag on that commit.Push a tag:
git tag -a v0.6.0 <merge-commit> -m "Release 0.6.0" && git push origin v0.6.0.
The Release workflow checks the version against
package.json, runs the full check and audit, and publishes to npm with provenance using trusted publishing (no stored npm token). A version already on npm is skipped, and a manual run refuses to publish if the tag already exists on a different commit. Publish promptly after merging: until the new version is on npm, the plugin onmainpins a version npm doesn't have yet.
npm's trusted publisher for this package is set to the workflow file release.yml and the npm environment. Renaming either breaks publishing until the npm setting is updated to match.
References
The Resource Graph client follows the Microsoft Learn Azure Resource Graph REST API and Resources API reference.
License
Available Tools
11 toolscheck_ingestion_healthARead-onlyIdempotent
Check whether log data is still arriving: per-table last-seen time, 24h volume versus the previous daily average, stale tables, volume drops or spikes, and ingestion latency over the last hour.
Inputs: workspaceId (required), timespan, staleAfterHours.
describe_tableARead-onlyIdempotent
Get the canonical schema and retention metadata for a Log Analytics table.
Inputs: tableName (required), workspaceResourceId (required).
entity_timelineARead-onlyIdempotent
Build a cross-source activity timeline for one account (UPN), IP address or host: sign-ins, audit events, Office activity, endpoint telemetry, firewall logs and alerts. Tables that are not ingested are skipped automatically.
Inputs: entity (required), entityType (required), workspaceId (required), timespan, +1 more.
get_incidentARead-onlyIdempotent
Get one Microsoft Sentinel incident with its alerts and the account, IP and host entities involved. Returned pivotEntities can be passed directly to entity_timeline; unresolvedEntities lists entities recorded without a usable identifier (for example only a display name or SID).
Inputs: workspaceId (required), incidentNumber (required), timespan.
list_hunting_queriesARead-onlyIdempotent
List the available threat-hunting queries (built-in plus any loaded from AZURE_QUERY_MCP_HUNTING_DIR) with their MITRE ATT&CK tactics and techniques.
Inputs: no named parameters.
list_incidentsARead-onlyIdempotent
List Microsoft Sentinel incidents (latest state of each) from the SecurityIncident table, newest first. Use to find incidents to triage; then call get_incident for details.
Inputs: workspaceId (required), statuses, timespan, maxResults, +1 more.
list_workspacesARead-onlyIdempotent
List Azure Log Analytics workspaces accessible in a subscription.
Inputs: subscriptionId (required).
query_azure_resourcesARead-onlyIdempotent
Use for Azure resource inventory, configuration, tags, policy, health, and cross-subscription discovery through Azure Resource Graph. Do not use for telemetry, logs, events, or time-series analysis; use query_workspace for those.
Inputs: query (required), subscriptionIds (required), skipToken, maxResults.
query_workspaceARead-onlyIdempotent
Use for telemetry, logs, events, metrics, and time-series data stored in a Log Analytics workspace. Run bounded, read-only KQL against one workspace. Do not use for Azure resource inventory or configuration; use query_azure_resources for those.
Inputs: query (required), timespan (required), workspaceId (required).
run_hunting_queryARead-onlyIdempotent
Run one hunting query from list_hunting_queries against a workspace. Results are capped at 1,000 rows.
Inputs: queryId (required), workspaceId (required), timespan.
search_tablesARead-onlyIdempotent
List or search tables in a Log Analytics workspace using ARM metadata.
Inputs: workspaceResourceId (required), search.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 11 tools
Tools are cleanly partitioned by intent, and the two most conflict-prone tools (query_workspace vs query_azure_resources) explicitly exclude each other's use cases in their descriptions. Incident tools (list vs get) and table tools (search_tables vs describe_table) are clearly separated, and entity_timeline is framed as a cross-source pivot rather than a generic query.
Nearly all tools follow a verb_noun pattern (list_incidents, get_incident, run_hunting_query, check_ingestion_health, describe_table, search_tables). The lone deviation, entity_timeline, is a noun-only name but remains readable and unambiguous in context.
Eleven tools is well within the ideal range for a security/telemetry server, and each maps to a distinct stage of the workflow (discovery, querying, schema introspection, incident triage, hunting, health checks). No tool appears redundant or filler.
The read-only surface covers workspace/table discovery, KQL querying, resource graph inventory, ingestion health, incident retrieval, entity timelines, and hunting queries with good lifecycle coherence. The main gap is incident write-back — the description says list_incidents is for finding incidents to triage, but there is no update_incident to assign, comment, or close one.
Maintenance
Related MCP Connectors
Read-only MCP access to sessions, funnels, campaigns, errors, live visitors, and anomalies.
Read-only MCP access to a documented IT fleet: state, changes, posture. 15 tools.
Governed data discovery, exact queries, decisions, simulations, and runtime utilities over MCP.
Query your org's data in natural language — read-only MCP access to SQL, NoSQL, files & warehouses.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceEnables read-only access to SentinelOne's platform through MCP, allowing security investigations, threat hunting, and asset inventory queries via natural language.MIT
- AlicenseAqualityDmaintenanceEnables natural-language queries about Azure resource compliance, including VM compliance, patch status, orphaned RBAC, and infrastructure health, through read-only MCP tools.3MIT
- FlicenseNot gradedqualityBmaintenanceEnables read-only Kubernetes incident investigation through MCP tools for listing pods, describing resources, fetching logs, and searching runbooks.1-
- AlicenseNot gradedqualityDmaintenanceEnables read-only SQL querying and schema inspection across MSSQL, PostgreSQL, and MySQL databases via MCP tools.ISC