entraadm-mcp
English | 日本語
MCP server for Microsoft Entra ID sign-in and audit-log triage. Read-only.
Why this instead of the official Microsoft MCP Server for Enterprise
Microsoft ships an official MCP Server for Enterprise for Entra ID data. It is a good fit for an interactive admin at a keyboard, and is not a fit for an unattended triage bot:
- Delegated auth only. The official server does not support app-only
(client credentials) auth, so it cannot run headless behind a service
account. entraadm-mcp is built for that case: app-only in production, with
a delegated (
az login) fallback for local development. - A general-purpose Graph query tool, not a fixed tool set. The official
server exposes one tool that lets the model construct arbitrary
GET/schema-discovery calls against Microsoft Graph. That is flexible for a human, and awkward to put behind an allow-list for an automated triage profile. entraadm-mcp exposes seven fixed, read-only tools instead. - No AADSTS translation. Sign-in failures come back as raw error codes; triage still needs a lookup table. entraadm-mcp annotates every sign-in failure with what the code actually means.
- No cross-request aggregation. Microsoft Graph itself cannot filter
sign-ins on
status/errorCodeserver-side, and has no built-in password-spray view.signin_failure_statsaggregates client-side and flags IPs with failed sign-ins against many distinct users — the pattern Entra's per-account smart lockout does not catch on its own.
Tools
| Tool | What it answers |
|---|---|
health_check |
Is Graph reachable, and can this credential read sign-in logs? |
get_user |
Is this account enabled, synced from on-prem, and what are its licenses? |
signin_logs |
Why did this user's sign-in fail (or succeed), with the AADSTS code translated? |
signin_failure_stats |
Tenant-wide failure aggregation: top error codes, users, apps, source IPs, and password-spray suspects |
directory_audits |
Who changed what in the directory (block/unblock, attribute edits), and when? |
get_user_auth_methods |
Is MFA actually registered for this account? |
daily_brief |
One-call summary combining signin_failure_stats and directory_audits |
Every tool is read-only. Write operations (unblocking an account, resetting a password, revoking a session) are out of scope for this server.
Auth model
Two auth modes, selected by which environment variables are set:
| Mode | When | Env vars |
|---|---|---|
| app-only | All three set | ENTRAADM_TENANT_ID, ENTRAADM_CLIENT_ID, ENTRAADM_CLIENT_SECRET |
| azure-cli | None set | (uses the current az login session) |
Setting one or two of the three app-only variables is a configuration error and the server refuses to start, rather than silently falling back to a different auth mode than intended.
Required Graph permissions
| Tool(s) | Permission | Notes |
|---|---|---|
get_user (base fields) |
User.Read.All |
|
signin_logs, signin_failure_stats, directory_audits, get_user's sign_in_activity field |
AuditLog.Read.All (app-only) or the Reports Reader directory role (delegated) |
|
get_user_auth_methods |
UserAuthenticationMethod.Read.All |
App-only only; not available under delegated (az login) auth in a typical tenant role assignment |
A missing permission never crashes a tool. It degrades that tool (or that
one field) to {"error": "...", "missing_permission": "..."} with a
human-readable explanation of what role or permission is needed, so
health_check and every other tool stay usable even before full permissions
are granted.
Setup
uv tool install entraadm-mcp
# or
pip install entraadm-mcp
Configuration
Set the three app-only variables for production/unattended use:
export ENTRAADM_TENANT_ID=00000000-0000-0000-0000-000000000000
export ENTRAADM_CLIENT_ID=00000000-0000-0000-0000-000000000000
export ENTRAADM_CLIENT_SECRET=your-client-secret
Or leave all three unset and run az login first for local development.
Optional:
# Default page cap for the log-scanning tools (1-50, default 5).
export ENTRAADM_MAX_PAGES_DEFAULT=5
Usage
Claude Code (plugin)
/plugin marketplace add shigechika/entraadm-mcp
/plugin install entraadm-mcp@entraadm-mcp
Claude Code (manual)
Add to .mcp.json:
{
"mcpServers": {
"entraadm-mcp": {
"type": "stdio",
"command": "uvx",
"args": ["entraadm-mcp"],
"env": {
"ENTRAADM_TENANT_ID": "${ENTRAADM_TENANT_ID:-}",
"ENTRAADM_CLIENT_ID": "${ENTRAADM_CLIENT_ID:-}",
"ENTRAADM_CLIENT_SECRET": "${ENTRAADM_CLIENT_SECRET:-}"
}
}
}
}
Direct execution
entraadm-mcp
CLI options
| Option | Effect |
|---|---|
--version |
Print the version and exit |
--check |
Resolve auth, probe Graph reachability and sign-in log access, print a report, exit 0 (or 1 on config error) |
Notes
- Coverage contract. Every result that walks a paged Graph collection
carries a
cappedboolean when its window was not fully scanned — a partial scan is never reported as if it were exhaustive. found: falseis not an error.get_userandget_user_auth_methodsanswer a nonexistent account with{"found": false, ...}, not anerrorkey — a typo'd userPrincipalName should never look like this server being broken.- Retention. Entra ID P1 retains sign-in and directory audit logs for 30 days. A window beyond that returns an empty result, not an error.
Development
uv sync --dev
uv run pytest -v
uv run ruff check .
uv run ruff format --check .
Live smoke test
uv run python scripts/smoke_test.py
Read-only, no payloads printed (tool names/statuses/row counts only), and bounded (small explicit windows/page caps) — nothing here writes to the tenant or scans more than a day of logs.
Releasing
This repository uses release-please
driven by Conventional Commits. Merge
a feat:/fix: PR to main, and release-please opens (or updates) a
release PR; merging that PR tags a release and triggers the publish pipeline
(PyPI, MCP Registry).
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file entraadm_mcp-0.1.0.tar.gz.
File metadata
- Download URL: entraadm_mcp-0.1.0.tar.gz
- Upload date:
- Size: 37.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2cea4424542b5b6209d1ccb63f4ab61b12c704ac97043b2bcd3d7a2b4c8d8bf5
|
|
| MD5 |
d810f47c320e805ca193717278e2c389
|
|
| BLAKE2b-256 |
8a47510db8134bffe12014f8a0ace1850f539261d56341113d599c64349b9248
|
Provenance
The following attestation bundles were made for entraadm_mcp-0.1.0.tar.gz:
Publisher:
release.yml on shigechika/entraadm-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
entraadm_mcp-0.1.0.tar.gz -
Subject digest:
2cea4424542b5b6209d1ccb63f4ab61b12c704ac97043b2bcd3d7a2b4c8d8bf5 - Sigstore transparency entry: 2579322941
- Sigstore integration time:
-
Permalink:
shigechika/entraadm-mcp@65d50aa74f974e45d550674d1873369ac6e3f6d2 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/shigechika
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@65d50aa74f974e45d550674d1873369ac6e3f6d2 -
Trigger Event:
release
-
Statement type:
File details
Details for the file entraadm_mcp-0.1.0-py3-none-any.whl.
File metadata
- Download URL: entraadm_mcp-0.1.0-py3-none-any.whl
- Upload date:
- Size: 23.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c436b559e13fc37e20edbefb825c5044a0bda7c3e88d42e57739a93669e35298
|
|
| MD5 |
24e230a263006432c955182234304123
|
|
| BLAKE2b-256 |
04eb51f637bbe3205fd4afbcae2f671b0bfac023e341083fa127d5a2ff782c69
|
Provenance
The following attestation bundles were made for entraadm_mcp-0.1.0-py3-none-any.whl:
Publisher:
release.yml on shigechika/entraadm-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
entraadm_mcp-0.1.0-py3-none-any.whl -
Subject digest:
c436b559e13fc37e20edbefb825c5044a0bda7c3e88d42e57739a93669e35298 - Sigstore transparency entry: 2579322944
- Sigstore integration time:
-
Permalink:
shigechika/entraadm-mcp@65d50aa74f974e45d550674d1873369ac6e3f6d2 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/shigechika
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@65d50aa74f974e45d550674d1873369ac6e3f6d2 -
Trigger Event:
release
-
Statement type: