01 / Project overview
This project was developed in a personal SOC homelab. The objective was to let an AI agent investigate security telemetry in Microsoft Sentinel without hard-coding every investigative query into the workflow. n8n orchestrates the investigation, a locally hosted Qwen model reasons about the incident, and Azure MCP Server exposes Azure operations as callable tools.
Problem
A conventional automation can run known KQL queries but cannot easily select new lines of enquiry based on returned evidence.
Approach
Expose Azure Log Analytics through an MCP service that n8n can call as an AI Agent tool.
Result
The agent successfully executed Sentinel queries, inspected authentication and command events, and retried a failed query after correcting a field name.
02 / Architecture
The Azure MCP service and n8n run as containers on a shared Docker network named soc-mcp-net. n8n addresses the MCP service using Docker's internal DNS name, azure-mcp, on port 5000. The language model does not receive Azure credentials; the MCP service authenticates to Azure on its behalf.
03 / Components and prerequisites
| Component | Role | Observed configuration |
|---|---|---|
| n8n | Workflow and AI Agent orchestration | Version 2.43.4; Docker Compose |
| Azure MCP Server | Authenticated Azure tool interface | Version 3.0.0; service azure-mcp |
| Docker network | Container-to-container communication | soc-mcp-net |
| Microsoft Entra ID | Non-interactive service authentication | OAuth2 client credentials |
| Microsoft Sentinel | Security investigation data | Log Analytics workspace sentinel |
| Ollama | Local model serving | Qwen3:14b used in later tests |
The setup required an Azure subscription, a Log Analytics workspace, an Entra application/service principal with suitable workspace query permissions, Docker and Docker Compose, and an n8n installation with an AI Agent and MCP Client Tool.
04 / Configure Azure authentication
Step 1 — Register an application
Configure a Microsoft Entra application for service-to-service authentication. Record its tenant ID and application (client) ID, then create a client secret. The Azure MCP container uses these values to acquire tokens using the client credentials flow.
Step 2 — Assign least-privilege access
Grant the service principal the permissions required to query the target Log Analytics workspace. Prefer workspace-scoped, read-only access for this investigation use case. Avoid broad subscription-level roles unless a specific MCP operation requires them.
Step 3 — Supply secrets to the container
Keep credentials out of the Compose file and version control. The environment file below is a redacted configuration pattern, not a verbatim export of the deployed environment:
# Illustrative .env pattern — match variable names to the image documentation
AZURE_TENANT_ID=<tenant-id>
AZURE_CLIENT_ID=<application-client-id>
AZURE_CLIENT_SECRET=<client-secret>
AZURE_SUBSCRIPTION_ID=<subscription-id>05 / Deploy the MCP container
The MCP service was deployed as a Docker container alongside an existing n8n Docker Compose installation. Both services were connected to the shared soc-mcp-net network. The service was reachable inside Docker at:
http://azure-mcp:5000/The following snippet documents the networking arrangement without inventing the exact image reference, startup command, or environment-variable mapping used during installation:
# Illustrative Docker Compose networking fragment
services:
azure-mcp:
# image: <verified Azure MCP Server 3.0.0 image reference>
# environment: <image-specific Azure credential variables>
networks:
- soc-mcp-net
n8n:
# Existing working n8n service configuration retained
networks:
- soc-mcp-net
networks:
soc-mcp-net:
external: true # Only if the network was created separatelyBecause the original Compose file and exact image startup arguments were not preserved in the available project notes, this is intentionally a topology example rather than a copy-and-paste deployment manifest. The important verified details are the running Azure MCP Server 3.0.0 service, the shared network, and the internal HTTP endpoint.
To inspect a deployment without changing it:
docker ps
docker network inspect soc-mcp-net
docker logs --tail 100 azure-mcpCheck the actual Compose service and container names before using the commands.
06 / Connect n8n to Azure MCP
Step 1 — Add an MCP Client Tool
Within the n8n AI Agent workflow, an MCP Client Tool was connected to the Azure MCP endpoint using the HTTP Streamable transport. The endpoint was configured as http://azure-mcp:5000/. The AI Agent could then discover and invoke supported Azure operations.
Step 2 — Select the Log Analytics query operation
The operation used for Sentinel investigations was monitor_workspace_log_query. The tool was exposed to the agent as MCP_Client_monitor. Its parameters included the workspace, underlying table, KQL query, subscription, lookback hours, and result limit.
{
"command": "monitor_workspace_log_query",
"parameters": {
"workspace": "sentinel",
"subscription": "<subscription-id>",
"table": "cowrie_JSON_CL",
"query": "Cowrie_Normalized | take 5",
"hours": 168,
"limit": 100
}
}table parameter remains cowrie_JSON_CL, while the KQL itself references the saved function Cowrie_Normalized. Mixing these up caused early agent-generated queries to fail because SourceIp is created by the function, not stored as a physical column in the raw table.Step 3 — Confirm tool execution
The n8n MCP Client Tool's standalone test interface was not consistently reliable during setup. Successful tool calls from within an AI Agent execution provided the meaningful end-to-end validation. The execution logs showed the exact query submitted and the response returned by Azure MCP.
07 / Validate access with a read-only KQL query
The Cowrie honeypot sends events to the physical cowrie_JSON_CL table. A saved KQL function, Cowrie_Normalized, parses the RawData JSON into typed fields such as SourceIp, EventID, Session, Username and Input.
Cowrie_Normalized
| where TimeGenerated >= datetime(2026-10-17T15:00:00Z)
| where TimeGenerated <= datetime(2026-10-17T18:00:00Z)
| where SourceIp == "45.148.10.240"
| summarize
TotalEvents = count(),
DistinctSessions = dcount(Session),
FailedLogins = countif(EventID == "cowrie.login.failed"),
SuccessfulLogins = countif(EventID == "cowrie.login.success"),
Commands = countif(EventID == "cowrie.command.input")This query was independently checked in Sentinel and used as a baseline for agent testing. The verified result was 526 events, 98 distinct sessions, 84 failed logins, 13 successful logins and 13 command events for the test IP and window.
A separate command investigation retrieved the observed command /bin/./uname -s -v -n -r -m 13 times. This allowed direct comparison between actual Sentinel evidence and the model's generated report.
08 / Troubleshooting and lessons learned
| Issue | Observed cause / lesson | Resolution |
|---|---|---|
Unknown SourceIp column | The model queried cowrie_JSON_CL directly instead of the saved parsing function. | Clarified the distinction between the MCP table argument and the KQL source. |
| Invalid KQL aggregation | Model-generated sum(if(...)) failed with an unknown-function error. | Used supported countif() aggregations and supplied known-good query patterns. |
Unknown User column | The model guessed a column that was not exposed by Cowrie_Normalized. | The agent inspected the error and retried successfully with Username. |
| Invented commands and counts | A plausible SOC report was not necessarily grounded in tool results. | Compared MCP execution logs with Sentinel; identified the need for a separate evidence-validation stage. |
| Missing incident context | The model substituted an arbitrary IP and historical dates when no real incident values were supplied. | Passed IP and UTC timestamps explicitly from n8n into the agent's user prompt. |
Tool invocation is not the same as analytical reliability. In one later test, Azure MCP returned 23 events and two command-input records, including SSH configuration/key-related commands, while the agent's final report incorrectly claimed there were only two events and no commands. This directly motivated plans for deterministic evidence validation before publishing SOC reports.
09 / Security design
Credential isolation
Azure credentials reside with the MCP service, not in the local model prompt or the Discord report.
Read-only investigation
Limit the service principal and agent to required query operations wherever practical.
Network boundary
Use the private Docker network for n8n-to-MCP communication; avoid exposing the MCP endpoint publicly without additional controls.
Auditability
Retain executed KQL, tool errors, responses, incident parameters and generated reports for review.
The design should also treat log data as untrusted input: honeypot usernames, command strings and other attacker-controlled fields must not be interpreted as instructions to the agent. Avoid publishing captured credentials or sensitive content in reports.
10 / Outcome and next steps
The deployment successfully established an authenticated Azure MCP tool interface between n8n and Microsoft Sentinel. A local Qwen3:14b model could execute real KQL queries, perform conditional follow-up investigations and recover from certain schema errors. The project also exposed a critical engineering requirement: the final narrative must be validated against the actual tool outputs.
The next planned enhancement is an n8n evidence-validation stage that compares model-generated findings with recorded MCP results before the investigation report is delivered to Discord. This keeps the agent flexible while preventing unsupported claims from being presented as confirmed SOC evidence.