{
  "markdown": "<!-- mcp-name: io.github.shigechika/entraadm-mcp -->\n\n# entraadm-mcp\n\nEnglish | [日本語](README.ja.md)\n\nMCP server for Microsoft Entra ID sign-in and audit-log triage. Read-only.\n\n## Why this instead of the official Microsoft MCP Server for Enterprise\n\nMicrosoft ships an [official MCP Server for Enterprise](https://learn.microsoft.com/en-us/graph/mcp-server/overview)\nfor Entra ID data. It is a good fit for an interactive admin at a keyboard,\nand is not a fit for an unattended triage bot:\n\n- **Delegated auth only.** The official server does not support app-only\n  (client credentials) auth, so it cannot run headless behind a service\n  account. entraadm-mcp is built for that case: app-only in production, with\n  a delegated (`az login`) fallback for local development.\n- **A general-purpose Graph query tool, not a fixed tool set.** The official\n  server exposes one tool that lets the model construct arbitrary\n  `GET`/schema-discovery calls against Microsoft Graph. That is flexible for\n  a human, and awkward to put behind an allow-list for an automated triage\n  profile. entraadm-mcp exposes seven fixed, read-only tools instead.\n- **No AADSTS translation.** Sign-in failures come back as raw error codes;\n  triage still needs a lookup table. entraadm-mcp annotates every sign-in\n  failure with what the code actually means.\n- **No cross-request aggregation.** Microsoft Graph itself cannot filter\n  sign-ins on `status/errorCode` server-side, and has no built-in\n  password-spray view. `signin_failure_stats` aggregates client-side and\n  flags IPs with failed sign-ins against many distinct users — the pattern\n  Entra's per-account smart lockout does not catch on its own.\n\n## Tools\n\n| Tool | What it answers |\n|---|---|\n| `health_check` | Is Graph reachable, and can this credential read sign-in logs? |\n| `get_user` | Is this account enabled, synced from on-prem, and what are its licenses? |\n| `signin_logs` | Why did this user's sign-in fail (or succeed), with the AADSTS code translated? |\n| `signin_failure_stats` | Tenant-wide failure aggregation: top error codes, users, apps, source IPs, and password-spray suspects |\n| `directory_audits` | Who changed what in the directory (block/unblock, attribute edits), and when? |\n| `get_user_auth_methods` | Is MFA actually registered for this account? |\n| `daily_brief` | One-call summary combining `signin_failure_stats` and `directory_audits` |\n\nEvery tool is read-only. Write operations (unblocking an account, resetting a\npassword, revoking a session) are out of scope for this server.\n\n## Auth model\n\nTwo auth modes, selected by which environment variables are set:\n\n| Mode | When | Env vars |\n|---|---|---|\n| app-only | All three set | `ENTRAADM_TENANT_ID`, `ENTRAADM_CLIENT_ID`, `ENTRAADM_CLIENT_SECRET` |\n| azure-cli | None set | (uses the current `az login` session) |\n\nSetting one or two of the three app-only variables is a configuration error\nand the server refuses to start, rather than silently falling back to a\ndifferent auth mode than intended.\n\n### Required Graph permissions\n\n| Tool(s) | Permission | Notes |\n|---|---|---|\n| `get_user` (base fields) | `User.Read.All` | |\n| `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) | |\n| `get_user_auth_methods` | `UserAuthenticationMethod.Read.All` | App-only only; not available under delegated (`az login`) auth in a typical tenant role assignment |\n\nA missing permission never crashes a tool. It degrades that tool (or that\none field) to `{\"error\": \"...\", \"missing_permission\": \"...\"}` with a\nhuman-readable explanation of what role or permission is needed, so\n`health_check` and every other tool stay usable even before full permissions\nare granted.\n\n## Setup\n\n```bash\nuv tool install entraadm-mcp\n# or\npip install entraadm-mcp\n```\n\n## Configuration\n\nSet the three app-only variables for production/unattended use:\n\n```bash\nexport ENTRAADM_TENANT_ID=00000000-0000-0000-0000-000000000000\nexport ENTRAADM_CLIENT_ID=00000000-0000-0000-0000-000000000000\nexport ENTRAADM_CLIENT_SECRET=your-client-secret\n```\n\nOr leave all three unset and run `az login` first for local development.\n\nOptional:\n\n```bash\n# Default page cap for the log-scanning tools (1-50, default 5).\nexport ENTRAADM_MAX_PAGES_DEFAULT=5\n```\n\n## Usage\n\n### Claude Code (plugin)\n\n```\n/plugin marketplace add shigechika/entraadm-mcp\n/plugin install entraadm-mcp@entraadm-mcp\n```\n\n### Claude Code (manual)\n\nAdd to `.mcp.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"entraadm-mcp\": {\n      \"type\": \"stdio\",\n      \"command\": \"uvx\",\n      \"args\": [\"entraadm-mcp\"],\n      \"env\": {\n        \"ENTRAADM_TENANT_ID\": \"${ENTRAADM_TENANT_ID:-}\",\n        \"ENTRAADM_CLIENT_ID\": \"${ENTRAADM_CLIENT_ID:-}\",\n        \"ENTRAADM_CLIENT_SECRET\": \"${ENTRAADM_CLIENT_SECRET:-}\"\n      }\n    }\n  }\n}\n```\n\n### Direct execution\n\n```bash\nentraadm-mcp\n```\n\n### CLI options\n\n| Option | Effect |\n|---|---|\n| `--version` | Print the version and exit |\n| `--check` | Resolve auth, probe Graph reachability and sign-in log access, print a report, exit 0 (or 1 on config error) |\n\n## Notes\n\n- **Coverage contract.** Every result that walks a paged Graph collection\n  carries a `capped` boolean when its window was not fully scanned — a\n  partial scan is never reported as if it were exhaustive.\n- **`found: false` is not an error.** `get_user` and `get_user_auth_methods`\n  answer a nonexistent account with `{\"found\": false, ...}`, not an `error`\n  key — a typo'd userPrincipalName should never look like this server being\n  broken.\n- **Retention.** Entra ID P1 retains sign-in and directory audit logs for 30\n  days. A window beyond that returns an empty result, not an error.\n\n## Development\n\n```bash\nuv sync --dev\nuv run pytest -v\nuv run ruff check .\nuv run ruff format --check .\n```\n\n### Live smoke test\n\n```bash\nuv run python scripts/smoke_test.py\n```\n\nRead-only, no payloads printed (tool names/statuses/row counts only), and\nbounded (small explicit windows/page caps) — nothing here writes to the\ntenant or scans more than a day of logs.\n\n## Releasing\n\nThis repository uses [release-please](https://github.com/googleapis/release-please)\ndriven by [Conventional Commits](https://www.conventionalcommits.org/). Merge\na `feat:`/`fix:` PR to `main`, and release-please opens (or updates) a\nrelease PR; merging that PR tags a release and triggers the publish pipeline\n(PyPI, MCP Registry).\n\n## License\n\nMIT\n",
  "bytes": 6486,
  "sha": "ac95e6f635e65224bd38f1e6d2ff13b029e5179fcc534a2787ce82f672b8a739",
  "repo_slug": "shigechika/entraadm-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_shigechika_entraadm_mcp_727128ea/readme"
}