check_connectivity
check_connectivity
Checks network connectivity by pinging multiple targets (gateway, DNS servers, and internet hosts). Returns reachability status for each target. Use when user reports network issues, before/after VPN operations, or when diagnosing email/service connectivity problems.
Metadata
Name
check_connectivity
Updated
3 weeks ago
Source
Risk
Low
Requires consent
false
Affected scope
user
Code
/**
* mcp/skills/checkConnectivity.ts — check_connectivity skill
*
* Checks network connectivity by pinging multiple targets (gateway, DNS
* servers, and internet hosts). Returns reachability status for each target.
*
* Platform strategy
* -----------------
* darwin `ping -c {count} -W 2 {target}` — built-in BSD ping
* win32 PowerShell Test-Connection | ConvertTo-Json
*
* Smoke test
* npx tsx -r dotenv/config mcp/skills/checkConnectivity.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_connectivity",
description:
"Checks network connectivity by pinging multiple targets (gateway, DNS " +
"servers, and internet hosts). Returns reachability status for each target. " +
"Use when user reports network issues, before/after VPN operations, or when " +
"diagnosing email/service connectivity problems.",
riskLevel: "low",
destructive: false,
requiresConsent: false,
supportsDryRun: false,
affectedScope: ["user"],
auditRequired: false,
schema: {
targets: z
.array(z.string())
.optional()
.describe("Hosts to ping. Defaults to ['8.8.8.8', '1.1.1.1', 'google.com']"),
count: z
.number()
.optional()
.describe("Ping count per target. Default: 3"),
},
} as const;
// -- Types --------------------------------------------------------------------
interface TargetResult {
host: string;
reachable: boolean;
packetLoss: number;
avgRttMs: number | null;
}
// -- 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, timeout: 20_000 },
);
return stdout.trim();
}
// -- darwin implementation ----------------------------------------------------
async function pingTargetDarwin(host: string, count: number): Promise<TargetResult> {
// Validate host to prevent shell injection — allow hostnames, IPv4, IPv6
if (!/^[a-zA-Z0-9.\-:]+$/.test(host)) {
return { host, reachable: false, packetLoss: 100, avgRttMs: null };
}
try {
const { stdout } = await execAsync(
`ping -c ${count} -W 2 ${host} 2>&1`,
{ maxBuffer: 1 * 1024 * 1024, timeout: 15_000 },
);
// Parse packet loss: "3 packets transmitted, 3 packets received, 0.0% packet loss"
const lossMatch = stdout.match(/(\d+(?:\.\d+)?)%\s+packet loss/);
const packetLoss = lossMatch ? parseFloat(lossMatch[1]) : 100;
// Parse RTT: "round-trip min/avg/max/stddev = 1.234/5.678/9.012/0.123 ms"
const rttMatch = stdout.match(/min\/avg\/max\/(?:stddev|mdev)\s*=\s*[\d.]+\/([\d.]+)/);
const avgRttMs = rttMatch ? parseFloat(rttMatch[1]) : null;
return {
host,
reachable: packetLoss < 100,
packetLoss,
avgRttMs,
};
} catch (err) {
// ping exits non-zero when host is unreachable; stdout may still have loss info
const stdout = (err as { stdout?: string }).stdout ?? "";
const lossMatch = stdout.match(/(\d+(?:\.\d+)?)%\s+packet loss/);
const packetLoss = lossMatch ? parseFloat(lossMatch[1]) : 100;
return { host, reachable: false, packetLoss, avgRttMs: null };
}
}
async function checkConnectivityDarwin(
targets: string[],
count: number,
): Promise<TargetResult[]> {
return Promise.all(targets.map((t) => pingTargetDarwin(t, count)));
}
// -- win32 implementation -----------------------------------------------------
async function checkConnectivityWin32(
targets: string[],
count: number,
): Promise<TargetResult[]> {
const safeTargets = targets.filter((t) => /^[a-zA-Z0-9.\-:]+$/.test(t));
const targetList = safeTargets.map((t) => `'${t}'`).join(",");
const ps = `
$ErrorActionPreference = 'SilentlyContinue'
$results = @()
foreach ($h in @(${targetList})) {
$pings = Test-Connection -ComputerName $h -Count ${count} -ErrorAction SilentlyContinue
if ($null -eq $pings -or $pings.Count -eq 0) {
$results += [PSCustomObject]@{
host = $h
reachable = $false
packetLoss = 100
avgRttMs = $null
}
} else {
$received = ($pings | Where-Object { $_.StatusCode -eq 0 }).Count
$loss = [Math]::Round((1 - ($received / ${count})) * 100, 1)
$avgRtt = if ($received -gt 0) { [Math]::Round(($pings | Where-Object { $_.StatusCode -eq 0 } | Measure-Object ResponseTime -Average).Average, 2) } else { $null }
$results += [PSCustomObject]@{
host = $h
reachable = ($received -gt 0)
packetLoss = $loss
avgRttMs = $avgRtt
}
}
}
if ($results.Count -eq 0) { '[]' } else { ConvertTo-Json -InputObject @($results) -Depth 2 -Compress }`.trim();
const raw = await runPS(ps);
if (!raw || raw === "null") {
return targets.map((host) => ({ host, reachable: false, packetLoss: 100, avgRttMs: null }));
}
let parsed: TargetResult | TargetResult[];
try {
parsed = JSON.parse(raw) as TargetResult | TargetResult[];
} catch {
return targets.map((host) => ({ host, reachable: false, packetLoss: 100, avgRttMs: null }));
}
const arr = Array.isArray(parsed) ? parsed : [parsed];
// Re-add any filtered-out invalid hosts as unreachable
return targets.map((host) => {
const found = arr.find((r) => r.host === host);
return found ?? { host, reachable: false, packetLoss: 100, avgRttMs: null };
});
}
// -- Exported run function ----------------------------------------------------
export async function run({
targets = ["8.8.8.8", "1.1.1.1", "google.com"],
count = 3,
}: {
targets?: string[];
count?: number;
} = {}) {
const platform = os.platform();
const results = platform === "win32"
? await checkConnectivityWin32(targets, count)
: await checkConnectivityDarwin(targets, count);
return {
platform,
targets: results,
allReachable: results.every((r) => r.reachable),
anyReachable: results.some((r) => r.reachable),
};
}
// -- 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); });
}