Skip to main content
Glama
henrymbuguakiarie

Entra Identity Posture MCP

Entra Identity Posture MCP

CI

An Agentic Security and Governance MCP server built with FastMCP and the Microsoft Graph API for auditing Microsoft Entra ID app registrations and Conditional Access policies against Zero-Trust principles — and generating dry-run remediation scripts an AI agent (or human) can review and run.

Status: Alpha — under active development. Interfaces and tool surfaces may change.

Overview

This server exposes Microsoft Entra ID (Azure AD) security posture data as a full MCP surface — Tools, a Resource, and a Prompt — so that AI agents (e.g., Claude Desktop, VS Code Copilot Chat) can:

  • Audit app registrations for expiring/long-lived credentials, over-privileged Graph permissions, insecure redirect URIs, and risky multi-tenant configurations

  • Scan Conditional Access policies for admin MFA exclusions and policies stuck in report-only mode

  • Run both scans concurrently with a single run_posture_scan call, with optional severity/rule/app filtering, when an agent wants one combined pass instead of two separate tool calls

  • Generate a Markdown Zero-Trust security report plus ready-to-run (dry-run only) Azure CLI / Microsoft Graph PowerShell remediation commands

  • Query the most recent scan results directly as an MCP Resource, without re-invoking a tool

  • Kick off a guided triage workflow via a predefined MCP Prompt

The server is read-only against Microsoft Graph (Application.Read.All, Policy.Read.All). It never calls a Graph write endpoint — remediation tools only generate text commands for a human or CI pipeline to execute.

Architecture

