check_ad_binding
check_ad_binding
Checks whether the Mac is bound to an Active Directory domain and reports the binding status, domain name, and last successful authentication. Use when diagnosing AD login failures or password sync issues.
Metadata
Name
check_ad_binding
Updated
2 months ago
Source
Risk
Low
Requires consent
false
Affected scope
user
Code
/**
* mcp/skills/checkAdBinding.ts — check_ad_binding skill
*
* Checks whether the Mac is bound to an Active Directory domain and reports
* the binding status, domain name, and last successful authentication. Use
* when diagnosing AD login failures or password sync issues.
*
* Platform strategy
* -----------------
* darwin `dsconfigad -show` to get AD config,
* `dscl /Active\ Directory/ -list /` for domain list
* win32 PowerShell (Get-WmiObject Win32_ComputerSystem).PartOfDomain and
* [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
*
* Smoke test
* npx tsx -r dotenv/config mcp/skills/checkAdBinding.ts
*/
import * as os from "os";
import { exec } from "child_process";
import { promisify } from "util";
import { z } from "zod";
const execAsync = promisify(exec);
// -- Meta ---------------------------------------------------------------------
export const meta = {
name: "check_ad_binding",
description:
"Checks whether the Mac is bound to an Active Directory domain and reports " +
"the binding status, domain name, and last successful authentication. " +
"Use when diagnosing AD login failures or password sync issues.",
riskLevel: "low",
destructive: false,
requiresConsent: false,
supportsDryRun: false,
affectedScope: ["user"],
auditRequired: false,
schema: {},
} as const;
// -- Types --------------------------------------------------------------------
interface AdBindingInfo {
isBound: boolean;
domain: string | null;
domainController: string | null;
lastBindCheck: string | null;
errors: string[];
}
// -- PowerShell helper --------------------------------------------------------
async function runPS(script: string): Promise<string> {
const encoded = Buffer.from(script, "utf16le").toString("base64");
const { stdout } = await execAsync(
`powershell.exe -NoProfile -NonInteractive -EncodedCommand ${encoded}`,
{ maxBuffer: 10 * 1024 * 1024 },
);
return stdout.trim();
}
// -- darwin implementation ----------------------------------------------------
async function checkAdBindingDarwin(): Promise<AdBindingInfo> {
const errors: string[] = [];
let isBound = false;
let domain: string | null = null;
let domainController: string | null = null;
let lastBindCheck: string | null = null;
// Run dsconfigad -show to get AD binding details
try {
const { stdout } = await execAsync(
"dsconfigad -show 2>&1",
{ maxBuffer: 2 * 1024 * 1024 },
);
if (stdout.includes("There is no Active Directory binding")) {
isBound = false;
} else {
// Parse domain
const domainMatch = stdout.match(/Active Directory Domain\s*=\s*(.+)/i);
if (domainMatch) {
domain = domainMatch[1].trim();
isBound = true;
}
// Parse domain controller
const dcMatch = stdout.match(/Preferred Domain controller\s*=\s*(.+)/i);
if (dcMatch) domainController = dcMatch[1].trim();
// Parse last bind check from directory services logs if available
const bindingMatch = stdout.match(/Computer Account\s*=\s*(.+)/i);
if (bindingMatch) lastBindCheck = `Computer account: ${bindingMatch[1].trim()}`;
}
} catch (err) {
const msg = (err as Error).message ?? "dsconfigad failed";
errors.push(msg);
}
// Confirm by listing /Active Directory domains
if (isBound) {
try {
const { stdout } = await execAsync(
`dscl "/Active Directory/" -list / 2>/dev/null`,
{ maxBuffer: 1 * 1024 * 1024, shell: "/bin/bash" },
);
const listed = stdout.trim().split("\n").filter(Boolean);
if (listed.length > 0 && !domain) {
domain = listed[0];
}
} catch (err) {
errors.push(`dscl listing failed: ${(err as Error).message}`);
}
}
return { isBound, domain, domainController, lastBindCheck, errors };
}
// -- win32 implementation -----------------------------------------------------
async function checkAdBindingWin32(): Promise<AdBindingInfo> {
const errors: string[] = [];
const ps = `
$ErrorActionPreference = 'SilentlyContinue'
$cs = Get-WmiObject Win32_ComputerSystem
$isBound = [bool]$cs.PartOfDomain
$domain = if ($isBound) { $cs.Domain } else { $null }
$dc = $null
$lastCheck = $null
if ($isBound) {
try {
$d = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$dc = $d.PdcRoleOwner.Name
$lastCheck = (Get-Date).ToString('o')
} catch {
# domain unreachable
}
}
[PSCustomObject]@{
isBound = $isBound
domain = $domain
domainController = $dc
lastBindCheck = $lastCheck
errors = @()
} | ConvertTo-Json -Compress`.trim();
try {
const raw = await runPS(ps);
const parsed = JSON.parse(raw) as AdBindingInfo;
return parsed;
} catch (err) {
errors.push((err as Error).message);
return { isBound: false, domain: null, domainController: null, lastBindCheck: null, errors };
}
}
// -- Exported run function ----------------------------------------------------
export async function run(_args: Record<string, never> = {}) {
const platform = os.platform();
const info = platform === "win32"
? await checkAdBindingWin32()
: await checkAdBindingDarwin();
return { platform, ...info };
}
// -- CLI smoke test -----------------------------------------------------------
if (false) {
run({})
.then(r => console.log(JSON.stringify(r, null, 2)))
.catch((err: Error) => { console.error(err.message); process.exit(1); });
}