Multi-tenant Active Directory MCP Server — manage AD via AI assistants
About • Architecture • Quick Start • Tools • Configuration • Security
Active Directory MCP is an open-source Model Context Protocol server that lets AI assistants (Claude, Gemini CLI, ChatGPT via API, etc.) safely manage Active Directory environments.
- 47 tools covering users, groups, computers, OUs, security, audit, and 15 MSP prompt playbooks.
- Three transports: stdio (
server.py), Streamable HTTP via FastMCP (server_http.py), and Streamable HTTP via FastAPI (server_fastapi.py). - Multi-tenant by design: each instance binds to its own AD via
AD_MCP_CONFIG; the same codebase can serve unlimited tenants from one host. - Write-operation guard rails: every mutating tool requires either a per-tenant client confirmation string or an automation Bearer token before touching AD.
- Audit log on every operation: each call records operation name, target, mode (CONFIRMED / AUTOMATION / NO_CONFIRMATION_REQUIRED), and outcome.
All MCP tool names use the ad_* prefix with a descriptive suffix — e.g. ad_list_users_with_filters, ad_create_user_account, ad_disable_computer_account_trust. This avoids collisions when this MCP runs alongside other servers (GLPI, Hudu, etc.) connected to the same AI client.
This MCP is designed to run as one process per tenant, all sharing the same code:
.base-code/ <- this repository (shared source of truth)
src/active_directory_mcp/
ad-config/
ad-config.example.json <- template only (real configs are .gitignored)
<deployment>/ <- one directory per tenant, OUTSIDE this repo
tenant-a/
ad-config/ad-config.json <- real credentials (NEVER committed)
start.sh <- exports AD_MCP_CONFIG and launches the server
tenant-b/
ad-config/ad-config.json
start.sh
Each start.sh exports AD_MCP_CONFIG pointing at that tenant's config and runs python -m active_directory_mcp.server_http on a dedicated port. Update the shared .base-code/ once, restart all tenants — same code, isolated state.
- Python 3.11+
- LDAP/LDAPS reachable from the host
- An AD service account with the permissions required by the operations you plan to expose
git clone https://github.com/DevSkillsIT/Skills-MCP-AD.git
cd Skills-MCP-AD
python -m venv .venv
source .venv/bin/activate # Linux/macOS
# .venv\Scripts\activate # Windows
pip install -e . # installs from pyproject.tomlmkdir -p /etc/ad-mcp
cp ad-config/ad-config.example.json /etc/ad-mcp/ad-config.json
$EDITOR /etc/ad-mcp/ad-config.json # set server, bind_dn, password, base_dn, OUs
chmod 600 /etc/ad-mcp/ad-config.jsonThe example file is the only template kept in git. Any real
ad-config.jsonis blocked by.gitignore(ad-config/*.json+!ad-config/*.example.json).
export AD_MCP_CONFIG=/etc/ad-mcp/ad-config.json
# stdio transport (for direct Claude Desktop / mcp-cli use):
python -m active_directory_mcp.server
# HTTP transport (for Claude Code, Gemini CLI, n8n, etc.):
python -m active_directory_mcp.server_http --host 0.0.0.0 --port 8813 --path /activedirectory-mcpclaude mcp add --transport http ad http://localhost:8813/activedirectory-mcp \
--headers "Authorization: Bearer YOUR_AUTOMATION_TOKEN"~/.gemini/settings.json:
{
"mcpServers": {
"ad": {
"httpUrl": "http://localhost:8813/activedirectory-mcp",
"headers": { "Authorization": "Bearer YOUR_AUTOMATION_TOKEN" },
"timeout": 30000
}
}
}All tools use the ad_* prefix. Tools marked as Write require a confirmation string OR an automation Bearer token.
| Tool | Operation |
|---|---|
ad_get_client_tenant_info |
Return tenant info for this instance (call first) |
ad_list_configured_clients |
List all clients registered in the client registry |
ad_check_client_configuration |
Check if a given client slug has an AD configured |
| Tool | Write | Operation |
|---|---|---|
ad_list_users_with_filters |
— | List users (optionally filtered by OU/criteria) |
ad_get_user_details_by_username |
— | Fetch user attributes by sAMAccountName |
ad_get_user_group_memberships |
— | List groups a user is member of |
ad_create_user_account |
yes | Create a new user |
ad_modify_user_attributes |
yes | Modify user attributes |
ad_delete_user_account_permanently |
yes | Delete a user |
ad_enable_user_account_access |
yes | Enable user account |
ad_disable_user_account_access |
yes | Disable user account |
ad_reset_user_password_forced |
yes | Reset password (force change on next login) |
| Tool | Write | Operation |
|---|---|---|
ad_list_groups_with_filters |
— | List groups |
ad_get_group_details_by_name |
— | Fetch group attributes |
ad_get_group_members_recursive |
— | List members, optionally recursive |
ad_create_group_security_or_distribution |
yes | Create security or distribution group |
ad_modify_group_attributes |
yes | Modify group attributes |
ad_delete_group_permanently |
yes | Delete a group |
ad_add_member_to_group |
yes | Add member |
ad_remove_member_from_group |
yes | Remove member |
| Tool | Write | Operation |
|---|---|---|
ad_list_computers_with_filters |
— | List computers |
ad_get_computer_details_by_name |
— | Fetch computer attributes |
ad_get_inactive_computers_by_days |
— | List computers idle for N+ days |
ad_create_computer_account |
yes | Create computer object |
ad_modify_computer_attributes |
yes | Modify computer attributes |
ad_delete_computer_account_permanently |
yes | Delete computer object |
ad_enable_computer_account_trust |
yes | Enable computer account |
ad_disable_computer_account_trust |
yes | Disable computer account |
ad_reset_computer_password_trust |
yes | Reset computer secure-channel password |
| Tool | Write | Operation |
|---|---|---|
ad_list_organizational_units_hierarchy |
— | List OUs (recursive option) |
ad_get_organizational_unit_details |
— | Fetch OU attributes |
ad_get_organizational_unit_objects |
— | List objects inside an OU |
ad_create_organizational_unit |
yes | Create OU |
ad_modify_organizational_unit_attributes |
yes | Modify OU |
ad_delete_organizational_unit_forced |
yes | Delete OU (force=true to delete non-empty) |
ad_move_organizational_unit_parent |
yes | Move OU to a new parent |
| Tool | Operation |
|---|---|
ad_get_domain_security_policy_info |
Domain info + password/lockout policy |
ad_get_privileged_security_groups |
List privileged groups (Domain Admins, Enterprise Admins, etc.) |
ad_get_user_effective_permissions |
Show effective permissions for a user |
ad_get_inactive_users_by_days |
Users with no logon for N+ days |
ad_get_password_policy_violations |
Accounts violating the password policy |
ad_audit_administrative_accounts |
Audit privileged account hygiene |
| Tool | Operation |
|---|---|
ad_list_msp_prompts |
List the 15 professional MSP playbooks (manager & analyst) |
ad_execute_msp_prompt |
Execute a named playbook with arguments |
See PROMPTS.md for the full prompt catalog (security audit, onboarding, offboarding, password reset playbook, etc.).
| Tool | Operation |
|---|---|
ad_test_ldap_connection_status |
LDAP connectivity probe |
ad_health_check_mcp_server |
Full health check (server + LDAP search test + stats) |
ad_get_mcp_schema_tools_info |
Self-describing schema of all registered tools |
The runtime configuration file path is provided via the AD_MCP_CONFIG environment variable. Schema in ad-config/ad-config.example.json.
| Field | Required | Description |
|---|---|---|
active_directory.server |
yes | Primary LDAP URL, e.g. ldaps://dc.example.com:636 |
active_directory.server_pool |
no | Additional LDAP URLs for failover |
active_directory.bind_dn |
yes | Full DN of the service account |
active_directory.password |
yes | Service account password (keep file at chmod 600) |
active_directory.base_dn |
yes | Base DN, e.g. DC=example,DC=com |
organizational_units.* |
yes | Default OUs for users/groups/computers/service accounts |
security.enable_tls |
no | Force StartTLS / LDAPS |
security.validate_certificate |
no | Verify server certificate against ca_cert_file |
security.require_secure_connection |
no | Refuse to bind over plaintext |
automation.token |
no | Bearer token for unattended write operations |
client.slug |
no | Tenant identifier reported by ad_get_client_tenant_info |
Grant the bind account the minimum delegated rights required by the operations you intend to expose:
- Read-only deployments: "Read all properties" + "List contents" on the domain root is enough.
- User/group write: delegate "Create/Delete objects" + "Write all properties" on the target OUs.
- Password reset: delegate the "Reset password" extended right on the target OUs.
- Computer join/leave: delegate "Create/Delete computer objects" on the computers OU.
Always use a dedicated service account, LDAPS in production, and rotate the password regularly.
Every mutating tool (ad_create_*, ad_modify_*, ad_delete_*, ad_enable_*, ad_disable_*, ad_reset_*, ad_add_*, ad_remove_*, ad_move_*) calls check_write_permission() before reaching LDAP. It accepts the write if one of:
automation_tokenmatchesautomation.tokenin the config — intended for CI / scheduled jobs.client_confirmationmatches the tenant slug — the AI assistant must callad_get_client_tenant_infofirst, read the slug back to the user, and pass that exact string.- The tenant has
require_confirmation_for_writes: false(explicit opt-out, not recommended).
If none of the above is satisfied, the call short-circuits with a permitted: false message and the LDAP write is never attempted.
All operations write a structured log line including: timestamp, tool name, target, confirmation mode (AUTOMATION / CONFIRMED / WRONG_CONFIRMATION / NO_CONFIRMATION_REQUIRED), and success/failure. Logs go wherever logging.file points.
- Real
ad-config.jsonfiles are git-ignored. Only*.example.jsonis tracked. - Never paste a config containing a real
passwordorautomation.tokeninto a chat that's logged or transcribed by a third party. - Rotate
automation.tokenwhenever you regenerate it; treat it as a privileged credential.
# Unit + integration tests
pytest tests/ -v
# Coverage
pytest --cov=src --cov-report=term-missing
# Lint
ruff check .A bundled docker-compose-ad.yml spins up a Samba AD container at 192.168.1.100 plus an MCP container so the integration tests can run against a real LDAP backend without touching production.
| Symptom | Likely cause | Fix |
|---|---|---|
LDAP bind failed |
bad bind_dn / password |
Verify against ldapsearch -H <server> -D '<bind_dn>' -W |
Insufficient permissions |
service account lacks delegated rights | Re-delegate on the target OU |
Certificate verification failed |
self-signed cert without trust | Set ca_cert_file or validate_certificate: false (test only) |
permitted: false on every write |
Missing confirmation/token | Call ad_get_client_tenant_info first, or pass automation_token |
Health degraded |
socket open but LDAP search failed | Check service-account lockout / replication / network ACLs |
- Fork the repo.
- Create a feature branch:
git checkout -b feat/your-feature. - Run tests:
pytest. - Open a PR with a clear description and a link to the relevant issue.
Commits follow Conventional Commits.
MIT — see LICENSE.
- Based on the upstream
alpadalar/ActiveDirectoryMCPproject by Alperen Adalar. - Built on top of
mcp,fastmcp,ldap3, and FastAPI.
- Bug reports: GitHub Issues
- Discussions: GitHub Discussions