flowchart LR
    subgraph Client["MCP Client (Claude Desktop / VS Code Copilot Chat)"]
        A[AI Agent]
    end

    subgraph Server["entra-identity-posture-mcp (FastMCP, stdio)"]
        T1[Tool: audit_app_registrations]
        T2[Tool: scan_conditional_access_gaps]
        T5[Tool: run_posture_scan]
        T3[Tool: generate_remediation_plan]
        T4[Tool: revoke_or_disable_app_registration]
        R1[Resource: entra://posture/latest]
        P1[Prompt: security_triage_prompt]
        Rules[Rules Engine\napp_registration_rules.py\nconditional_access_rules.py]
        Cache[(In-memory\nposture cache\nper category)]
    end

    subgraph Graph["Microsoft Graph API"]
        G1[/applications/]
        G2[/servicePrincipals/]
        G3[/identity/conditionalAccess/policies/]
    end

    Auth[auth.py\nMSAL cert-based\nConfidentialClientApplication]

    A -->|JSON-RPC over stdio| T1 & T2 & T5 & T3 & T4 & R1 & P1
    T1 --> Rules
    T2 --> Rules
    T5 --> Rules
    T1 -->|GET| G1 & G2
    T2 -->|GET| G3
    T5 -->|GET| G1 & G2 & G3
    T1 & T2 & T5 -.->|auth token| Auth
    Auth -->|client cert| G1
    T1 & T2 & T5 --> Cache
    R1 --> Cache
    T3 -->|renders| Report[[security_report.md.j2]]

Related MCP server: Microsoft MCP Server for Enterprise

Requirements

  • Python 3.12+

  • A Microsoft Entra ID app registration configured with a certificate credential (client secrets are not supported by auth.py)

  • Admin-consented Microsoft Graph application permissions: Application.Read.All and Policy.Read.All

1. Clone the repository

git clone https://github.com/henrymbuguakiarie/entra-identity-posture-mcp.git
cd entra-identity-posture-mcp

2. Install dependencies

Install with uv (recommended):

uv sync

Or install with pip:

pip install -e .

Include test and lint tooling for development:

uv sync --group dev

3. Create the Entra app registration and certificate credential

Authenticate the server with a certificate, not a client secret — auth.py only supports MSAL's certificate-based confidential client flow. Complete these steps once, manually; the server does not automate app registration or admin consent.

3.1 Register the app

  1. Open the Entra admin center and go to Identity → Applications → App registrations.

  2. Select New registration, name it (e.g. entra-identity-posture-mcp), keep the default single-tenant account type, and select Register.

  3. Copy the Application (client) ID and Directory (tenant) ID from the app's Overview page — you'll need both for .env.

3.2 Generate a certificate

Generate the certificate on the machine that will run the server, so the private key never leaves your workstation.

Windows (PowerShell):

# Generate a self-signed certificate and store it in your user certificate store
$cert = New-SelfSignedCertificate `
  -Subject "CN=entra-identity-posture-mcp" `
  -CertStoreLocation "Cert:\CurrentUser\My" `
  -KeyExportPolicy Exportable `
  -KeySpec Signature `
  -KeyLength 2048 `
  -NotAfter (Get-Date).AddYears(1)

# Export the public certificate to upload to Entra
Export-Certificate -Cert $cert -FilePath "$HOME\entra-mcp-cert.cer"

# Export the private key as a password-protected PFX
$securePwd = Read-Host -Prompt "Set a temporary PFX password" -AsSecureString
Export-PfxCertificate -Cert $cert -FilePath "$HOME\entra-mcp-cert.pfx" -Password $securePwd

Convert the PFX to the PEM private key format auth.py expects — this requires OpenSSL, which ships with Git for Windows at C:\Program Files\Git\mingw64\bin\openssl.exe:

openssl pkcs12 -in ~/entra-mcp-cert.pfx -nocerts -nodes -out ~/entra-mcp-cert.key.pem

Delete the PFX once you have the PEM file — you no longer need it:

Remove-Item "$HOME\entra-mcp-cert.pfx" -Force

macOS/Linux (OpenSSL, cross-platform):

# Generate a private key and matching self-signed public certificate in one step
openssl req -x509 -newkey rsa:2048 -keyout entra-mcp-cert.key.pem -out entra-mcp-cert.cer \
  -days 365 -nodes -subj "/CN=entra-identity-posture-mcp"

Either path produces two files:

  • entra-mcp-cert.cer — the public certificate. Upload this one to Entra.

  • entra-mcp-cert.key.pem — the private key. Keep this file local and never commit it (see .gitignore); ENTRA_CERT_PATH points to it.

3.3 Upload the certificate

  1. Open Certificates & secrets → Certificates on your app registration.

  2. Select Upload certificate and choose entra-mcp-cert.cer — upload only the public certificate, never the private key.

  3. Copy the certificate's Thumbprint after the upload completes — you'll need it for .env.

3.4 Grant API permissions

  1. Open API permissions → Add a permission → Microsoft Graph → Application permissions.

  2. Add Application.Read.All and Policy.Read.All, then select Add permissions.

  3. Select Grant admin consent for <tenant> and confirm. Both permissions must show a green check under Status before the server can call Graph.

4. Configure environment variables

The server authenticates to Microsoft Graph via MSAL certificate-based confidential client auth. Copy .env.example to .env:

cp .env.example .env

Fill in the values you collected in step 3:

Variable

Description

ENTRA_TENANT_ID

The Directory (tenant) ID from the app registration's Overview page

ENTRA_CLIENT_ID

The Application (client) ID from the app registration's Overview page

ENTRA_CERT_PATH

Path to the PEM-encoded private key file (entra-mcp-cert.key.pem), not the .cer

ENTRA_CERT_THUMBPRINT

Thumbprint of the certificate you uploaded to the app registration

IMMINENT_EXPIRATION_DAYS

Days-until-expiry threshold for the IMMINENT_EXPIRATION rule (default 30)

EXCESSIVE_LIFESPAN_DAYS

Max credential lifespan in days before flagging EXCESSIVE_LIFESPAN (default 180)

Note: ENTRA_CERT_PATH must point to the PEM private key, not the .cer file you uploaded to Entra — auth.py reads this file and passes its contents to MSAL as the client credential.

5. Run the server

Start the MCP server directly over stdio:

uv run entra-posture-mcp

VS Code (mcp.json)

{
  "servers": {
    "entra-identity-posture": {
      "command": "uv",
      "args": ["run", "entra-posture-mcp"],
      "cwd": "${workspaceFolder}",
    },
  },
}

Claude Desktop (claude_desktop_config.json)

{
  "mcpServers": {
    "entra-identity-posture": {
      "command": "uv",
      "args": [
        "run",
        "--directory",
        "C:\\Work\\Automation\\entra-identity-posture-mcp",
        "entra-posture-mcp",
      ],
    },
  },
}

MCP surface reference

Kind

Name

Description

Tool

audit_app_registrations

Scans app registrations for expiring/long-lived secrets, risky permissions, and insecure redirect URIs. Returns structured findings (metadata + issues) plus a text summary

Tool

scan_conditional_access_gaps

Scans Conditional Access policies for admin MFA exclusions and report-only status. Returns structured findings plus a text summary

Tool

run_posture_scan

Runs both scans above concurrently and returns one merged, optionally severity/rule/app-filtered result. Updates both cache categories

Tool

generate_remediation_plan

Renders a Markdown Zero-Trust report + dry-run CLI/PowerShell snippets from findings

Tool

revoke_or_disable_app_registration

Generates a dry-run Azure CLI/PowerShell command to disable sign-in, rotate a password or certificate credential (type-specific), remove a credential, or remove a permission

Resource

entra://posture/latest

Cached JSON from the most recent scan of each category (app registrations, Conditional Access), queryable without re-invoking a tool

Prompt

security_triage_prompt

Predefined Zero-Trust triage prompt to prioritize findings and recommend fixes

Sample JSON-RPC request/response

MCP clients talk to the server over stdio using JSON-RPC 2.0 — every tool call is one request/response pair on stdin/stdout. Here's what actually crosses the wire when a client calls revoke_or_disable_app_registration (captured against this server):

Request (client → server, on stdin):

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "revoke_or_disable_app_registration",
    "arguments": {
      "app_id": "abc-123",
      "action": "disable_sign_in",
    },
  },
}

