Initial commit: forge-tools-ssh — MCP-сервер для администрирования по SSH
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
// registerDiskTools добавляет точные инструменты диска: по конкретному пути
|
||||
// (безпотерьный ответ "сколько места на X") и по всем точкам монтирования.
|
||||
// Отличается от ssh__usage, который исторически отдавал только корень /.
|
||||
func registerDiskTools(s *mcp.Server) {
|
||||
s.AddTool(&mcp.Tool{
|
||||
Name: "disk_usage",
|
||||
Description: "Get free/used space for the filesystem that contains the given path (df equivalent). Resolves the MOUND, not just root: returns the mount point, device, size, used, available and use% for the volume holding <path>. Use for 'how much space/free on <path>'.",
|
||||
InputSchema: schema(map[string]any{
|
||||
"path": strProps("Absolute path; reports the filesystem that contains it", true),
|
||||
"target": strProps("Connection alias (default: primary)", false),
|
||||
}, []string{"path"}),
|
||||
}, diskUsageHandler)
|
||||
|
||||
s.AddTool(&mcp.Tool{
|
||||
Name: "disk_usage_all",
|
||||
Description: "Get free/used space for ALL mounted filesystems (df over every mount point) with a marker on partitions that are >=80% used. Use for an overview of disk pressure.",
|
||||
InputSchema: schema(map[string]any{
|
||||
"target": strProps("Connection alias (default: primary)", false),
|
||||
}, nil),
|
||||
}, diskUsageAllHandler)
|
||||
}
|
||||
|
||||
func diskUsageHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
m := manager()
|
||||
if m == nil {
|
||||
return errorResult("no SSH manager initialized"), nil
|
||||
}
|
||||
args := requestArgs(req)
|
||||
path, err := requireString(args, "path")
|
||||
if err != nil {
|
||||
return errorResult(err.Error()), nil
|
||||
}
|
||||
target := getString(args, "target", "primary")
|
||||
|
||||
// df -hP <path> reports the FS containing <path> (mount point), not just
|
||||
// root. shellQuote guards the path; if the path doesn't exist yet (e.g., a
|
||||
// not-yet-mounted volume) we degrade to the full mount list rather than fail.
|
||||
cmd := diskUsageCmd(path)
|
||||
output, err := m.Execute(ctx, cmd, target)
|
||||
if err != nil {
|
||||
return errorResult(err.Error()), nil
|
||||
}
|
||||
return textResult(output), nil
|
||||
}
|
||||
|
||||
func diskUsageAllHandler(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
m := manager()
|
||||
if m == nil {
|
||||
return errorResult("no SSH manager initialized"), nil
|
||||
}
|
||||
target := getString(requestArgs(req), "target", "primary")
|
||||
output, err := m.Execute(ctx, "df -hP 2>/dev/null", target)
|
||||
if err != nil {
|
||||
return errorResult(err.Error()), nil
|
||||
}
|
||||
return textResult(highlightHighUsage(output)), nil
|
||||
}
|
||||
|
||||
// diskUsageCmd собирает команду "место на томе, содержащем path".
|
||||
func diskUsageCmd(path string) string {
|
||||
return fmt.Sprintf("df -hP %s 2>/dev/null || df -hP 2>/dev/null", shellQuote(path))
|
||||
}
|
||||
|
||||
// highlightHighUsage помечает строки df с заполненностью >=80% (иначе
|
||||
// "почти полный" раздел выпадал у ssh__diagnose_system из-за фильтра >90%).
|
||||
// Парсим в Go, а не awk: тестируемо и не зависит от полей awk.
|
||||
func highlightHighUsage(dfOut string) string {
|
||||
if strings.TrimSpace(dfOut) == "" {
|
||||
return dfOut
|
||||
}
|
||||
var b strings.Builder
|
||||
for _, line := range strings.Split(dfOut, "\n") {
|
||||
b.WriteString(line)
|
||||
fields := strings.Fields(line)
|
||||
// POSIX df -hP: колонка 5 (индекс 4) — "Use%" вроде "84%".
|
||||
if len(fields) >= 5 {
|
||||
if pct, err := strconv.Atoi(strings.TrimSuffix(fields[4], "%")); err == nil && pct >= 80 {
|
||||
b.WriteString(" << USE>=80%")
|
||||
}
|
||||
}
|
||||
b.WriteByte('\n')
|
||||
}
|
||||
return strings.TrimRight(b.String(), "\n")
|
||||
}
|
||||
Reference in New Issue
Block a user