package tools import ( "context" "github.com/mark3labs/mcp-go/mcp" "spark-mcp-go/internal/rm" ) const GetApplicationLogsName = "get_application_logs" const defaultTailBytes = 1 << 20 // NewGetApplicationLogsTool returns the schema for the get_application_logs MCP Tool. func NewGetApplicationLogsTool() mcp.Tool { return mcp.NewTool(GetApplicationLogsName, mcp.WithDescription("Fetch YARN application logs. Tries amContainerLogs first, then aggregated-logs, then the legacy /logs endpoint. Returns {source, text}."), mcp.WithString("cluster_id", mcp.Required(), mcp.Description("ID of the configured cluster (from list_clusters)"), ), mcp.WithString("app_id", mcp.Required(), mcp.Description("YARN application ID, e.g. application_1234567890_0001"), ), mcp.WithString("container", mcp.Description("Container ID used for the aggregated-logs fallback, e.g. container_1234567890_0001_01_000001."), ), mcp.WithNumber("tail_bytes", mcp.Description("Maximum bytes to return. 0 means use the server-wide response byte limit."), ), ) } // GetApplicationLogsHandler retrieves logs using the RM fallback chain. func (d *Deps) GetApplicationLogsHandler(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { clusterID, err := req.RequireString("cluster_id") if err != nil { return errResult("get_application_logs: " + err.Error()), nil } appID, err := req.RequireString("app_id") if err != nil { return errResult("get_application_logs: " + err.Error()), nil } args := req.GetArguments() container := "" if v, ok := args["container"].(string); ok { container = v } tailBytes := defaultTailBytes if v, ok := args["tail_bytes"].(float64); ok { tailBytes = int(v) } if tailBytes == 0 { tailBytes = int(d.MaxResponseBytes) } callLog := startToolCall(ctx, d.Logger, GetApplicationLogsName, map[string]any{ "cluster_id": clusterID, "app_id": appID, "container": container, "tail_bytes": tailBytes, }) defer callLog.End() cl, err := d.ClusterRepo.Get(ctx, clusterID) if err != nil { callLog.WithError(err) return errResult("get_application_logs: cluster " + clusterID + ": " + err.Error()), nil } rmc := rm.New(d.HTTPClient, cl) body, source, err := rmc.GetLogs(ctx, appID, container) if err != nil { callLog.WithError(err) return errResult("get_application_logs: " + err.Error()), nil } text := string(body) if len(text) > tailBytes { text = truncateMiddle(text, tailBytes) } result := map[string]any{ "source": source, "text": text, } callLog.WithResult(map[string]any{"source": source, "bytes": len(body), "returned_bytes": len(text)}) return textResult(encodeJSON(result)), nil }