Response (server → client, on stdout):

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "🔒 Dry-Run Remediation Output for App ID 'abc-123':\n\n```bash\n# PowerShell (MgGraph): Disable user sign-in\nUpdate-MgServicePrincipal -ServicePrincipalId abc-123 -AccountEnabled:$false\n```\n*(Note: Read-only mode active. Run the script manually or via CI pipeline to execute).*",
      },
    ],
    "structuredContent": {
      "result": "🔒 Dry-Run Remediation Output for App ID 'abc-123':\n\n```bash\n# PowerShell (MgGraph): Disable user sign-in\nUpdate-MgServicePrincipal -ServicePrincipalId abc-123 -AccountEnabled:$false\n```\n*(Note: Read-only mode active. Run the script manually or via CI pipeline to execute).*",
    },
    "isError": false,
  },
}

The MCP Inspector CLI commands in step 6 print just the result payload — reproduce the raw envelope above by driving the server directly with the MCP Python SDK client, or watch stdin/stdout while a client like Claude Desktop or VS Code Copilot Chat drives it.

revoke_or_disable_app_registration's action argument distinguishes password vs. certificate credentials, so the generated command matches the credential type flagged by a scan finding's evidence.credential_type:

// action: "rotate_password_credential"
"az ad app credential reset --id abc-123 --append"

// action: "rotate_certificate_credential"
"az ad app credential reset --id abc-123 --append --create-cert"

--append is always included — it adds a new credential alongside existing ones for zero-downtime rotation, instead of the Azure CLI's destructive default of clearing all existing credentials.

Example agent workflow

User: Run a Zero-Trust audit on my Entra tenant and tell me what to fix first.

Agent: [calls run_posture_scan]
       [calls generate_remediation_plan with the combined findings]

Agent: I found 2 CRITICAL and 3 HIGH severity issues:

       1. [CRITICAL] "Vendor Sync App" has Directory.ReadWrite.All combined with a
          multi-tenant (AzureADMultipleOrgs) sign-in audience.
          → Update-MgApplication -ApplicationId <id>

       2. [CRITICAL] Conditional Access policy "Require MFA for Admins" excludes the
          Global Administrator role from enforcement.
          → Get-MgIdentityConditionalAccessPolicy -ConditionalAccessPolicyId <id>

       Full report and remaining dry-run commands are in the Markdown report above.
       Review each command before running it — nothing has been changed in your tenant.

