Files
pi_mcps/zoo_backup/home/tools/port_watch.ts
T
2026-06-24 19:27:14 +02:00

64 lines
2.1 KiB
TypeScript

import { parametersSchema as z, defineCustomTool, CustomToolContext } from "@roo-code/types"
// @ts-ignore - Node built-ins
import { spawnSync } from "child_process"
export default defineCustomTool({
name: "port_watch",
description: "Check what process holds a given TCP/UDP port. Replaces 'lsof -i :PORT' shell calls when debugging dev servers.",
parameters: z.object({
port: z.number().describe("TCP port number to check (1-65535)"),
protocol: z.enum(["tcp", "udp", "both"]).optional().describe("Protocol filter (default: tcp)"),
}),
async execute({ port, protocol = "tcp" }, context: CustomToolContext) {
try {
if (port < 1 || port > 65535) {
return JSON.stringify({ error: "Invalid port: must be between 1 and 65535" }, null, 2)
}
const listeners: any[] = []
const runLsof = (proto: string) => {
const flag = proto === "tcp" ? `-iTCP:${port}` : `-iUDP:${port}`
const stateFlag = proto === "tcp" ? ["-sTCP:LISTEN"] : []
const result = spawnSync("lsof", [flag, ...stateFlag, "-P", "-n"], {
encoding: "utf-8",
timeout: 10_000,
})
if (!result.stdout) return
const lines = result.stdout.split("\n").filter((l: string) => l.trim())
// Skip header line
for (let i = 1; i < lines.length; i++) {
const parts = lines[i].split(/\s+/)
if (parts.length < 9) continue
listeners.push({
pid: parseInt(parts[1], 10) || 0,
command: parts[0],
user: parts[2],
type: parts[4], // IPv4 or IPv6
state: parts[9] || (proto === "udp" ? "UDP" : "UNKNOWN"),
address: parts[8] || `*:${port}`,
})
}
}
if (protocol === "tcp" || protocol === "both") {
runLsof("tcp")
}
if (protocol === "udp" || protocol === "both") {
runLsof("udp")
}
return JSON.stringify({
port,
protocol,
inUse: listeners.length > 0,
listeners,
}, null, 2)
} catch (err: any) {
return JSON.stringify({ error: err.message ?? String(err) }, null, 2)
}
},
})