get_agent_version
get_agent_version
Returns the installed version of a security agent. Use to verify the agent is on the expected version or to identify an outdated installation that needs updating.
Metadata
Name
get_agent_version
Updated
2 months ago
Source
Risk
Low
Requires consent
false
Affected scope
user
Code
/**
* mcp/skills/getAgentVersion.ts — get_agent_version skill
*
* Returns the installed version of a security agent. Use to verify the agent
* is on the expected version or to identify an outdated installation that
* needs updating.
*
* Platform strategy
* -----------------
* darwin plutil / jamf / mdatp commands, or reads Info.plist directly
* win32 PowerShell Get-WmiObject Win32_Product
*
* Smoke test
* npx tsx -r dotenv/config mcp/skills/getAgentVersion.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: "get_agent_version",
description:
"Returns the installed version of a security agent. Use to verify the agent " +
"is on the expected version or to identify an outdated installation that needs updating.",
riskLevel: "low",
destructive: false,
requiresConsent: false,
supportsDryRun: false,
affectedScope: ["user"],
auditRequired: false,
schema: {
agent: z
.enum(["crowdstrike", "sentinelone", "jamf", "carbonblack", "cylance", "defender"])
.describe("Security agent to get version for"),
},
} as const;
// -- Types --------------------------------------------------------------------
interface AgentVersionResult {
agent: string;
installedVersion: string | null;
installPath: string | null;
found: boolean;
message: 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: 20 * 1024 * 1024 },
);
return stdout.trim();
}
// -- darwin helper: extract version from Info.plist ---------------------------
async function versionFromPlist(plistPath: string): Promise<string | null> {
try {
const safePath = plistPath.replace(/'/g, `'\''`);
const { stdout } = await execAsync(
`plutil -extract CFBundleShortVersionString raw -o - '${safePath}' 2>/dev/null`,
{ maxBuffer: 1024 * 1024 },
);
const v = stdout.trim();
return v || null;
} catch {
return null;
}
}
// -- darwin implementation ----------------------------------------------------
async function getAgentVersionDarwin(
agent: string,
): Promise<AgentVersionResult> {
let installPath: string | null = null;
let version: string | null = null;
let message = "";
try {
switch (agent) {
case "crowdstrike": {
installPath = "/Applications/Falcon.app";
version = await versionFromPlist(`${installPath}/Contents/Info.plist`);
message = version ? `CrowdStrike Falcon version ${version}` : "Falcon.app found but version unreadable";
break;
}
case "sentinelone": {
// SentinelOne may have version in app bundle name
const safePath = "/Applications".replace(/'/g, `'\''`);
const { stdout } = await execAsync(
`find '${safePath}' -maxdepth 1 -name "SentinelOne*.app" 2>/dev/null`,
{ maxBuffer: 1024 * 1024 },
);
installPath = stdout.trim().split("\n")[0] || null;
if (installPath) {
version = await versionFromPlist(`${installPath}/Contents/Info.plist`);
message = version ? `SentinelOne version ${version}` : "SentinelOne.app found but version unreadable";
} else {
message = "SentinelOne.app not found in /Applications";
}
break;
}
case "jamf": {
installPath = "/usr/local/bin/jamf";
const { stdout } = await execAsync("jamf version 2>/dev/null", { maxBuffer: 1024 * 1024 });
const match = stdout.match(/(\d+\.\d+[\.\d]*)/);
version = match ? match[1] : null;
message = version ? `Jamf version ${version}` : "jamf command found but version unreadable";
break;
}
case "defender": {
installPath = "/Applications/Microsoft Defender.app";
// Try mdatp command first
try {
const { stdout } = await execAsync("mdatp version 2>/dev/null", { maxBuffer: 1024 * 1024 });
const match = stdout.match(/(\d+\.\d+[\.\d]*)/);
version = match ? match[1] : null;
} catch {
version = null;
}
if (!version) {
version = await versionFromPlist(`${installPath}/Contents/Info.plist`);
}
message = version ? `Microsoft Defender version ${version}` : "Defender not found or version unreadable";
break;
}
case "carbonblack": {
installPath = "/Applications/VMware Carbon Black Cloud.app";
version = await versionFromPlist(`${installPath}/Contents/Info.plist`);
message = version ? `Carbon Black version ${version}` : "Carbon Black app found but version unreadable";
break;
}
case "cylance": {
installPath = "/Applications/Cylance/CylanceSvc.app";
version = await versionFromPlist(`${installPath}/Contents/Info.plist`);
message = version ? `Cylance version ${version}` : "CylanceSvc.app found but version unreadable";
break;
}
default:
message = `Unknown agent: ${agent}`;
}
} catch (err) {
message = `Error reading version: ${(err as Error).message}`;
}
return {
agent,
installedVersion: version,
installPath,
found: version !== null || installPath !== null,
message,
};
}
// -- win32 implementation -----------------------------------------------------
const WIN32_AGENT_NAMES: Record<string, string> = {
crowdstrike: "CrowdStrike",
sentinelone: "SentinelOne",
jamf: "Jamf",
carbonblack: "Carbon Black",
cylance: "Cylance",
defender: "Windows Defender",
};
async function getAgentVersionWin32(
agent: string,
): Promise<AgentVersionResult> {
const agentDisplayName = WIN32_AGENT_NAMES[agent] ?? agent;
const ps = `
$ErrorActionPreference = 'SilentlyContinue'
$prod = Get-WmiObject Win32_Product |
Where-Object { $_.Name -match '${agentDisplayName}' } |
Select-Object Name,Version |
Select-Object -First 1
if ($prod) { $prod | ConvertTo-Json -Compress } else { 'null' }`.trim();
const raw = await runPS(ps);
if (!raw || raw === "null") {
return {
agent,
installedVersion: null,
installPath: null,
found: false,
message: `${agentDisplayName} not found in Win32_Product`,
};
}
const parsed = JSON.parse(raw) as { Name: string; Version: string };
return {
agent,
installedVersion: parsed.Version || null,
installPath: null,
found: true,
message: `${parsed.Name} version ${parsed.Version}`,
};
}
// -- Exported run function ----------------------------------------------------
export async function run({
agent,
}: {
agent: "crowdstrike" | "sentinelone" | "jamf" | "carbonblack" | "cylance" | "defender";
}) {
const platform = os.platform();
return platform === "win32"
? getAgentVersionWin32(agent)
: getAgentVersionDarwin(agent);
}
// -- CLI smoke test -----------------------------------------------------------
if (false) {
run({ agent: "crowdstrike" })
.then(r => console.log(JSON.stringify(r, null, 2)))
.catch((err: Error) => { console.error(err.message); process.exit(1); });
}