6. Verify the server

Before you wire the server into an LLM client, validate the JSON-RPC tool/resource/prompt schemas in isolation using the official MCP Inspector CLI:

npx @modelcontextprotocol/inspector --cli uv run entra-posture-mcp --method tools/list
npx @modelcontextprotocol/inspector --cli uv run entra-posture-mcp --method resources/list
npx @modelcontextprotocol/inspector --cli uv run entra-posture-mcp --method prompts/list

# Exercise a tool that doesn't require live Graph credentials
npx @modelcontextprotocol/inspector --cli uv run entra-posture-mcp \
  --method tools/call --tool-name revoke_or_disable_app_registration \
  --tool-arg app_id=abc-123 --tool-arg action=disable_sign_in

Once you configure .env against a real test tenant, run the stdio entrypoint and invoke audit_app_registrations / scan_conditional_access_gaps / run_posture_scan to confirm known findings (an expiring secret, a risky permission, or a report-only Conditional Access policy) surface correctly — then repeat the workflow through Claude Desktop or VS Code Copilot Chat using the configs above.

Live MCP Inspector CLI session against a real test tenant, captured verbatim (tenant/app IDs redacted). Scan tools return structured metadata + issues alongside a summary text field — shown below trimmed to the first/last finding for brevity. Note evidence.credential_type ("certificate" or "password") on the EXCESSIVE_LIFESPAN finding, which drives which of rotate_password_credential / rotate_certificate_credential a matching IMMINENT_EXPIRATION finding would recommend:

$ npx @modelcontextprotocol/inspector --cli uv run entra-posture-mcp --method tools/call --tool-name audit_app_registrations
{
  "content": [
    {
      "type": "text",
      "text": "{\n  \"metadata\": {\n    \"tenant_id\": \"248c1b45-...\",\n    \"scanned_at\": \"2026-07-29T09:35:26.482128Z\",\n    \"rule_version\": \"1.1\"\n  },\n  \"issues\": [\n    {\n      \"app_id\": \"fd0486bd-...\",\n      \"app_name\": \"InsomniaWebApp\",\n      \"severity\": \"HIGH\",\n      \"rule_id\": \"DANGEROUS_REDIRECT_URI\",\n      \"issue\": \"Insecure redirect URIs detected: http://localhost.\",\n      \"evidence\": {\"redirect_uris\": [\"http://localhost\"]},\n      \"remediation_action\": null,\n      ... 3 more DANGEROUS_REDIRECT_URI findings ...\n    },\n    {\n      \"app_id\": \"c712c7f1-...\",\n      \"app_name\": \"entra-identity-posture-mcp\",\n      \"severity\": \"MEDIUM\",\n      \"rule_id\": \"EXCESSIVE_LIFESPAN\",\n      \"issue\": \"Credential key_id '5e44aaeb-...' has an excessive lifespan of 365 days.\",\n      \"evidence\": {\"key_id\": \"5e44aaeb-...\", \"credential_type\": \"certificate\", \"lifespan_days\": 365},\n      \"remediation_action\": \"remove_credential\",\n      \"remediation_params\": {\"app_id\": \"c712c7f1-...\", \"key_id\": \"5e44aaeb-...\"}\n    }\n  ],\n  \"summary\": \"Found 6 app registration security issues:\\n\\n- [HIGH] InsomniaWebApp (fd0486bd-...): Insecure redirect URIs detected: http://localhost.\\n... 4 more ...\\n- [MEDIUM] entra-identity-posture-mcp (c712c7f1-...): Credential key_id '5e44aaeb-...' has an excessive lifespan of 365 days.\"\n}"
    }
  ],
  "isError": false
}

