Grounded Kql
FreeNot checkedAn MCP server that answers network troubleshooting questions ("why can't A reach B?") with deterministic, pre-shaped KQL against Azure Log Analytics — the agent
About
An MCP server that answers network troubleshooting questions ("why can't A reach B?") with deterministic, pre-shaped KQL against Azure Log Analytics — the agent picks typed tools and fills parameters, it never writes KQL itself.
README
An MCP server that answers network questions with deterministic, pre-shaped KQL against Azure Log Analytics custom tables — correlating firewall, VNet flow, on-premises Cisco IOS and Cisco ISE logs to answer the question an engineer actually asks: why can't A reach B?
The agent never writes KQL. It picks a tool and fills typed parameters; the server renders the query. That is the whole design, and the reason this is safe to point at a production workspace.
Runs with no Azure dependency at all (stdlib only), and against a real Log Analytics workspace by flipping one environment variable.
Why it is built this way
The tool catalog is the authorization surface. There is no free-form KQL
entry point. The agent picks a tool and fills typed parameters; src/kqlmcp/query.py
renders the query. That is what keeps the blast radius of the server's identity
bounded by tools.py rather than by whatever the model decides to write — and
it is the argument that matters in a security review.
Two backends, one tool contract. local runs the same tool semantics over
generated CSVs in stdlib SQLite; azure runs the rendered KQL against a real
workspace. Both return the KQL, so a local demo shows exactly the query that
would run in production.
Credential resolution is pluggable. Today: application identity (managed
identity in Azure, az login locally) with Log Analytics Reader on one
workspace. The same tool contract accepts a per-user delegated credential for a
tenant-wide, RBAC-scoped deployment — see docs/design.md.
Quick start (no Azure needed)
python -m venv .venv && . .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e . # mcp is the only dependency
python data/generate_logs.py # writes data/out/*.csv
python smoke_test.py # all six scenarios should pass
Run the server:
python -m kqlmcp.server --transport stdio # Claude Desktop / VS Code
python -m kqlmcp.server --transport http --host 0.0.0.0 --port 8080 # any MCP client over HTTP
HTTP mode serves streamable HTTP at /mcp. SSE is deprecated in the MCP spec
and deliberately not supported.
Claude Desktop
claude_desktop_config.json:
{
"mcpServers": {
"kqlmcp": {
"command": "C:\\path\\to\\grounded-kql-mcp\\.venv\\Scripts\\python.exe",
"args": ["-m", "kqlmcp.server", "--transport", "stdio"],
"env": {
"PYTHONPATH": "C:\\path\\to\\grounded-kql-mcp\\src",
"KQLMCP_BACKEND": "local"
}
}
}
}
VS Code
.vscode/mcp.json:
{
"servers": {
"kqlmcp": {
"type": "stdio",
"command": "${workspaceFolder}/.venv/Scripts/python.exe",
"args": ["-m", "kqlmcp.server", "--transport", "stdio"],
"env": { "PYTHONPATH": "${workspaceFolder}/src", "KQLMCP_BACKEND": "local" }
}
}
}
Switching to a real workspace
pwsh ./infra/provision.ps1 -ResourceGroup rg-kqlmcp -Location westeurope
pip install -e ".[azure]"
python ./infra/ingest.py # uses the values provision.ps1 printed
Then set KQLMCP_BACKEND=azure and KQLMCP_LA_WORKSPACE_ID=<customer id>.
Nothing else changes — same tools, same output shape.
The ingested data is perishable by design, and does not refresh itself.
data/generate_logs.py writes a rolling window ending at generation time,
Log Analytics rows don't move once ingested, and every tool — trace_connection
included — defaults to a 1440-minute (24h) window (D-017). Generate and ingest
right before you intend to query — data ingested more than a day earlier is
invisible to default time windows, not broken, just outside the window a
default query looks at. (The local backend doesn't have this property: its
container regenerates the dataset at every start, so it can't go stale. The
Azure path has no equivalent — there's nothing to regenerate at, since the
data already lives in the workspace.) When a hop's query does come back empty,
trace_connection now runs one bounded 30-day follow-up per miss to tell you
whether that means "never happened" or "outside your window" (D-017).
infra/ingest.py also refuses to run a second time against a workspace whose
tables already hold rows in that window — custom-table rows can't practically
be deleted, so a repeat run would silently double the dataset. Pass --append
to add anyway, or tear down and re-provision for a clean workspace.
Adding your own KQL queries
This project's tool catalog is deliberately closed: there is no free-form
KQL entry point, and every tool goes through the same typed pipeline
(QuerySpec → Filter/Agg → to_kql/to_sql in src/kqlmcp/query.py).
Adding a new query means adding a new tool function to src/kqlmcp/tools.py,
not writing raw KQL anywhere the agent can reach.
The fastest way to do that correctly is to fill in docs/new-query-template.yaml with:
- the table you want to query (must already be in
src/kqlmcp/schemas.py, or described as a new table if not) - the parameters the tool should accept, and which real column/operator each one maps to
- a reference KQL query you've already run by hand in Log Analytics, as proof the question is answerable against that data
Then hand the filled-in file to Claude Code (or another AI coding agent)
working in this repo, pointing it at CLAUDE.md's conventions and an
existing tool such as search_firewall_sessions as a pattern to follow.
Review the generated function before trusting it — in particular, confirm
every IP-typed parameter routes through _valid_ip() the same way
src_ip/dst_ip do elsewhere — then run
python data/generate_logs.py && python smoke_test.py
as a baseline check against the local/SQLite backend before pointing the new tool at a real workspace.
This only works for questions expressible as a single QuerySpec: a
filtered/projected row search, or a group-by aggregation, over one table. If
your reference query needs something the IR can't express — joins across
tables, window functions, multi-stage let statements — that's a sign it
needs new capability in query.py itself, not just a new tool.
The tools
| Tool | Answers |
|---|---|
describe_environment |
what topology and tables exist |
trace_connection |
why can't A reach B — correlates ISE → IOS → firewall → NIC |
search_flows |
did traffic leave / arrive at a NIC |
search_firewall_sessions |
what did the hub firewall decide, under which rule |
search_onprem_device_logs |
was it stopped on-premises before reaching Azure |
lookup_user_sessions |
who was using this on-premises IP |
top_denied_traffic |
ranked denies by rule, source, destination, application |
traffic_volume |
top talkers by bytes |
Layout
src/kqlmcp/
topology.py reference hub-and-spoke + on-prem estate (single source of truth)
schemas.py custom table definitions -> generator, SQLite DDL, Azure table JSON
query.py query IR + KQL and SQLite renderers
tools.py the grounded tool catalog
server.py MCP server (stdio + streamable HTTP)
backends/ local_sqlite.py | azure_la.py (pluggable credential)
data/generate_logs.py seeded synthetic logs with six planted scenarios
infra/ provision.ps1, ingest.py, emit_table_schemas.py
docs/ feasibility note, demo script, new-query-template.yaml
License
Apache-2.0. The synthetic data, topology and vendor log shapes are illustrative and do not describe any particular organisation's network.
Install Grounded Kql in Claude Desktop, Claude Code & Cursor
unyly install grounded-kqlInstalls into Claude Desktop, Claude Code, Cursor & VS Code — handles npx, uvx and build-from-source repos for you.
First time? Get the CLI: curl -fsSL https://unyly.org/install | sh
Or configure manually
Run in your terminal:
claude mcp add grounded-kql -- uvx --from git+https://github.com/TheGroundedHog/grounded-kql-mcp grounded-kql-mcpStep-by-step: how to install Grounded Kql
FAQ
Is Grounded Kql MCP free?
Yes, Grounded Kql MCP is free — one-click install via Unyly at no cost.
Does Grounded Kql need an API key?
No, Grounded Kql runs without API keys or environment variables.
Is Grounded Kql hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Grounded Kql in Claude Desktop, Claude Code or Cursor?
Open Grounded Kql on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
Opencode Omniroute Plugin
OpenCode plugin for the OmniRoute AI Gateway. Drives dynamic model discovery, /connect auth flow, and multi-instance OmniRoute providers via the official @openc
by GitHub ActionsRoblox Studio
Enables AI coding tools to control Roblox Studio for workspace exploration, instance manipulation, and script management. It provides tools for playtesting, sce
by paralovAntigravity Drawio
MCP server that enables AI assistants to create, parse, render, and validate Draw.io diagrams programmatically.
by S-SUJAN-SFetch
Web content fetching and conversion for efficient LLM usage.
AWS KB Retrieval
Retrieval from AWS Knowledge Base using Bedrock Agent Runtime.
by modelcontextprotocolSpring AI MCP Server
Provides auto-configuration for setting up an MCP server in Spring Boot applications.
llm-analysis-assistant
A very streamlined mcp client that supports calling and monitoring stdio/sse/streamableHttp, and can also view request responses through the /logs page. It also
by xuzexin-hzMCP-Agent
A simple, composable framework to build agents using Model Context Protocol by [LastMile AI](https://www.lastmileai.dev)
by lastmile-aiSpring AI MCP Client
Provides auto-configuration for MCP client functionality in Spring Boot applications.
mcp.natoma.ai
A Hosted MCP Platform to discover, install, manage and deploy MCP servers by [Natoma Labs](https://www.natoma.ai)
Compare Grounded Kql with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All ai MCPs
