get_vpn_profiles

get_vpn_profiles

Lists all configured VPN profiles including their type, server, and last used date. Use when reconnecting to VPN or diagnosing which profile to repair.

Metadata

Name

get_vpn_profiles

Updated

2 weeks ago

Source

GitHub source

Risk

Low

Requires consent

false

Affected scope

user

Code

/**
 * mcp/skills/getVpnProfiles.ts — get_vpn_profiles skill
 *
 * Lists all configured VPN profiles including their type, server, and last
 * used date. Use when reconnecting to VPN or diagnosing which profile to repair.
 *
 * Platform strategy
 * -----------------
 * darwin  `scutil --nc list` for all Network Configuration VPN entries; checks
 *         for AnyConnect profiles in /opt/cisco/anyconnect/profile/ and
 *         GlobalProtect in /Library/Application Support/Palo Alto Networks/GlobalProtect/
 * win32   PowerShell Get-VpnConnection (per-user and all-user)
 *
 * Smoke test
 *   npx tsx -r dotenv/config mcp/skills/getVpnProfiles.ts
 */

import * as os       from "os";
import * as fs       from "fs/promises";
import * as nodePath from "path";
import { exec }      from "child_process";
import { promisify } from "util";
import { z }         from "zod";

import { enumerateVendorVpnProfilesDarwin, WIN32_VPN_VENDOR_PROCS } from "./_shared/vpnProfiles";

const execAsync = promisify(exec);

// -- Meta ---------------------------------------------------------------------

export const meta = {
  name: "get_vpn_profiles",
  description:
    "Lists all configured VPN profiles including their type, server, and last " +
    "used date. Use when reconnecting to VPN or diagnosing which profile to repair.",
  riskLevel:       "low",
  destructive:     false,
  requiresConsent: false,
  supportsDryRun:  false,
  affectedScope:   ["user"],
  auditRequired:   false,
  tccCategories:   ["FullDiskAccess"],
  schema: {} as Record<string, z.ZodTypeAny>,
} as const;

// -- Types --------------------------------------------------------------------

interface VpnProfile {
  name:        string;
  type:        string;
  server:      string | null;
  protocol:    string | null;
  isConnected: boolean;
  lastUsed:    string | null;
}

interface GetVpnProfilesResult {
  profiles: VpnProfile[];
}

// -- 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 implementation ----------------------------------------------------

async function getVpnProfilesDarwin(): Promise<GetVpnProfilesResult> {
  const profiles: VpnProfile[] = [];

// Parse scutil --nc list for native macOS VPN profiles
  let scutilOut = "";
  try {
    ({ stdout: scutilOut } = await execAsync("scutil --nc list 2>/dev/null", {
      maxBuffer: 5 * 1024 * 1024,
    }));
  } catch {
    scutilOut = "";
  }

const scutilLines = scutilOut.trim().split("\n").filter(Boolean);
  for (const line of scutilLines) {
    // Format: * (Status) <uuid> [Proto/Type] "Name" [...] 
    const statusMatch   = line.match(/\((\w+)\)/);
    const nameMatch     = line.match(/"([^\"]+)"/);
    const typeMatch     = line.match(/\[([^\]]+)\]/);
    if (!nameMatch) continue;

const name        = nameMatch[1];
    const status      = statusMatch ? statusMatch[1] : "Unknown";
    const typeStr     = typeMatch   ? typeMatch[1]   : "VPN";
    const isConnected = status === "Connected";

// Attempt to get server for this profile via scutil --nc show
    let server: string | null = null;
    try {
      const { stdout: showOut } = await execAsync(
        `scutil --nc show \