adslayer
adslayer lets an AI agent read and change on-premises Active Directory by running sandboxed JavaScript against one domain.
Execute AD scripts: read, filter, count, join, add, modify, delete, move objects; manage ACLs; restore deleted objects from the Recycle Bin.
Manage GPOs via GroupPolicy module: list, get, create, delete, link, unlink, set/remove registry policy values, read security settings (password/lockout, user rights, Restricted Groups, audit policy, Security Options), and backup GPOs.
Search the domain catalogue for schema classes/attributes, confidential attributes, LDAP controls, extended rights, and ADMX policy settings.
Search Microsoft Learn documentation for Active Directory, LDAP, schema, Group Policy, LAPS, delegation, permissions, and Recycle Bin topics.
Manage domain connections: list, add in read or write mode, remove; runs as the signed-in Windows user with no stored password.
Read-mode connections refuse writes; write-mode sends requests and relies on Active Directory permissions. Output is capped around 10,000 tokens.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@adslayerlist all users in the Sales OU"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
adslayer
adslayer is an MCP server that lets an AI agent read and change on-premises Active Directory. The agent writes a short JavaScript script, and adslayer runs it in a sandbox against one domain.
adslayer has three main tools, the same as graphslayer:
executeruns a script against a domain.searchruns a script over the domain's catalogue. The catalogue holds every class and attribute in the schema, the LDAP controls, the extended rights, and the policy settings a GPO can set. adslayer reads the catalogue from the domain the first time you search it and keeps it for the session (ADR-0010).docssearches Microsoft Learn.
It also has connections_list, connection_add and connection_remove.
How it works
adslayer runs on a Windows machine that is joined to the domain. Every call runs as the Windows user who is signed in, over Kerberos with signing and sealing. adslayer stores no password and no certificate. What you can do through adslayer is exactly what your Active Directory account can do (ADR-0002).
The Node code has no LDAP client of its own. adslayer starts a PowerShell process, helper/adslayer-helper.ps1, and keeps it running. The helper sends each call to the domain's PDC emulator (ADR-0003, ADR-0006).
Each script runs in a workerd isolate with no network access. A script can reach only two objects. The ad object reads and writes directory objects. The gpo object reads and changes Group Policy objects through Microsoft's GroupPolicy module (ADR-0007).
const r = await ad.search({ base: "OU=Sales,DC=contoso,DC=local", filter: "(objectClass=user)", attributes: ["sAMAccountName"] });
return r.entries.map(e => e.attributes.sAMAccountName[0]);// Turn on "Do not display the lock screen" in a GPO
await gpo.set("Workstation Baseline", { key: "HKLM\\Software\\Policies\\Microsoft\\Windows\\Personalization", valueName: "NoLockScreen", type: "DWord", value: 1 });gpo.get also returns a GPO's security settings: password and lockout policy, user rights, Security Options, Restricted Groups and audit policy. These are read-only for now.
Related MCP server: adds-mcp
Installation
Follow these steps in order on the Windows machine where your AI agent runs. Each step says how to check it and how to fix it. Run the commands in PowerShell as yourself, not as an administrator, except where a step says otherwise.
Before you start
The machine has to be a member of the domain you want to reach. A domain controller works too.
adslayer acts as the Windows user who is signed in. Sign in as the user whose Active Directory rights you want your AI agent to have. adslayer never asks for a password.
Steps 2 to 4 may need an administrator, because they install software.
1. Check that the machine is on the domain
(Get-CimInstance Win32_ComputerSystem).DomainThis should print your domain's DNS name, for example contoso.local. If it prints WORKGROUP, the machine is not joined to a domain, and adslayer cannot work on it.
2. Install Node.js 22 or newer
Check:
node --versionIf this prints v22 or higher, go to step 3. If it prints an error or a lower version, install Node.js LTS. Then close PowerShell, open it again, and check again.
winget install OpenJS.NodeJS.LTS3. Install the Microsoft Visual C++ Redistributable
adslayer runs each script in a sandbox, and the sandbox cannot start without this runtime. Windows Server does not include it.
Check:
Test-Path "$env:SystemRoot\System32\vcruntime140_1.dll"If this prints True, go to step 4. If it prints False, install it with the command below, or download it from https://aka.ms/vs/17/release/vc_redist.x64.exe.
winget install Microsoft.VCRedist.2015+.x644. Install Group Policy Management
adslayer needs this to read and change GPOs. Domain controllers already have it.
Check:
Test-Path "$env:SystemRoot\System32\WindowsPowerShell\v1.0\Modules\GroupPolicy"If this prints True, go to step 5. If it prints False, open PowerShell as administrator and run the command for your version of Windows.
On Windows 10 or 11:
Add-WindowsCapability -Online -Name Rsat.GroupPolicy.Management.Tools~~~~0.0.1.0On Windows Server:
Install-WindowsFeature GPMCYou do not need to install PowerShell. adslayer uses Windows PowerShell 5.1, which comes with Windows, or PowerShell 7 if you have it.
5. Add your domain
npx -y adslayer connect contoso.localUse your own domain name from step 1. This adds the domain in read mode, so the agent can read but not change anything. The first run downloads adslayer from npm before it adds the domain. It should print Added "contoso.local" (contoso.local), mode read.
To let the agent make changes too, add the domain in write mode. See Connections before you do.
npx -y adslayer connect contoso.local --alias contoso-write --mode write6. Add adslayer to your AI agent
For Claude Code, run:
claude mcp add adslayer -- npx -y adslayerFor Claude Desktop and other AI agents, add this to the agent's config file. For Claude Desktop on Windows, the file is %APPDATA%\Claude\claude_desktop_config.json.
{
"mcpServers": {
"adslayer": {
"command": "npx",
"args": ["-y", "adslayer"]
}
}
}Then restart your AI agent.
7. Check that it works
Ask your AI agent:
Use adslayer to run
return await ad.whoami();against contoso.local.
The answer should show your own account, for example u:CONTOSO\jane, and the name of the domain controller that adslayer sends its calls to. If it shows an error, see Problems.
Problems
What you see | What to do |
| Run adslayer on Windows, on a machine joined to the domain. See step 1. |
| Do step 3, then restart your AI agent. |
| Do step 5. Run |
| Do step 4, then restart your AI agent. |
| Active Directory refused the change for your account. adslayer can do only what your account can do. |
Your AI agent does not list the adslayer tools | Restart your AI agent. In Claude Code, run |
Update
npx keeps a copy of adslayer and may keep using it after a new version comes out. To get the newest version, close your AI agent, delete the folder %LOCALAPPDATA%\npm-cache\_npx, and start the agent again.
Uninstall
Follow these steps on the machine where you installed adslayer. Uninstalling changes nothing in Active Directory. adslayer adds nothing to the domain itself, and any changes your AI agent made through adslayer stay in place.
1. Remove adslayer from your AI agent
For Claude Code, run:
claude mcp remove adslayerThen run claude mcp list. adslayer should no longer be in the list.
For Claude Desktop and other AI agents, open the agent's config file and delete the "adslayer" entry under mcpServers. For Claude Desktop on Windows, the file is %APPDATA%\Claude\claude_desktop_config.json. Then restart your AI agent.
2. Delete your list of domains
Remove-Item -Recurse -Force "$env:USERPROFILE\.adslayer"This folder holds only the domains you added with connect. It holds no passwords.
3. Delete the copy that npx downloaded
Remove-Item -Recurse -Force "$env:LOCALAPPDATA\npm-cache\_npx"This deletes every package that npx has downloaded, not only adslayer. For any other tool that runs through npx, npx downloads it again the next time it runs.
4. Delete GPO backups, if you made any
gpo.backup writes each backup to the folder on this machine that the script named. adslayer does not keep a list of these folders. A backup folder holds a manifest.xml file and one folder per GPO, named by the GPO's id in braces. Delete a backup only if you no longer need it, because it is the only way to restore that GPO to the state it was in.
5. Decide whether to keep the software from installation steps 2 to 4
Other programs on this machine may use Node.js, the Visual C++ Redistributable or Group Policy Management. Keep them unless you installed them only for adslayer. To remove them, run PowerShell as administrator.
winget uninstall OpenJS.NodeJS.LTS
winget uninstall Microsoft.VCRedist.2015+.x64To remove Group Policy Management on Windows 10 or 11, run:
Remove-WindowsCapability -Online -Name Rsat.GroupPolicy.Management.Tools~~~~0.0.1.0On Windows Server, run Uninstall-WindowsFeature GPMC. Do not remove it from a domain controller, because the people who manage the domain use it there.
Connections
A connection is one domain, stored with its alias and its mode in %USERPROFILE%\.adslayer\connections.json. It holds no password.
npx -y adslayer connect contoso.local # read mode
npx -y adslayer connect contoso.local --alias corp --mode write
npx -y adslayer connections
npx -y adslayer disconnect corpThe agent can also add a connection with the connection_add tool, in either mode (ADR-0005). If you want a person to decide when writes are allowed, add only read connections and deny connection_add calls in your MCP client.
Writes
A read connection refuses every add, modify, delete, move, addAce and removeAce, and every GPO change, before anything is sent. It can still read permissions with getAcl. gpo.backup is allowed, because it changes nothing in the domain. That mode check is the only limit adslayer puts on a write. A write connection sends whatever the script asks for, and Active Directory permissions decide the rest (ADR-0004).
To bring back a deleted object, a script finds it in the Recycle Bin with ad.search and the showDeleted control, then restores it with ad.modify and the same control. A read connection can find deleted objects but cannot restore them.
adslayer does not back anything up, show a preview, or keep its own log. The yoloslayer skills hold those steps. Without them, an agent gets no backup and no preview. Active Directory's own security log, with Directory Service Changes auditing turned on, records each change.
Where results go
Whatever a script returns goes into the model's context, so it goes to your model provider. With adslayer that is usually directory data, e.g., names and group memberships. Make sure your agreement with the domain's owner covers this (ADR-0009).
Develop
npm test # runs on any OS; LDAP calls go to a fake helper
npm run typecheckspike/ holds the scripts that test adslayer against the lab domain controller from azure-ad-lab. spike/run-on-lab.ps1 tests the helper alone. spike/run-e2e-on-lab.ps1 installs the packed server on the DC and drives it with a real MCP client.
License
MIT
Available Tools
6 toolsconnection_addAdd a domain connectionAIdempotent
Add an Active Directory domain by its DNS name. No sign-in: every call runs as the Windows user this server runs as, so that user's own permissions apply. A read connection refuses every add, modify, delete and move; choose write only when the person wants changes made. Adding an alias that exists replaces it.
| Name | Required | Description | Default |
|---|---|---|---|
| mode | No | Defaults to read. | |
| alias | No | Short name for the connection. Defaults to the domain name. | |
| domain | Yes | The domain's DNS name, e.g. contoso.local. |
Output Schema
| Name | Required | Description |
|---|---|---|
| connection | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already give the safety profile (readOnlyHint=false, destructiveHint=false, idempotentHint=true), but the description adds genuinely new behavior: no sign-in, execution under the server's Windows user, and that user's permissions governing every call. It also discloses the alias-overwrite side effect. Missing only things like failure modes when the domain is unreachable.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four tight sentences, front-loaded with the action and resource, then credentials model, then mode guidance, then the overwrite caveat. No filler or restatement of the title.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be explained. The description covers the pieces an agent must know before calling: auth identity, permission inheritance, mode selection, and overwrite behavior. Nothing material is left for the agent to guess.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3 and mode/alias/domain descriptions already exist in the schema. The description adds meaning beyond it by defining the read-vs-write consequence of 'mode' and the replace-on-collision semantics of 'alias', which the schema does not state.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Add an Active Directory domain by its DNS name'), including the identifying key used to add it. It clearly reads as the inverse of the sibling connection_remove and distinct from connections_list. No ambiguity about what the tool acts on.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly tells the agent how to choose the mode: a read connection refuses every mutation, so choose write only when changes are actually wanted. It also warns that an existing alias is replaced. It stops short of naming sibling alternatives (e.g., connection_remove for undo), so it is clear context rather than full when/when-not routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
connection_removeRemove a domain connectionBDestructiveIdempotent
Remove a stored domain connection. Nothing in the domain changes.
| Name | Required | Description | Default |
|---|---|---|---|
| alias | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| removed | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare destructiveHint=true, idempotentHint=true, and readOnlyHint=false, so the safety profile is covered. The description adds genuinely useful context beyond that: 'Nothing in the domain changes,' which tells the agent the destruction is limited to the stored connection and does not affect live domain data — exactly the reassurance an agent needs before calling a destructive tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences with zero filler. The action is front-loaded and the risk-relevant scope note follows immediately.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be explained, and the annotations plus the 'nothing in the domain changes' note cover the destructive/idempotent profile. The only shortfall is the undocumented 'alias' parameter, which is a minor gap for a single-parameter tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% and the description never mentions the single required parameter 'alias'. The agent must infer from the parameter name alone that it identifies the stored connection to remove; the description does not compensate for the missing schema documentation as the low-coverage rule requires.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (remove) and a specific resource (a stored domain connection), so the agent knows exactly what is deleted. It does not explicitly differentiate itself from connection_add or connections_list, though the name/verb pairing makes the distinction easy to infer.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No indication of when to use this versus connection_add or connections_list, and no prerequisites (e.g., that the connection must already exist) are stated. The only guidance-like content is the scoping note about the domain, which is a behavioral clarification rather than usage routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
connections_listList domain connectionsARead-onlyIdempotent
List the Active Directory domains this server can reach, with each one's mode. Use the alias or the domain name as the domain argument of execute.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| connections | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, destructiveHint=false and openWorldHint=false, so the safety profile is fully covered. The description adds the scoping notion of 'domains this server can reach' and that a mode is returned, but says nothing about auth requirements, error/empty behavior, or latency for an environment-probing call. With annotations carrying the load, a 3 is appropriate.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences: what is returned first, then how to use it. No filler, no restatement of the title, and the actionable chaining hint is last.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so the return shape and the 'mode' field need no elaboration, and the annotation set covers the safety profile. The description is complete enough for a zero-parameter read; only the edge case of an empty connection list is unaddressed.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Zero input parameters, so the baseline is 4. The only parameter-like mention (the domain argument of execute) belongs to a different tool, and the description correctly frames it as how to use this tool's output rather than as an input here.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (List) and resource (Active Directory domains this server can reach) plus the returned attribute (mode). This cleanly separates it from the mutation siblings connection_add/connection_remove and the query siblings search/execute.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly tells the agent how to consume the result: 'Use the alias or the domain name as the domain argument of execute.' That is real chaining guidance rather than an implied context, though it offers no when-not-to-use or alternative-selection rule among the other siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
docsSearch Microsoft LearnARead-onlyIdempotent
Search the Microsoft Learn documentation. Use it to answer how something in Active Directory works before or instead of reading a domain: Active Directory Domain Services, LDAP and its controls, the schema, Group Policy and its settings, Windows LAPS, delegation and permissions, and the Recycle Bin.
Returns the most relevant passages, each with its page title and link. To find an attribute or a policy setting, use search.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | What to look up, in plain words. |
Output Schema
| Name | Required | Description |
|---|---|---|
| results | Yes | |
| truncated | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, openWorld and non-destructive, so the safety profile is covered. The description adds useful context beyond them: it clarifies the tool queries external Microsoft documentation and that results are 'passages, each with its page title and link', setting expectations for an open-world read. It doesn't discuss rate limits or result limits, hence not a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose and usage come first, then the alternative routing and return shape. The middle enumeration of AD topics is longer than strictly necessary but does usefully bound the searchable scope, so it earns its place. Slightly verbose but well front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, the return values needn't be detailed, and annotations cover the safety profile. The description still supplies the usage routing, content scope, and result shape, leaving nothing an agent needs in order to call this correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
There is a single parameter with 100% schema description coverage ('What to look up, in plain words'), so the schema already carries the meaning. The description implies the input is a natural-language lookup but adds no syntax, formatting, or example beyond the schema. Baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Search the Microsoft Learn documentation') and enumerates the subject scope (AD DS, LDAP, schema, Group Policy, LAPS, delegation, Recycle Bin). It also explicitly distinguishes itself from the sibling `search`, which handles attributes and policy settings, so an agent can pick correctly without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit when-to-use ('to answer how something in Active Directory works before or instead of reading a domain') and names the alternative with its triggering condition ('To find an attribute or a policy setting, use search'). This is exactly the when/when-not/alternative pattern.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
executeRun an Active Directory scriptADestructive
Run a JavaScript script against one Active Directory domain. Use this for any read, filter, join, count, or change. Write the body of an async function and "return" the value you want back. Only what you return (and console.log) comes back to you, so filter and pick attributes inside the script. Output is capped at about 10,000 tokens.
Every call runs as the Windows user the server runs as, over Kerberos with signing and sealing, against the domain's PDC emulator, so Active Directory's own permissions decide what succeeds. Use connections_list to find domain aliases. Name objects by DN. Pass your own attributes; without them you get name, objectClass and sAMAccountName. A connection added in read mode refuses add, modify, delete, move, addAce and removeAce.
Available in the script: declare const ad: { // One object by DN, or null if there is none. Default attributes: name, objectClass, sAMAccountName. ["*"] for all. get(dn: string, attributes?: string[], opts?: { controls?: Controls }): Promise<Entry | null>; // Objects matching an LDAP filter. base defaults to the domain head, scope to "sub", max to 1000 (cap 20000). // more is true when there were more than max. Multi-valued attributes like member come back whole. search(opts: { filter?: string; base?: string; scope?: "base" | "one" | "sub"; attributes?: string[]; max?: number; controls?: Controls }): Promise<{ entries: Entry[]; more: boolean }>; // The account the server runs as, and the PDC emulator it talks to. whoami(): Promise<{ user: string; pdc: string; defaultNamingContext: string }>; // Writes. A read-mode connection refuses these four before anything is sent. add(dn: string, attributes: Record<string, Value | Value[]>): Promise<{ dn: string }>; // include objectClass modify(dn: string, changes: Array<{ op: "add" | "replace" | "delete"; attribute: string; values?: Value | Value[] }>, opts?: { controls?: Controls }): Promise<{ dn: string }>; delete(dn: string, opts?: { tree?: boolean }): Promise<{ dn: string }>; move(dn: string, to: { newParent?: string; newName?: string }): Promise<{ dn: string }>; // newName is an RDN, e.g. "CN=New Name" // Permissions. getAcl reads the owner and DACL, which needs no admin rights. A read-mode connection refuses addAce and removeAce. getAcl(dn: string): Promise<{ dn: string; owner: Principal; protected: boolean; aces: Ace[] }>; // objectType: an attribute, class or extended right name (find it with search) or a GUID. inheritedObjectType: a class. addAce(dn: string, ace: NewAce): Promise<{ dn: string }>; // Removes the entry that matches exactly; an Ace from getAcl can be passed back as it is. removed is false if none matched. removeAce(dn: string, ace: NewAce | Ace): Promise<{ dn: string; removed: boolean }>; }; // Group Policy, through Microsoft's GroupPolicy module on the PDC emulator. g is a GPO's name or id. // A read-mode connection refuses create, delete, link, unlink, set and remove. declare const gpo: { list(): Promise<Gpo[]>; // With where it is linked and every registry policy value it sets. get(g: string): Promise<Gpo & { links: Link[]; computerSettings: Setting[]; userSettings: Setting[]; securitySettings: SecuritySettings }>; create(name: string, opts?: { comment?: string }): Promise; delete(g: string): Promise<{ id: string; name: string; deleted: true }>; // Creates the link, or changes it if the GPO is already linked there. target is an OU or the domain DN. link(g: string, target: string, opts?: { enabled?: boolean; enforced?: boolean; order?: number }): Promise<{ id: string; links: Link[] }>; unlink(g: string, target: string): Promise<{ id: string; links: Link[] }>; // key starts with HKLM\ (computer) or HKCU\ (user). Find keys with search over catalogue.policies. set(g: string, s: { key: string; valueName: string; type: "String" | "ExpandString" | "DWord" | "QWord" | "MultiString"; value: string | number | string[] }): Promise; remove(g: string, key: string, valueName?: string): Promise; // Backup-GPO to a folder on the machine adslayer runs on. Allowed on a read connection. backup(g: string, path: string): Promise<{ id: string; backupId: string; path: string; timestamp: string }>; }; interface Gpo { id: string; name: string; status: string; created: string; modified: string; computerVersion: number; userVersion: number; wmiFilter: string | null } interface Link { target: string; enabled: boolean; enforced: boolean; order: number } interface Setting { key: string; valueName: string; type: string; value: unknown } // From the GPO's security template (GptTmpl.inf). Read-only for now. Empty when the GPO sets none. interface SecuritySettings { systemAccess: Record<string, number | string>; // password and lockout policy, e.g. MinimumPasswordLength, LockoutBadCount eventAudit: Record<string, number | string>; privilegeRights: Record<string, GpoPrincipal[]>; // user rights, e.g. SeInteractiveLogonRight groupMembership: Array<{ group: GpoPrincipal; members?: GpoPrincipal[]; memberOf?: GpoPrincipal[] }>; // Restricted Groups registryValues: Record<string, { type: number; value: number | string }>; // Security Options, keyed MACHINE... other: Record<string, Record<string, string>>; } interface GpoPrincipal { sid: string | null; name: string | null } // Every attribute is an array. GUIDs and SIDs are strings; other binary values are { base64 }. interface Entry { dn: string; attributes: Record<string, Array<string | { base64: string }>> } type Value = string | number | boolean | { base64: string }; interface Principal { sid: string; name: string | null } // name e.g. "CONTOSO\Helpdesk" // rights: e.g. "GenericAll", "ReadProperty", "WriteProperty", "ExtendedRight", "CreateChild", "DeleteChild", "Delete", "DeleteTree", "WriteDacl" // inheritance: "All" = this object and everything below it; "Descendents" = only below it. type Inheritance = "None" | "All" | "Descendents" | "SelfAndChildren" | "Children"; interface Ace { principal: Principal; type: "allow" | "deny"; rights: string[]; objectType?: string; inheritedObjectType?: string; inheritance: Inheritance; inherited: boolean } interface NewAce { principal: string; type: "allow" | "deny"; rights: string[]; objectType?: string; inheritedObjectType?: string; inheritance?: Inheritance } // principal: "CONTOSO\Helpdesk" or a SID // showDeleted: see and restore objects in the Recycle Bin (CN=Deleted Objects). Restore = modify with it. interface Controls { showDeleted?: true } // Calls may run in parallel with Promise.all. Each run may make at most 200 calls.
Examples: // Enabled users in an OU, by name const r = await ad.search({ base: "OU=Sales,DC=contoso,DC=local", filter: "(&(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))", attributes: ["sAMAccountName"] }); return r.entries.map(e => e.attributes.sAMAccountName[0]);
// How many members a group has, nested groups not expanded const g = await ad.get("CN=Helpdesk,OU=Groups,DC=contoso,DC=local", ["member"]); return g?.attributes.member?.length ?? 0;
// Disable a user (514 = normal account + disabled) return await ad.modify("CN=Jane Doe,OU=Sales,DC=contoso,DC=local", [{ op: "replace", attribute: "userAccountControl", values: "514" }]);
// Reset a password. unicodePwd takes the password in double quotes as UTF-16LE bytes. Add pwdLastSet "0" to force a change at next sign-in. const pw = '"' + newPassword + '"'; let b = ""; for (let i = 0; i < pw.length; i++) { const c = pw.charCodeAt(i); b += String.fromCharCode(c & 255, c >> 8); } return await ad.modify("CN=Jane Doe,OU=Sales,DC=contoso,DC=local", [{ op: "replace", attribute: "unicodePwd", values: { base64: btoa(b) } }, { op: "replace", attribute: "pwdLastSet", values: "0" }]);
// Restore a deleted user from the Recycle Bin to where it was. Delete isDeleted first, then set the new DN. const controls = { showDeleted: true }; const gone = (await ad.search({ base: "CN=Deleted Objects,DC=contoso,DC=local", filter: "(&(isDeleted=TRUE)(sAMAccountName=jdoe))", attributes: ["lastKnownParent", "msDS-LastKnownRDN"], controls })).entries[0]; const to = "CN=" + gone.attributes["msDS-LastKnownRDN"][0] + "," + gone.attributes.lastKnownParent[0]; return await ad.modify(gone.dn, [{ op: "delete", attribute: "isDeleted" }, { op: "replace", attribute: "distinguishedName", values: to }], { controls });
// Who can reset passwords in an OU: full control, all extended rights, or the Reset Password right const acl = await ad.getAcl("OU=Sales,DC=contoso,DC=local"); return acl.aces.filter(a => a.type === "allow" && (a.rights.includes("GenericAll") || (a.rights.includes("ExtendedRight") && (!a.objectType || a.objectType === "User-Force-Change-Password")))).map(a => a.principal.name ?? a.principal.sid);
// Password and lockout policy for the domain, from the Default Domain Policy's security settings const sa = (await gpo.get("Default Domain Policy")).securitySettings.systemAccess; return { minLength: sa.MinimumPasswordLength, lockoutAfter: sa.LockoutBadCount };
// Protect an OU from accidental deletion, as the admin tools do. AD allows a delete with Delete on the object or // DeleteChild on its parent, so deny both. To undo, removeAce the first; the parent's deny also protects its other children. await ad.addAce("OU=Sales,DC=contoso,DC=local", { principal: "S-1-1-0", type: "deny", rights: ["Delete", "DeleteTree"] }); return await ad.addAce("DC=contoso,DC=local", { principal: "S-1-1-0", type: "deny", rights: ["DeleteChild"] });
| Name | Required | Description | Default |
|---|---|---|---|
| code | Yes | Body of an async JavaScript function. Use await ad.* and return a value. | |
| domain | Yes | Connection alias or the domain's DNS name. See connections_list. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | Yes | |
| logs | Yes | |
| calls | Yes | |
| error | No | |
| domain | Yes | |
| result | No | |
| truncated | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Far exceeds the annotations: it discloses the auth model (runs as the server's Windows user over Kerberos with signing and sealing), the target endpoint (PDC emulator), that AD's own ACLs decide success, the default attribute set, read-mode write refusal, the ~10,000-token output cap, the 200-call-per-run limit, and that calls may run in parallel via Promise.all. Annotations (readOnly=false, destructive=true, openWorld=true, idempotent=false) are fully consistent with this and the description adds the operational context they omit.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
It is long, but front-loaded: purpose and scope first, then execution model, then the full `ad`/`gpo` API reference and worked examples. The length is defensible because the tool is an arbitrary-code surface with a large implicit API, yet some per-method commentary duplicates what the sibling `docs` tool likely holds.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a code-execution tool with an output schema and no nested params, nothing an agent needs is missing: it covers identity/permissions, targeting, defaults, limits, the full ad/gpo surface with types, and concrete examples including password reset, recycle-bin restore, ACL inspection and deny-ACE protection.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and both parameters are already documented there, so the baseline is 3. The description goes beyond it by defining the contract of `code` (body of an async function, only the returned value and console.log come back, filter and pick attributes inside the script) and by clarifying the `domain` value against connections_list, which meaningfully shapes how the parameter must be written.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening sentence pins a specific verb and resource ('Run a JavaScript script against one Active Directory domain') and then enumerates the scope of work it covers (read, filter, join, count, change), so an agent knows this is a general-purpose AD execution surface rather than a single operation. The title and body align, and no sibling could be mistaken for it once this is read.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives clear when-to-use guidance ('Use this for any read, filter, join, count, or change') and routes the agent to connections_list for domain aliases, plus a meaningful exclusion ('A connection added in read mode refuses add, modify, delete, move, addAce and removeAce'). It never explicitly contrasts itself with the sibling `search` or `docs`, so an agent could still wonder whether a simple lookup belongs here or in `search`.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
searchSearch the AD catalogueARead-onlyIdempotent
Search one domain's Active Directory catalogue: every class and attribute in its schema, which attributes are confidential, each attribute's syntax and whether it holds one value, which attributes a class may hold, the LDAP controls the domain controller supports, and the extended rights. Use it before execute to find the right attribute or class rather than guessing.
The catalogue is read from the domain the first time you search it, which takes a few seconds, and kept for the session. Pass refresh: true after a schema change. Your script runs with no network and cannot reach the domain.
Write the body of an async function and "return" the value you want back. Output is capped at about 10,000 tokens, so filter inside the script.
Available in the script: declare const catalogue: { domain: string; readAt: string; dc: string; schemaNamingContext: string; forestFunctionality: number; domainFunctionality: number; // 10 = Windows Server 2025 attributes: Record<string, { // keyed by lDAPDisplayName, e.g. "member" oid: string; guid?: string; syntax: string; // e.g. "DN", "UnicodeString", "LargeInteger", "SID" single: boolean; confidential?: true; indexed?: true; systemOnly?: true; linkID?: number; range?: [number | null, number | null]; propertySet?: string; description?: string; }>; classes: Record<string, { // keyed by lDAPDisplayName, e.g. "user" oid: string; guid?: string; kind: "structural" | "abstract" | "auxiliary" | "88"; parent: string; must: string[]; may: string[]; // including inherited and auxiliary-class attributes auxiliary: string[]; possibleSuperiors: string[]; description?: string; }>; controls: Array<{ oid: string; name?: string }>; // what the DC supports extendedRights: Record<string, { // keyed by name, e.g. "User-Force-Change-Password" displayName: string; guid: string; kind: "control" | "propertySet" | "validatedWrite" | "other"; appliesTo: string[]; }>; // Policy settings from the ADMX files (the domain's central store, or the local PolicyDefinitions). // To set one with gpo.set, prefix key with HKLM\ for class Machine or HKCU\ for class User. // A policy's own valueName is set to 1 to enable it; elements are its extra values. policies: Array<{ name: string; displayName: string; class: "Machine" | "User" | "Both"; key: string; valueName?: string; category: string; file: string; elements: Array<{ type: string; id?: string; valueName?: string; key?: string }> }>; policiesSource?: string; policiesError?: string; };
Examples: // Confidential attributes return Object.entries(catalogue.attributes).filter(([, a]) => a.confidential).map(([n]) => n);
// Attributes about passwords, with their syntax return Object.entries(catalogue.attributes).filter(([n]) => /pwd|password/i.test(n)).map(([n, a]) => ({ n, syntax: a.syntax, single: a.single }));
// Everything a user object may hold return catalogue.classes.user.may.length;
| Name | Required | Description | Default |
|---|---|---|---|
| code | Yes | Body of an async JavaScript function over `catalogue`. Return a value. | |
| domain | Yes | Connection alias or the domain's DNS name. See connections_list. | |
| refresh | No | Read the catalogue from the domain again. Defaults to false. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | Yes | |
| logs | Yes | |
| error | No | |
| domain | Yes | |
| result | No | |
| truncated | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well past the annotations: first read is slow (a few seconds) and then cached per session, refresh semantics, ~10,000 token output cap with 'filter inside the script' advice, and the no-network sandbox constraint. These are execution-relevant traits the readOnly/idempotent hints cannot convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose and the search-before-execute rule are front-loaded, but the definition is long and carries a full TypeScript type block plus three examples. Every part is arguably load-bearing for writing a correct script, yet the examples could be trimmed without losing an agent's ability to call it.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a code-execution tool with an output schema, the description supplies exactly what is missing elsewhere: the catalogue's structure, its caching lifecycle, network isolation, and the output cap. Nothing an agent needs to write a valid script is absent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description contributes real meaning beyond it: `code` must be the body of an async function that returns a value, and the full shape of the injected `catalogue` object is documented with worked examples. `refresh` and `domain` are covered by the schema description, so this stays a 4 rather than a 5.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Names a specific verb and resource (search one domain's AD catalogue) and enumerates exactly what is covered: classes, attributes, confidentiality, syntax, single-valuedness, class-attribute links, LDAP controls, extended rights. It also routes the agent relative to its sibling: 'Use it before execute to find the right attribute or class rather than guessing.'
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit sequencing guidance (search before execute), an explicit refresh condition ('Pass refresh: true after a schema change'), and an exclusion ('Your script runs with no network and cannot reach the domain'). The agent knows when this tool is the right one and when it must fall back.
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.
6 tool updates
v0.2.0- First observed
connection_add - First observed
connection_remove - First observed
connections_list - First observed
docs - First observed
execute - First observed
search
TDQS
Scored across 6 tools
The tools split cleanly into connection management (connection_add/remove/list), informational lookups (docs, search), and domain operations (execute). The main risk is mild confusion between docs (Microsoft Learn) and search (domain schema catalogue), but their descriptions clearly delimit each to a different information source, so an agent can pick correctly.
There is a mix: bare verbs (docs, execute, search) alongside noun_verb-prefixed names (connection_add, connection_remove, connections_list). The connection trio is also internally inconsistent in plurality (connection_ vs connections_), so conventions are readable but not uniform.
Six tools is a lean, well-scoped set for an AD management server, with the heavy lifting consolidated into the execute mega-tool. It is slightly thin, but each tool has a distinct role, so nothing feels redundant.
Coverage is broad: schema/docs discovery, full CRUD and move via execute, ACL read/write (getAcl, addAce, removeAce), and a thorough GPO lifecycle (list, get, create, delete, link, unlink, set, remove, backup). Minor gaps remain, such as GPO restore and write access to security-template settings, but core admin workflows are covered.
Maintenance
Related MCP Connectors
Give your AI hands. Identity, credential vault, and API gateway for autonomous agents.
Git-backed platform for skills, tools, and context for AI agents
A registry of AI agent tools — MCP servers, APIs, CLIs, SDKs — kept current by automated ingestion.
Governed memory and workspace for any AI: tasks, calendar, mail and pages, with per-action consent.
Related MCP Servers
- FlicenseAqualityCmaintenanceProvides AI assistants with unified access to on-prem Active Directory via LDAP and Azure AD / Entra ID through the Microsoft Graph API. It enables comprehensive management and search of users, groups, computers, and cloud devices using 18 specialized tools.18-
- FlicenseNot gradedqualityBmaintenanceProvides read-only tools for monitoring and investigating Active Directory Domain Services, including users, groups, OUs, computers, GPOs, and domain metadata via LDAPS.-
- FlicenseNot gradedqualityCmaintenanceEnables local AI models to perform defensive cybersecurity analysis through narrowly scoped read-only tools for host posture, Windows security operations, file/IOC triage, code scanning, and allowlisted filesystem/network access while enforcing boundaries and audit trails.-
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to safely perform file operations, run terminal sessions, manage processes, search content, and inspect Git repositories on a local Windows machine under configurable permission and audit controls.Apache 2.0