$ npx @modelcontextprotocol/inspector --cli uv run entra-posture-mcp --method tools/call --tool-name scan_conditional_access_gaps
{
  "content": [
    {
      "type": "text",
      "text": "{\n  \"metadata\": {\n    \"tenant_id\": \"248c1b45-...\",\n    \"scanned_at\": \"2026-07-29T09:11:33.227421Z\",\n    \"rule_version\": \"1.1\"\n  },\n  \"issues\": [],\n  \"summary\": \"✅ Conditional Access scan complete: All policies comply with Zero-Trust standards.\"\n}"
    }
  ],
  "isError": false
}

$ npx @modelcontextprotocol/inspector --cli uv run entra-posture-mcp --method tools/call --tool-name run_posture_scan
{
  "content": [
    {
      "type": "text",
      "text": "{\n  \"metadata\": {\n    \"tenant_id\": \"248c1b45-...\",\n    \"scanned_at\": \"2026-07-29T09:24:31.284144Z\",\n    \"rule_version\": \"app:1.1;ca:1.1\"\n  },\n  \"issues\": [ ... 6 combined app-registration + Conditional Access findings ... ],\n  \"summary\": \"Found 6 posture issues (of 6 total): ...\"\n}"
    }
  ],
  "isError": false
}

severity is a Pydantic Literal["CRITICAL", "HIGH", "MEDIUM", "LOW"] at the MCP schema boundary, so an invalid value on run_posture_scan fails validation before the scan runs — it never silently returns zero results:

$ npx @modelcontextprotocol/inspector --cli uv run entra-posture-mcp \
    --method tools/call --tool-name run_posture_scan --tool-arg severity=NOT_A_LEVEL
{
  "content": [
    {
      "type": "text",
      "text": "Error executing tool run_posture_scan: 1 validation error for run_posture_scanArguments\nseverity\n  Input should be 'CRITICAL', 'HIGH', 'MEDIUM' or 'LOW' [type=literal_error, input_value='NOT_A_LEVEL', input_type=str]\n    For further information visit https://errors.pydantic.dev/2.13/v/literal_error"
    }
  ],
  "isError": true
}

A GIF/screenshot of the same workflow running through Claude Desktop or VS Code Copilot Chat will replace this transcript once captured.

Development

Run tests:

uv run pytest

Lint and format:

uv run ruff check .
uv run ruff format .

Continuous integration runs ruff check and pytest on every push/PR via .github/workflows/ci.yml.

Roadmap

Deliberately out of scope for v1:

  • Terraform file generation (e.g. azuread_application_password, azuread_application_pre_authorized) for remediation — v1 only emits dry-run Azure CLI / PowerShell snippets.

  • Automated GitHub PR creation for remediation changes — v1 leaves execution and change management entirely to the human/CI pipeline.

Both are fast-follow candidates now that v1 has been validated against a live tenant.

License

MIT © Henry Mbugua

Available Tools

5 tools
audit_app_registrationsA

Scans Entra ID app registrations for expiring secrets, excessive lifespans, risky permissions, and insecure redirect URIs. Returns structured findings (metadata + issues) alongside a human-readable summary.

ParametersJSON Schema
NameRequiredDescriptionDefault
imminent_expiry_daysNo
excessive_lifespan_daysNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
issuesNoStructured findings produced by the scan.
summaryYesHuman-readable recap of the findings.
metadataYesScan-level context: tenant, timestamp, rule version.

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description must convey behavioral traits. It mentions scanning and returning results but does not state whether the tool is read-only, requires specific permissions, or has side effects. For a security audit tool, read-only behavior is likely but not confirmed.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences long, front-loads the core purpose, and contains no unnecessary words. Every phrase adds value: the scanning targets and the output structure are clearly stated.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description adequately covers the tool's purpose and output (structured findings + summary), and the presence of an output schema reduces the need for return value details. However, the lack of parameter descriptions and usage context leaves gaps in completeness for a tool with two configurable thresholds.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The description does not mention the two parameters (imminent_expiry_days, excessive_lifespan_days) or explain their meaning. With 0% schema description coverage, the description should compensate, but it only hints at the concepts (expiring, excessive) without connecting them to the parameters. The agent must infer from parameter names alone.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool scans Entra ID app registrations for specific issues (expiring secrets, excessive lifespans, risky permissions, insecure redirect URIs) and returns structured findings with a summary. This distinguishes it from sibling tools like scan_conditional_access_gaps or run_posture_scan.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage when auditing app registration security but provides no explicit when-to-use guidance, conditions, or alternatives. Given siblings exist for similar tasks, some direction would help an agent decide between this and other audit tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

