list_installed_apps
list_installed_apps
Lists all installed applications with their names, versions, and install locations. Use when checking if software is installed, finding outdated apps, or before reinstalling an application.
Metadata
Name
list_installed_apps
Updated
3 weeks ago
Source
Risk
Low
Requires consent
false
Affected scope
user
Code
/**
* mcp/skills/listInstalledApps.ts — list_installed_apps skill
*
* Lists all installed applications with their names, versions, and install
* locations. Useful for checking if software is installed, finding outdated
* apps, or before reinstalling an application.
*
* Platform strategy
* -----------------
* darwin `system_profiler SPApplicationsDataType -json` — comprehensive app list
* win32 PowerShell Get-Package | ConvertTo-Json
*
* Smoke test
* npx tsx -r dotenv/config mcp/skills/listInstalledApps.ts [filter]
*/
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: "list_installed_apps",
description:
"Lists all installed applications with their names, versions, and install " +
"locations. Use when checking if software is installed, finding outdated " +
"apps, or before reinstalling an application.",
riskLevel: "low",
destructive: false,
requiresConsent: false,
supportsDryRun: false,
affectedScope: ["user"],
auditRequired: false,
schema: {
filter: z
.string()
.optional()
.describe("Case-insensitive name filter to narrow results"),
includeSystemApps: z
.boolean()
.optional()
.describe("Include Apple/Microsoft system apps. Default: false"),
},
} as const;
// -- Types --------------------------------------------------------------------
interface AppEntry {
name: string;
version: string;
path: string;
vendor: 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: 50 * 1024 * 1024 },
);
return stdout.trim();
}
// -- darwin implementation ----------------------------------------------------
interface SPApplicationEntry {
_name: string;
version?: string;
path?: string;
obtained_from?: string;
// system_profiler uses different key names depending on macOS version
info?: string;
}
interface SPApplicationsData {
SPApplicationsDataType: SPApplicationEntry[];
}
const APPLE_SYSTEM_VENDORS = [
"apple",
"com.apple",
];
function isSystemApp(entry: SPApplicationEntry): boolean {
const vendor = (entry.obtained_from ?? "").toLowerCase();
const name = (entry._name ?? "").toLowerCase();
const path = (entry.path ?? "").toLowerCase();
return (
APPLE_SYSTEM_VENDORS.some((v) => vendor.includes(v)) ||
path.startsWith("/system/") ||
path.startsWith("/library/apple/")
) && !name.includes("xcode"); // keep Xcode as it's developer-relevant
}
async function listInstalledAppsDarwin(
filter: string | undefined,
includeSystemApps: boolean,
): Promise<AppEntry[]> {
const { stdout } = await execAsync(
"system_profiler SPApplicationsDataType -json 2>/dev/null",
{ maxBuffer: 50 * 1024 * 1024 },
);
const data = JSON.parse(stdout) as SPApplicationsData;
const apps = data.SPApplicationsDataType ?? [];
return apps
.filter((entry) => {
if (!includeSystemApps && isSystemApp(entry)) return false;
if (filter) {
return entry._name.toLowerCase().includes(filter.toLowerCase());
}
return true;
})
.map((entry) => ({
name: entry._name,
version: entry.version ?? "unknown",
path: entry.path ?? "unknown",
vendor: entry.obtained_from ?? "unknown",
}))
.sort((a, b) => a.name.localeCompare(b.name));
}
// -- win32 implementation -----------------------------------------------------
interface WinPackage {
Name: string;
Version: string;
Source: string | null;
}
const MICROSOFT_SYSTEM_PREFIXES = [
"microsoft windows",
"windows ",
"microsoft visual c++",
"microsoft .net",
];
function isMicrosoftSystemApp(name: string): boolean {
const lower = name.toLowerCase();
return MICROSOFT_SYSTEM_PREFIXES.some((p) => lower.startsWith(p));
}
async function listInstalledAppsWin32(
filter: string | undefined,
includeSystemApps: boolean,
): Promise<AppEntry[]> {
const ps = `
$ErrorActionPreference = 'SilentlyContinue'
Get-Package | Select-Object Name, Version, Source | ConvertTo-Json -Depth 2 -Compress`.trim();
const raw = await runPS(ps);
if (!raw) return [];
let pkgs: WinPackage[];
try {
const parsed = JSON.parse(raw) as WinPackage | WinPackage[];
pkgs = Array.isArray(parsed) ? parsed : [parsed];
} catch {
return [];
}
return pkgs
.filter((pkg) => {
if (!pkg.Name) return false;
if (!includeSystemApps && isMicrosoftSystemApp(pkg.Name)) return false;
if (filter) {
return pkg.Name.toLowerCase().includes(filter.toLowerCase());
}
return true;
})
.map((pkg) => ({
name: pkg.Name,
version: pkg.Version ?? "unknown",
path: pkg.Source ?? "unknown",
vendor: "unknown",
}))
.sort((a, b) => a.name.localeCompare(b.name));
}
// -- Exported run function ----------------------------------------------------
export async function run({
filter,
includeSystemApps = false,
}: {
filter?: string;
includeSystemApps?: boolean;
} = {}) {
const platform = os.platform();
const apps = platform === "win32"
? await listInstalledAppsWin32(filter, includeSystemApps)
: await listInstalledAppsDarwin(filter, includeSystemApps);
return {
platform,
filter: filter ?? null,
includeSystemApps,
total: apps.length,
apps,
};
}
// -- CLI smoke test -----------------------------------------------------------
if (false) {
run({ filter: process.argv[2] })
.then((r) => console.log(JSON.stringify(r, null, 2)))
.catch((err: Error) => { console.error(err.message); process.exit(1); });
}