Build an Autonomous Phishing Triage Agent with Azure Logic Apps and MCP Servers
Azure Logic Apps Standard is moving toward agentic automation patterns, including preview support for exposing workflows as MCP servers and agent-style orchestration. This tutorial walks through a phishing triage reference architecture that checks URLs against VirusTotal, reads user risk scores from Microsoft Graph, and writes a structured verdict back to Microsoft Sentinel.

What We Are Building
Status note, June 2026: Microsoft documents preview support for configuring Logic Apps Standard workflows as remote MCP servers. Treat the autonomous phishing triage flow below as a reference architecture for agentic SOC automation, not as a guarantee that every tenant currently exposes the same designer labels or hosted MCP endpoints. Verify the current Logic Apps preview availability, region support, and connector names in Microsoft Learn before implementing in production.
Azure Logic Apps Standard can now participate in MCP-based agent workflows where an LLM calls approved tools instead of following only hardcoded branches. Unlike a traditional Logic Apps SOAR playbook where you wire every step explicitly, the agent reads a system prompt, examines the incoming alert, and determines what evidence it needs before rendering a verdict.
This tutorial builds a phishing triage agent end to end. Here is the complete flow:
Sentinel Automation Rule (webhook trigger)
→ Logic App HTTP trigger
→ Autonomous Agent
├── VirusTotal MCP: URL scan + file hash lookup
├── Microsoft Graph MCP: user risk score + risky sign-ins
└── Verdict: FALSE_POSITIVE | ESCALATE | AUTO_REMEDIATE
→ Sentinel incident comment
→ Analyst email (if ESCALATE or AUTO_REMEDIATE)There is no explicit branching, no hardcoded thresholds. The agent reads the evidence from its tool calls and decides.
Prerequisites
Before starting, have the following ready:
- Azure subscription with access to create Logic Apps Standard (Workflow Standard SKU). MCP server support applies to Standard workflows and is a preview capability. Consumption workflows are not the target architecture here.
- Microsoft Sentinel workspace with at least one mailbox connector ingesting phishing reports, or a simulated alert you can trigger manually.
- VirusTotal API key: the free tier (500 requests/day) is sufficient. Get one at virustotal.com.
- Entra ID app registration with these Microsoft Graph API permissions (application permissions, not delegated): User.Read.All, IdentityRiskyUser.Read.All, IdentityRiskEvent.Read.All
- Microsoft Sentinel Contributor access on your account.
Step 1: Create the Logic App
- In the Azure portal, search for Logic Apps and select Add.
- For Plan type, choose Standard (not Consumption). The autonomous agent workflow type is only available here.
- Choose your subscription, resource group, and a name like phishing-triage-agent.
- For Region, pick the same region as your Sentinel workspace to avoid cross-region data transfer.
- Under Hosting, select Workflow Standard with a WS1 SKU.
- Enable system-assigned managed identity under the Identity tab before creating. You will use this identity to authenticate to Microsoft Graph.
- Click Review + Create.
Once deployed, go to the Logic App resource and select Workflows in the left menu.
Step 2: Create the Autonomous Agent Workflow
- Click Add to create a new workflow.
- If your tenant exposes an Autonomous Agent or equivalent agent workflow option, select it. If it does not, build the same pattern by exposing Standard workflows as MCP tools and invoking them from your approved agent runtime.
- Name the workflow phishing-triage.
- In tenants with the agent designer, the workflow opens with an agent configuration block. In tenants without it, configure the system prompt and tool list in the external agent runtime that calls your Logic Apps MCP server.
- Add an HTTP Request trigger (search for "When a HTTP request is received") as the entry point.
Use this JSON schema for the request body:
{
"type": "object",
"properties": {
"incidentId": { "type": "string" },
"incidentTitle": { "type": "string" },
"severity": { "type": "string" },
"reportingUser": {
"type": "object",
"properties": {
"email": { "type": "string" },
"userId": { "type": "string" }
}
},
"extractedUrls": {
"type": "array",
"items": { "type": "string" }
},
"extractedHashes": {
"type": "array",
"items": { "type": "string" }
},
"emailSubject": { "type": "string" },
"senderEmail": { "type": "string" }
}
}This schema passes structured fields only, not raw email body text. Passing raw email body to an LLM agent is a prompt injection risk covered in the third article in this series.
Step 3: Write the Agent System Prompt
This is the core of the autonomous agent workflow. In the agent configuration panel, paste the following system prompt:
You are a SOC analyst triaging phishing reports for a corporate security team. You will receive a structured alert containing extracted indicators only: no raw email content.
You will receive:
- incidentId: the Sentinel incident identifier
- reportingUser: email and userId of the person who reported the phishing email
- extractedUrls: array of URLs found in the reported email
- extractedHashes: array of file attachment hashes (SHA256)
- emailSubject: subject line of the reported email
- senderEmail: sender address
Your job:
1. Check every URL in extractedUrls using the VirusTotal URL scan tool. Do not skip any URL even if the first result is clean.
2. Check every hash in extractedHashes using the VirusTotal file hash lookup tool.
3. Check the reportingUser's risk score using the Microsoft Graph user risk tool.
4. Check the reportingUser's recent risky sign-ins using the Microsoft Graph risky sign-ins tool.
5. Based on all findings, produce exactly one verdict:
- FALSE_POSITIVE: all indicators clean, user risk is none or low, no anomalous sign-ins
- ESCALATE: any ambiguous signal (low VirusTotal detection count, medium user risk, unusual sign-in location), or if the reporting user is in a high-value role (finance, executive, IT admin)
- AUTO_REMEDIATE: confirmed malicious indicator (VirusTotal detection count above 10/72) AND user showing active compromise signals (high risk score or risky sign-in in past 6 hours)
Output format: always return this exact JSON structure, nothing else:
{
"verdict": "FALSE_POSITIVE" | "ESCALATE" | "AUTO_REMEDIATE",
"confidence": <integer 0-100>,
"reasoning": "<2-3 sentences explaining your verdict based on specific findings>",
"indicators_checked": {
"urls_checked": <number>,
"hashes_checked": <number>,
"max_vt_detection_count": <number or null>,
"user_risk_score": "<none|low|medium|high>",
"risky_sign_ins_past_6h": <number>
},
"recommended_actions": ["<action 1>", "<action 2>"]
}
Never produce a verdict without completing all tool calls. If a tool call fails, note the failure in reasoning and base your verdict on the remaining evidence.Why this prompt structure works: each tool call is mandatory (prevents lazy short-circuiting on the first clean result), the output format is a strict schema (enables downstream validation before actions fire), and the verdict criteria are expressed as natural language heuristics rather than hardcoded thresholds. The agent can handle novel signal combinations that an explicit branching tree would miss.
Step 4: Connect the VirusTotal MCP Server
In the Logic Apps designer or your agent runtime, add VirusTotal as an MCP tool source.
Configuration:
- MCP Server URL: use the current VirusTotal MCP endpoint from VirusTotal documentation. Do not hardcode an endpoint from a tutorial into production; MCP endpoints and transports can change during preview.
- Authentication: API Key header. Store the key as a Logic Apps parameter (Settings > Parameters), not hardcoded. Reference it as @parameters('virusTotalApiKey').
- Tools to expose to the agent: url_scan, file_hash_lookup, ip_address_report. Limit exposed tools to what the agent actually needs; every additional tool expands the agent's action surface.
Once saved, the agent's reasoning context automatically includes the tool descriptions published by the MCP server. The agent reads these descriptions to decide when and how to call each tool: you do not wire up specific calls manually.
Step 5: Connect the Microsoft Graph MCP Server
Add a second MCP tool source for Microsoft Graph.
Configuration:
- MCP Server URL: point to your self-hosted Microsoft Graph MCP server or the current Microsoft-published endpoint if one is available in your tenant.
Note: Do not assume https://graph.microsoft.com/mcp is a hosted Graph MCP endpoint. In practice, many teams deploy an approved Microsoft Graph MCP server themselves, restrict it to a narrow tool list, and point the agent runtime to that internal endpoint. Check Microsoft's current Graph MCP documentation before deployment.
- Authentication: Managed Identity (recommended). In the Logic App's system-assigned managed identity, grant these Microsoft Graph application permissions via Entra ID admin consent: User.Read.All, IdentityRiskyUser.Read.All, IdentityRiskEvent.Read.All
Grant permissions via Azure CLI. For a Logic App's system-assigned managed identity, use the Graph API directly to assign app roles:
# Get the Logic App's system-assigned managed identity principal ID
MI_OBJECT_ID=$(az logic workflow show \
--name phishing-triage-agent \
--resource-group <your-rg> \
--query "identity.principalId" -o tsv)
# Get the Microsoft Graph service principal ID in your tenant
GRAPH_SP_ID=$(az ad sp show \
--id 00000003-0000-0000-c000-000000000000 \
--query id -o tsv)
# Find the app role ID for IdentityRiskyUser.Read.All
# Role ID: dc5007c0-2d7d-4c42-879c-2dab87571379
az rest \
--method POST \
--uri "https://graph.microsoft.com/v1.0/servicePrincipals/${MI_OBJECT_ID}/appRoleAssignments" \
--body "{\"principalId\": \"${MI_OBJECT_ID}\", \"resourceId\": \"${GRAPH_SP_ID}\", \"appRoleId\": \"dc5007c0-2d7d-4c42-879c-2dab87571379\"}"Alternatively, use the Entra ID portal: go to Enterprise Applications, search for your Logic App by name, select Permissions, then Add permission, and grant the three required Graph permissions from there. The portal path is simpler and less error-prone for one-time setup.
Tools to expose: get_user_risk_score, list_risky_sign_ins, get_user_profile. Do not expose write tools like dismiss_risky_user: the agent should read and recommend, not act on identity risk directly.
Step 6: Test with a Live Phishing Sample
Use a known-bad URL from URLhaus (urlhaus.abuse.ch): a public phishing feed of confirmed-malicious URLs safe to use for testing.
Construct a test request payload:
{
"incidentId": "sentinel-test-001",
"incidentTitle": "Phishing Email Reported by User",
"severity": "Medium",
"reportingUser": {
"email": "testuser@yourdomain.com",
"userId": "<a real user ID in your test tenant>"
},
"extractedUrls": ["<paste a URLhaus entry here>"],
"extractedHashes": [],
"emailSubject": "Urgent: Your account requires verification",
"senderEmail": "no-reply@suspicious-domain.example"
}Trigger via the Logic App portal: open the workflow run view, click Run Trigger > With Payload, paste the JSON above.
In the run history, expand each step to see the agent's reasoning trace: which tools it called, what arguments it passed, what each MCP server returned, and how it synthesized the findings into a verdict. This trace is your audit log.
Step 7: Reading the Output
A typical AUTO_REMEDIATE verdict from the agent looks like this:
{
"verdict": "AUTO_REMEDIATE",
"confidence": 91,
"reasoning": "The submitted URL matched 23 out of 72 VirusTotal engines as a phishing page targeting Microsoft credentials. The reporting user has a high risk score with two risky sign-ins in the past 4 hours from an IP address in a country outside their normal pattern. Taken together, these signals indicate an active credential-harvesting attempt against a potentially already-compromised account.",
"indicators_checked": {
"urls_checked": 1,
"hashes_checked": 0,
"max_vt_detection_count": 23,
"user_risk_score": "high",
"risky_sign_ins_past_6h": 2
},
"recommended_actions": [
"Revoke all active sessions for the reporting user via Entra ID",
"Block the sender domain in Exchange Online Protection",
"Open a Tier 2 investigation for lateral movement from this user account"
]
}After the agent step, add a Microsoft Sentinel connector action to post this as an incident comment. Map the verdict JSON fields to the comment body. For ESCALATE and AUTO_REMEDIATE verdicts, add a parallel branch that sends a notification email to your on-call queue.
Keep the recommended_actions as text in the Sentinel comment only. Do not wire up automated execution of these actions without a human approval gate: the architecture for that gate is covered in the third article in this series.
What to Build Next
[Article 2 in this series](/blog/azure-logic-apps-autonomous-agent-vs-soar-playbooks) shows how this agent compares to traditional Logic Apps SOAR playbooks, where the LLM reasoning loop is a genuine upgrade and where it is not, including a demo walkthrough of the agent's reasoning trace on an ambiguous alert. [Article 3](/blog/azure-logic-apps-autonomous-agent-threat-model-enterprise) covers the production threat model: prompt injection via phishing email content, MCP server trust boundaries, managed identity scoping, and a 12-item production readiness checklist to take to a security review board.
Frequently Asked Questions
What is the difference between the Azure Logic Apps MCP server preview and a traditional Logic Apps connector?
A traditional Logic Apps connector exposes a fixed schema where the workflow developer determines which operation to call and maps inputs explicitly at design time. An MCP server exposes tool descriptions in natural language that the LLM reads at runtime and uses to decide which tool to call and what arguments to pass based on the current situation. The agent architecture means the workflow is driven by LLM reasoning rather than predetermined branching logic, which is what enables it to handle novel signal combinations that a conventional playbook was never explicitly built for.
How does the phishing triage agent decide between the three verdicts (FALSE_POSITIVE, ESCALATE, AUTO_REMEDIATE)?
The verdict decision is driven by the system prompt, which defines thresholds and reasoning criteria in natural language. For example, ten or more VirusTotal detections combined with a high user risk score produces AUTO_REMEDIATE, while an ambiguous result such as four detections combined with a medium-risk finance VP escalates rather than closes as a false positive. The agent synthesizes all available signal from each MCP tool call it makes, and the specific combination of evidence determines which verdict it returns. The criteria can be updated by editing the system prompt rather than rewiring branching conditions.
Why must the AUTO_REMEDIATE verdict go through a human approval gate rather than trigger remediation directly?
The agent produces a recommendation, not a deterministic decision. An LLM reasoning trace is a narrative that reflects the evidence available at the time of the run, but it is not a perfect classifier. Wiring remediation actions directly to agent output removes the human review step that catches false positives before they cause operational disruption. For example, an AUTO_REMEDIATE verdict that incorrectly revokes sessions for a legitimate finance VP during a time-sensitive transaction causes real harm. The approval gate maintains an explicit human decision point with an auditable record of who approved action X at time T based on agent verdict V.
What happens if a VirusTotal API call times out during the agent run?
The agent notes the tool failure in its reasoning trace and bases the verdict on the remaining available evidence. This is a key architectural advantage over a traditional playbook: a playbook either fails the run entirely or requires an explicit error-handling branch for every possible API failure mode. The agent produces a verdict with a confidence score that reflects the missing evidence, such as ESCALATE with 55 percent confidence and a reasoning note that VirusTotal was unavailable. The human analyst reviewing the escalation can see that a tool failed and factor that into their investigation.
What Microsoft Sentinel permissions does the agent's managed identity need?
The managed identity requires the Microsoft Sentinel Contributor role on the Log Analytics workspace to post incident comments and update incident status. Microsoft Graph requires SecurityEvents.ReadWrite.All for reading and updating Sentinel incidents via the Graph security API, and IdentityRiskyUser.Read.All and AuditLog.Read.All for user risk score and sign-in data. VirusTotal access uses an API key stored in Azure Key Vault, and the managed identity requires the Key Vault Secrets User role on that specific vault.
Get weekly security insights
Cloud security, zero trust, and identity guides — straight to your inbox.
Microsoft Cloud Solution Architect
Cloud Solution Architect with deep expertise in Microsoft Azure and a strong background in systems and IT infrastructure. Passionate about cloud technologies, security best practices, and helping organizations modernize their infrastructure.
Share this article
Questions & Answers
Related Articles
Need Help with Your Security?
Our team of security experts can help you implement the strategies discussed in this article.
Contact Us