generate_remediation_planA

Generates a Zero-Trust Markdown security report and dry-run CLI remediation commands from findings.

ParametersJSON Schema
NameRequiredDescriptionDefault
issuesYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations provided, so description bears full burden. It accurately states the tool generates a report and CLI commands, but omits details about side effects, required permissions, or whether the report is persisted. The description is not misleading but lacks depth.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence that directly conveys purpose without superfluous words. It is concise but could benefit from slight restructuring to front-load key actions.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has one required parameter and no annotations, the description is minimally adequate. The existence of an output schema partly compensates, but the description does not elaborate on what the returned report or commands contain.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0% for the only parameter 'issues', and the description adds no additional meaning beyond the schema type definition. It says 'from findings', but does not explain what the issues array should contain (e.g., structure, required fields).

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states it generates a Zero-Trust Markdown security report and dry-run CLI remediation commands. It distinguishes from sibling tools like audit_app_registrations, scan_conditional_access_gaps, and revoke_or_disable_app_registration by focusing on plan generation rather than auditing, scanning, or revoking.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage 'from findings' but does not explicitly state when to use this tool vs. alternatives. No exclusion criteria or prerequisites are mentioned, leaving the agent to infer context from sibling names.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

revoke_or_disable_app_registrationC

Generates dry-run Azure CLI or PowerShell commands to disable sign-in or revoke credentials.

ParametersJSON Schema
NameRequiredDescriptionDefault
actionYes
app_idYes
key_idNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

C2.7/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It discloses that the tool only generates dry-run commands rather than executing them, which is key. However, it fails to mention other behaviors like authentication requirements, error handling, or output format.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, concise sentence. It front-loads the key concept (dry-run generation) without extraneous detail, making it easy to read. However, it could benefit from brief bullet points for clarity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has 3 parameters, an enum, and no annotations, the description is too sparse. It omits the output format, error scenarios, and prerequisites. The existing output schema is not referenced, so the agent lacks complete context.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so description must add meaning. It mentions 'disable sign-in or revoke credentials' which maps to some action enum values but not all. It does not explain the app_id or key_id parameters, leaving their semantics unclear.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose3/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states it generates dry-run commands for disabling sign-in or revoking credentials, but the action enum includes additional actions like remove_permission and rotations, which are not mentioned. It identifies the verb 'generates' and the resource 'app registration commands', but misses full scope.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance on when to use this tool versus siblings like audit_app_registrations or generate_remediation_plan. The description implies it's for generating remediation commands, but without explicit context, the agent may misuse it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

run_posture_scanB

Runs the app registration and Conditional Access scans concurrently and returns one combined, optionally filtered PostureScanResult. Updates the shared entra://posture/latest resource cache with both scan categories.

ParametersJSON Schema
NameRequiredDescriptionDefault
app_idNo
rule_idNo
severityNo
imminent_expiry_daysNo
excessive_lifespan_daysNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
issuesNoStructured findings produced by the scan.
summaryYesHuman-readable recap of the findings.
metadataYesScan-level context: tenant, timestamp, rule version.

TDQS

