215 lines
6.5 KiB
Go
215 lines
6.5 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
|
)
|
|
|
|
func registerNetworkTools(s *mcp.Server) {
|
|
// net_stat
|
|
s.AddTool(&mcp.Tool{
|
|
Name: "net_stat",
|
|
Description: "Check listening ports (ss/netstat)",
|
|
InputSchema: schema(map[string]any{
|
|
"port": intProps("Filter by specific port", false),
|
|
"target": strProps("Connection alias (default: primary)", false),
|
|
}, nil),
|
|
}, netStatHandler)
|
|
|
|
// search_files
|
|
s.AddTool(&mcp.Tool{
|
|
Name: "search_files",
|
|
Description: "Find files using POSIX find",
|
|
InputSchema: schema(map[string]any{
|
|
"pattern": strProps("File name pattern (supports wildcards)", true),
|
|
"path": strProps("Search path (default: /)", false),
|
|
"max_depth": intProps("Maximum directory depth", false),
|
|
"type": strProps("Filter by type: f (file), d (directory)", false, "f", "d"),
|
|
"target": strProps("Connection alias (default: primary)", false),
|
|
}, []string{"pattern"}),
|
|
}, searchFilesHandler)
|
|
|
|
// search_text
|
|
s.AddTool(&mcp.Tool{
|
|
Name: "search_text",
|
|
Description: "Search text in files using grep",
|
|
InputSchema: schema(map[string]any{
|
|
"pattern": strProps("Search pattern", true),
|
|
"path": strProps("File or directory path", true),
|
|
"recursive": boolProps("Search recursively", false),
|
|
"ignore_case": boolProps("Case-insensitive search", false),
|
|
"context": intProps("Lines of context around matches", false),
|
|
"target": strProps("Connection alias (default: primary)", false),
|
|
}, []string{"pattern", "path"}),
|
|
}, searchTextHandler)
|
|
|
|
// package_manage
|
|
s.AddTool(&mcp.Tool{
|
|
Name: "package_manage",
|
|
Description: "Install/remove/check packages (apt, apk, dnf, yum)",
|
|
InputSchema: schema(map[string]any{
|
|
"action": strProps("Package management action", true, "install", "remove", "check", "list"),
|
|
"package": strProps("Package name (required for install/remove/check)", false),
|
|
"target": strProps("Connection alias (default: primary)", false),
|
|
}, []string{"action"}),
|
|
}, packageManageHandler)
|
|
}
|
|
|
|
func netStatHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
m := manager()
|
|
if m == nil {
|
|
return errorResult("no SSH manager initialized"), nil
|
|
}
|
|
args := requestArgs(req)
|
|
port := getInt(args, "port", 0)
|
|
target := getString(args, "target", "primary")
|
|
|
|
var cmd string
|
|
if port > 0 {
|
|
cmd = fmt.Sprintf("ss -tlnp 2>/dev/null | grep ':%d ' || netstat -an 2>/dev/null | grep -i listen | grep '[\\.: ]%d '", port, port)
|
|
} else {
|
|
cmd = "ss -tlnp 2>/dev/null || netstat -an 2>/dev/null | grep -i listen"
|
|
}
|
|
output, err := m.Execute(ctx, cmd, target)
|
|
if err != nil {
|
|
return errorResult(err.Error()), nil
|
|
}
|
|
return textResult(output), nil
|
|
}
|
|
|
|
func searchFilesHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
m := manager()
|
|
if m == nil {
|
|
return errorResult("no SSH manager initialized"), nil
|
|
}
|
|
args := requestArgs(req)
|
|
pattern, _ := requireString(args, "pattern")
|
|
path := getString(args, "path", "/")
|
|
maxDepth := getInt(args, "max_depth", 0)
|
|
fileType := getString(args, "type", "")
|
|
target := getString(args, "target", "primary")
|
|
|
|
cmd := fmt.Sprintf("find %s", shellQuote(path))
|
|
if maxDepth > 0 {
|
|
cmd += fmt.Sprintf(" -maxdepth %d", maxDepth)
|
|
}
|
|
if fileType == "f" || fileType == "d" {
|
|
cmd += fmt.Sprintf(" -type %s", fileType)
|
|
}
|
|
cmd += fmt.Sprintf(" -name %s 2>/dev/null | head -100", shellQuote(pattern))
|
|
|
|
output, err := m.Execute(ctx, cmd, target)
|
|
if err != nil {
|
|
return errorResult(err.Error()), nil
|
|
}
|
|
return textResult(output), nil
|
|
}
|
|
|
|
func searchTextHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
m := manager()
|
|
if m == nil {
|
|
return errorResult("no SSH manager initialized"), nil
|
|
}
|
|
args := requestArgs(req)
|
|
pattern, _ := requireString(args, "pattern")
|
|
path, _ := requireString(args, "path")
|
|
recursive := getBool(args, "recursive", false)
|
|
ignoreCase := getBool(args, "ignore_case", false)
|
|
ctxLines := getInt(args, "context", 0)
|
|
target := getString(args, "target", "primary")
|
|
|
|
cmd := "grep"
|
|
if recursive {
|
|
cmd += " -r"
|
|
}
|
|
if ignoreCase {
|
|
cmd += " -i"
|
|
}
|
|
if ctxLines > 0 {
|
|
cmd += fmt.Sprintf(" -C %d", ctxLines)
|
|
}
|
|
cmd += fmt.Sprintf(" -n %s %s 2>/dev/null | head -100", shellQuote(pattern), shellQuote(path))
|
|
|
|
output, err := m.Execute(ctx, cmd, target)
|
|
if err != nil {
|
|
return errorResult(err.Error()), nil
|
|
}
|
|
return textResult(output), nil
|
|
}
|
|
|
|
func packageManageHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
m := manager()
|
|
if m == nil {
|
|
return errorResult("no SSH manager initialized"), nil
|
|
}
|
|
args := requestArgs(req)
|
|
action, _ := requireString(args, "action")
|
|
pkg := getString(args, "package", "")
|
|
target := getString(args, "target", "primary")
|
|
|
|
if action != "list" && pkg == "" {
|
|
return errorResult("Package name required for this action"), nil
|
|
}
|
|
|
|
detectCmd := `
|
|
if command -v apt-get >/dev/null 2>&1; then echo "apt"
|
|
elif command -v apk >/dev/null 2>&1; then echo "apk"
|
|
elif command -v dnf >/dev/null 2>&1; then echo "dnf"
|
|
elif command -v yum >/dev/null 2>&1; then echo "yum"
|
|
else echo "unknown"
|
|
fi`
|
|
|
|
pkgMgr, err := m.Execute(ctx, detectCmd, target)
|
|
if err != nil {
|
|
return errorResult(err.Error()), nil
|
|
}
|
|
pkgMgr = trimOutput(pkgMgr)
|
|
|
|
var cmd string
|
|
switch pkgMgr {
|
|
case "apt":
|
|
switch action {
|
|
case "install":
|
|
cmd = fmt.Sprintf("apt-get update && apt-get install -y %s", shellQuote(pkg))
|
|
case "remove":
|
|
cmd = fmt.Sprintf("apt-get remove -y %s", shellQuote(pkg))
|
|
case "check":
|
|
cmd = fmt.Sprintf("dpkg -s %s 2>/dev/null", shellQuote(pkg))
|
|
case "list":
|
|
cmd = "dpkg -l | head -50"
|
|
}
|
|
case "apk":
|
|
switch action {
|
|
case "install":
|
|
cmd = fmt.Sprintf("apk add %s", shellQuote(pkg))
|
|
case "remove":
|
|
cmd = fmt.Sprintf("apk del %s", shellQuote(pkg))
|
|
case "check":
|
|
cmd = fmt.Sprintf("apk info %s 2>/dev/null", shellQuote(pkg))
|
|
case "list":
|
|
cmd = "apk list --installed | head -50"
|
|
}
|
|
case "dnf", "yum":
|
|
switch action {
|
|
case "install":
|
|
cmd = fmt.Sprintf("%s install -y %s", pkgMgr, shellQuote(pkg))
|
|
case "remove":
|
|
cmd = fmt.Sprintf("%s remove -y %s", pkgMgr, shellQuote(pkg))
|
|
case "check":
|
|
cmd = fmt.Sprintf("rpm -qi %s 2>/dev/null", shellQuote(pkg))
|
|
case "list":
|
|
cmd = fmt.Sprintf("%s list installed 2>/dev/null | head -50", pkgMgr)
|
|
}
|
|
default:
|
|
return errorResult("No supported package manager found"), nil
|
|
}
|
|
|
|
output, err := m.Execute(ctx, cmd, target)
|
|
if err != nil {
|
|
return errorResult(err.Error()), nil
|
|
}
|
|
return textResult(output), nil
|
|
}
|