I Built an AI-Powered Security Scanner for Microsoft Power Platform
Low-code platforms like Power Apps are everywhere — and so are the security gaps they create. Hardcoded API keys, XSS vulnerabilities, insecure Dataverse calls, and exposed secrets hiding in plain sight across thousands of components. Security teams can't keep up by manually reviewing every app.
So I built a solution: an MCP server with 18 security scanning tools that any AI assistant — Claude Desktop, Microsoft Copilot Studio, or any MCP-compatible client — can use through natural language. Just say "scan my Default solution for security issues" and the AI does the rest.
This guide walks you through everything — from what MCP is, to building the tools, deploying to Azure, creating a Copilot Studio agent, and testing it end-to-end. If you're new to this, don't worry — every step has the exact commands and configurations you need.
stdio for local use and Streamable HTTP for cloud deployment.What We're Building
A Python MCP server with 18 security tools organized into 4 tiers:
Tier 1 — Paste-Based Scans (no auth needed)
pp_scan_inlinepp_scan_codepp_scan_xsspp_scan_env_secretspp_scan_dataverse_callspp_scan_dependenciespp_suggest_fixespp_generate_reportTier 2 — Environment Scans (Azure App Registration)
pp_check_connectionpp_list_solutionspp_scan_solutionpp_list_code_componentsTier 3 — Real-Time Scanning
pp_realtime_scan_environmentpp_scan_solution_with_fixespp_vulnerability_reportTier 4 — AI Deep Scan (Claude AI)
pp_ai_deep_scanpp_ai_scan_solutionpp_ai_fix_codePrerequisites
~20 minSoftware You Need
- Python 3.12+ — python.org
- Azure CLI — Install guide
- Claude Desktop (optional) — claude.ai/download
- A text editor — VS Code, Notepad++, or even Notepad
Azure App Registration
This gives your MCP server permission to talk to Power Platform APIs.
Create App Registration
Azure Portal → Entra ID → App registrations → New registration → Name it "PP-Security-Scanner" → Register
Copy Your IDs
From the Overview page, copy the Application (client) ID and Directory (tenant) ID. You'll need these later.
Create a Client Secret
Certificates & secrets → New client secret → Copy the Value immediately (it won't be shown again)
Grant Dataverse Permissions
API permissions → Add a permission → Dynamics CRM → Application permissions → user_impersonation → Grant admin consent
Add as Application User
Power Platform Admin Center → Environments → your env → Settings → Application users → New app user → Add your app → Assign System Administrator role
Anthropic API Key (Optional)
For the 3 AI-powered tools. Get it from console.anthropic.com → API Keys. If you skip this, the other 15 tools still work perfectly.
Deploy to Azure
~10 minThe deployment package includes server.py (2345 lines, 18 tools, all fixes pre-applied), requirements.txt, and deploy-to-azure.ps1 (one-command deploy with 7 automated tests).
Download & Extract the Package
Extract the ZIP file to a folder. You'll see three files: server.py, requirements.txt, and deploy-to-azure.ps1
Edit the Deploy Script
Open deploy-to-azure.ps1 in a text editor. At the top, fill in the CONFIGURATION section:
# ──── Azure App Service Settings ──── $APP_NAME = "ENTER_YOUR_APP_NAME_HERE" # globally unique name $RESOURCE_GROUP = "ENTER_YOUR_RESOURCE_GROUP_HERE" # e.g., "rg-pp-security-mcp" $LOCATION = "ENTER_YOUR_AZURE_REGION_HERE" # e.g., "eastus" $SKU = "F1" # F1=Free, B1=Basic # ──── Power Platform Credentials ──── $PP_TENANT_ID = "ENTER_YOUR_TENANT_ID_HERE" # Entra ID → Overview $PP_CLIENT_ID = "ENTER_YOUR_CLIENT_ID_HERE" # App reg → Overview $PP_CLIENT_SECRET = "ENTER_YOUR_CLIENT_SECRET_HERE" # Certificates & secrets $PP_ENVIRONMENT = "ENTER_YOUR_DATAVERSE_URL_HERE" # e.g., "https://org123.crm.dynamics.com" $ANTHROPIC_KEY = "ENTER_YOUR_ANTHROPIC_KEY_OR_EMPTY" # "" to skip AI tools
Login to Azure & Run
Open PowerShell in the folder with your files:
az login .\deploy-to-azure.ps1
Wait for All 7 Tests to Pass
The script creates everything, deploys, waits for the build, and runs 7 automated tests. Look for:
╔══════════════════════════════════════════════════════════════╗ ║ 🎉 ALL 7 TESTS PASSED! DEPLOYMENT SUCCESSFUL! ║ ╚══════════════════════════════════════════════════════════════╝ 🌐 Welcome Page: https://your-app.azurewebsites.net/ 💚 Health Check: https://your-app.azurewebsites.net/health 🔌 MCP Endpoint: https://your-app.azurewebsites.net/mcp/mcp
The 5 Fixes Already Baked In
The server.py in the package has all these fixes pre-applied so you never hit these problems:
⚠️ Missing Session Manager
session_manager.run()⚠️ Wrong Endpoint Path
Mount("/mcp") + SDK internal /mcp = full path /mcp/mcp/mcp/mcp as the endpoint URL everywhere⚠️ DNS Rebinding Block (HTTP 421)
TransportSecuritySettings(enable_dns_rebinding_protection=False)⚠️ Azure Detects Flask
gunicorn --worker-class uvicorn.workers.UvicornWorker⚠️ Env Var Update Breaks App
Create Your Copilot Studio Agent
~10 minOpen Copilot Studio
Go to copilotstudio.microsoft.com → Sign in → Click Create → New agent
Name & Describe Your Agent
Name: Power Platform Security Scanner
Description: Scans Power Apps code and Dataverse solutions for security vulnerabilities including XSS, hardcoded secrets, insecure API calls, and architecture issues.
Enable Generative Orchestration
Go to Settings → Generative AI and turn ON Generative orchestration. This is required for MCP — it lets the agent dynamically decide which tool to call.
Paste the Agent Instructions
Go to the Overview tab → Instructions field → Paste the instructions below:
Connect MCP Server to Your Agent
~5 minGo to Tools Tab
In your agent editor, click Tools in the top navigation.
Add a Tool → New Tool → Model Context Protocol
Click Add a tool → New tool → Select Model Context Protocol
Enter Your MCP Server Details
Fill in the wizard:
Server Name: Power Platform Security Scanner
Server Description: Scans Power Apps, PCF components, and
Dataverse solutions for security vulnerabilities.
18 tools: code scanning, XSS, secrets, AI analysis.
Server URL: https://YOUR-APP-NAME.azurewebsites.net/mcp/mcp$APP_NAME = "pp-security-mcp-contoso", your URL is https://pp-security-mcp-contoso.azurewebsites.net/mcp/mcpAuthentication → None
Select None. Your server handles auth internally via Azure environment variables.
Click Create → Review 18 Tools → Add
Copilot Studio connects to your server, discovers all 18 tools, and lists them. Click Add to add them all.
Verify on the Tools Tab
You should see "Power Platform Security Scanner" listed with all 18 tools enabled. Click on it to toggle individual tools on/off if needed.
Test Your Agent
~5 minUse the Test panel on the right side of Copilot Studio. Try these prompts — ordered from simple to advanced:
🟢 Quick Scans (No Credentials Needed)
🔵 Environment Scans (Uses Your Credentials)
🤖 AI Deep Scans (Anthropic API)
Publish & Share
~5 minPublish the Agent
Click Publish in the top-right corner of Copilot Studio.
Deploy to Microsoft Teams
Go to Channels → Microsoft Teams → Turn on Teams. Your security team can now chat with the scanner in Teams.
Share a Demo Link
Under Channels, grab the Demo website link to share with stakeholders for testing.
Claude Desktop (Bonus)
~3 minThe same server.py works locally with Claude Desktop via stdio transport — no Azure needed.
Configure Claude Desktop
Open your config file: %APPDATA%\Claude\claude_desktop_config.json (Windows) or ~/Library/Application Support/Claude/claude_desktop_config.json (Mac)
{
"mcpServers": {
"powerplatform-security": {
"command": "python",
"args": ["C:/path/to/server.py"], // ← your path
"env": {
"PP_TENANT_ID": "YOUR_TENANT_ID", // ← from Entra ID
"PP_CLIENT_ID": "YOUR_CLIENT_ID", // ← from App Registration
"PP_CLIENT_SECRET": "YOUR_SECRET", // ← from Certificates & secrets
"PP_ENVIRONMENT": "https://org.crm.dynamics.com", // ← your Dataverse URL
"ANTHROPIC_API_KEY":"sk-ant-..." // ← from console.anthropic.com
}
}
}
}Restart Claude Desktop. You'll see a 🔨 tool icon. Ask: "Scan this code: const secret = 'password123';"
Quick Reference
Lessons Learned — 5 Gotchas That Will Save You Hours
These were discovered during a marathon debugging session. Each one caused the MCP endpoint to fail silently — the welcome page and health check worked perfectly, but /mcp/mcp returned cryptic errors. Here's every trap and its fix, so you never have to go through what we did.
🚨 Gotcha #1 — The Silent Session Manager
streamable_http_app() requires a running session manager. Without a lifespan handler that calls mcp.session_manager.run(), the server boots up happily but silently refuses every MCP request.mcp_lifespan async context manager using AsyncExitStack and wire it to Starlette via lifespan=mcp_lifespan. This is documented in Microsoft's official Azure MCP tutorial but easy to miss.🚨 Gotcha #2 — The Double /mcp Path
Mount("/mcp", app=mcp_app), the SDK adds its own internal /mcp subpath. So the real endpoint becomes /mcp/mcp — the path doubles up. This is by design, not a bug./mcp/mcp as your endpoint URL. Don't try to "fix" the doubling by changing the Mount path — that breaks other things. The server.py in the package has this configured correctly.🚨 Gotcha #3 — HTTP 421 DNS Rebinding Block
WARNING Invalid Host header: your-app.azurewebsites.net followed by Terminating session: Nonelocalhost by default. Your Azure App Service hostname gets rejected because it's not on the allowlist. This is a widely reported issue (GitHub #1798).transport_security=TransportSecuritySettings(enable_dns_rebinding_protection=False) to the FastMCP() constructor. Azure App Service already handles network security at the infrastructure layer, so disabling this SDK-level check is safe.🚨 Gotcha #4 — Azure Thinks You're Flask
ModuleNotFoundError: No module named 'flask'gunicorn --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000 --timeout 600 --workers 1 server:app. This tells Azure to use gunicorn with uvicorn workers for ASGI.🚨 Gotcha #5 — Env Var Updates Kill the App
The Key Code Fixes (All Pre-Applied)
Here are the exact code changes. The server.py in the download package already has all of these applied — you don't need to make any changes.
Fix #1 + #3 — Imports
import contextlib from contextlib import asynccontextmanager from mcp.server.fastmcp import FastMCP from mcp.server.transport_security import TransportSecuritySettings # Fix #3
Fix #3 — Disable DNS Rebinding Protection
mcp = FastMCP(
"powerplatform_security_mcp",
stateless_http=True,
transport_security=TransportSecuritySettings(
enable_dns_rebinding_protection=False, # Fix #3 — allow Azure hostname
),
)Fix #1 — Lifespan Handler for Session Manager
@asynccontextmanager async def mcp_lifespan(app): # Fix #1 async with contextlib.AsyncExitStack() as stack: await stack.enter_async_context(mcp.session_manager.run()) yield
Fix #1 + #2 — Correct Mount & Wired Lifespan
mcp_app = mcp.streamable_http_app()
app = Starlette(
routes=[
Route("/", welcome),
Route("/health", health),
Mount("/mcp", app=mcp_app), # Fix #2 — endpoint = /mcp/mcp
],
lifespan=mcp_lifespan, # Fix #1 — MUST wire lifespan here
)server.py in the download package has all 5 fixes built in. The deploy script handles Fix #4 (startup command) and Fix #5 (re-apply after env changes) automatically. You just fill in your credentials and run the script.Get the Complete Package
Everything you need in one ZIP — all fixes applied, all tests included, zero credentials (you enter your own).
server.pyrequirements.txtdeploy-to-azure.ps1Quick Start After Download
# 1. Extract the ZIP # 2. Open deploy-to-azure.ps1 in a text editor # 3. Fill in the CONFIGURATION section (8 values) # 4. Run: az login .\deploy-to-azure.ps1 # 5. Wait for "ALL 7 TESTS PASSED!" # 6. Copy the MCP endpoint URL into Copilot Studio # 7. Done — your AI security scanner is live!
ENTER_YOUR_TENANT_ID_HERE with instructions showing where to find each value. The deploy script validates all fields before running — if you forget one, it tells you exactly what's missing.az login access, an App Registration in Entra ID (tenant ID, client ID, client secret), a Dataverse environment URL from Power Platform Admin Center, and optionally an Anthropic API key from console.anthropic.com for AI deep scan tools. The other 15 tools work without it.
Comments
Post a Comment