B3.2/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description discloses that scans run concurrently, results are combined and optionally filtered, and a shared cache is updated. However, with no annotations, it lacks details on side effects (e.g., whether scans modify data), permissions needed, or runtime implications.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences long, with the first sentence providing the core action and the second adding cache update context. It is concise and front-loaded, but could include parameter details without becoming verbose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has 5 parameters, no annotations, and no schema descriptions, the description is insufficient for correct invocation. It lacks parameter explanations, usage context, and behavioral details beyond the basic operation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters1/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema description coverage, the description does not explain the parameters. Terms like app_id, rule_id, severity, and expiry/duration fields are mentioned only as parameter names, so the description adds no meaning beyond the names themselves.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool runs app registration and Conditional Access scans concurrently and returns a combined, optionally filtered PostureScanResult. It also mentions updating a cache, which distinguishes it from sibling tools like 'audit_app_registrations' and 'scan_conditional_access_gaps' that likely run single scans.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies the tool is for running both scan types together and filtering results, but it does not explicitly state when to use this tool versus the individual sibling tools or provide any when-not scenarios.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

scan_conditional_access_gapsA

Scans Entra Conditional Access policies for admin MFA exclusions and policies stuck in report-only mode. Returns structured findings alongside a human-readable summary.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
issuesNoStructured findings produced by the scan.
summaryYesHuman-readable recap of the findings.
metadataYesScan-level context: tenant, timestamp, rule version.

TDQS

A3.6/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations provided, so description must fully disclose behavior. It states 'scans' and 'returns structured findings' but does not explicitly state it is read-only. No mention of authentication, rate limits, or scope (e.g., all policies or filtered). Even though it's a scan, the agent cannot be sure it doesn't trigger actions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, minimal waste. Each sentence adds value: first states action and targets, second describes output format. No redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a no-parameter tool with output schema, the description covers the core purpose and output format. It specifies exactly what it checks (admin MFA exclusions, report-only mode). Could mention it is safe to run frequently, but given low complexity, it is mostly complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Zero parameters, schema coverage 100%. Baseline per rule is 4. Description adds no parameter info, but none needed. The tool operates without inputs, so clarity on purpose is sufficient.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Description clearly identifies the tool scans Entra Conditional Access policies for specific security gaps (admin MFA exclusions, report-only mode). It distinguishes from siblings like audit_app_registrations (different resource) and run_posture_scan (broader scan). Verb 'scans' + resource 'Entra Conditional Access policies' + specific focus.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No explicit guidance on when to use this tool versus siblings. Given sibling names like run_posture_scan (broader security scan) and audit_app_registrations (audit of app registrations), the description should state when to choose this tool (e.g., for CA-specific gap analysis). The AI agent lacks decision context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 5 tool updatesv0.1.0
    • First observedaudit_app_registrations
    • First observedgenerate_remediation_plan
    • First observedrevoke_or_disable_app_registration
    • First observedrun_posture_scan
    • First observedscan_conditional_access_gaps

TDQS

A3.6/5.0

Scored across 5 tools

Disambiguation5/5

Each tool has a distinct purpose: two separate scans, a combined scan, a report generator, and a remediation command generator. There is no functional overlap.

Naming Consistency5/5

All tool names use a consistent verb_noun pattern in snake_case (e.g., audit_app_registrations, generate_remediation_plan), making them predictable and readable.

Tool Count5/5

With 5 tools, the server is well-scoped for its purpose of assessing and remediating Entra ID posture without being too sparse or bloated.

Completeness4/5

The tools cover auditing, combined scanning, remediation planning, and disabling/revoking registrations. Missing a tool to execute remediation commands directly is a minor gap, but the set is largely complete.

Maintenance

ActivitySlowing
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • -
    license
    Not graded
    quality
    Not graded
    maintenance
    Provides secure access to Microsoft Entra ID (Azure AD) resources including users, devices, and applications through Microsoft Graph API. Enables querying organizational data with comprehensive audit logging to Azure Blob Storage.
    -
  • F
    license
    Not graded
    quality
    C
    maintenance
    Read-only MCP server for Microsoft Entra ID (Azure AD) that enables querying user sign-in logs, group memberships, and assigned Microsoft 365 licenses via Microsoft Graph API. Provides security and audit visibility without any write operations.
    -
  • A
    license
    A
    quality
    B
    maintenance
    Enables security analysts to investigate Microsoft Entra ID security logs through natural language, exposing read-only tools for user context, sign-ins, risky users, risk detections, directory audits, and conditional access policies.
    6
    MIT