Phase 5 Batch 2: 4 RM Tool + rm 客户端 + 日志降级链

- internal/rm/client.go: YARN RM 客户端
  - ListApps / GetApp / KillApp / GetLogs
  - GetLogs 降级链数据驱动 (amContainerLogs → aggregated-logs → logs)
  - 跨主机 redirect 保留 Authorization (URLAllowlist 兜底)
- 4 Tool: list_applications / get_application_status /
  get_application_logs / kill_application
  - mcp.WithEnum(state) 约束 YARN app 状态
  - tool handler 永远 (result, nil), 业务错误用 NewToolResultError
- deps.go: +HTTPClient + MaxResponseBytes
- main.go: 注入 httpclient.Client
- 端到端实测: mock RM 验证 4 Tool + 降级链 + 错误路径

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
tao.chen
2026-07-10 16:50:27 +08:00
co-authored by Claude
parent 705665745e
commit c4ac3cc354
9 changed files with 885 additions and 0 deletions
+60
View File
@@ -0,0 +1,60 @@
package tools
import (
"context"
"github.com/mark3labs/mcp-go/mcp"
"spark-mcp-go/internal/rm"
)
const KillApplicationName = "kill_application"
// NewKillApplicationTool returns the schema for the kill_application MCP Tool.
func NewKillApplicationTool() mcp.Tool {
return mcp.NewTool(KillApplicationName,
mcp.WithDescription("Kill (move to KILLED state) a YARN application through the ResourceManager. Returns the RM JSON response."),
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"),
),
)
}
// KillApplicationHandler sends a KILLED state update to the ResourceManager.
func (d *Deps) KillApplicationHandler(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
clusterID, err := req.RequireString("cluster_id")
if err != nil {
return errResult("kill_application: " + err.Error()), nil
}
appID, err := req.RequireString("app_id")
if err != nil {
return errResult("kill_application: " + err.Error()), nil
}
callLog := startToolCall(ctx, d.Logger, KillApplicationName, map[string]any{
"cluster_id": clusterID,
"app_id": appID,
})
defer callLog.End()
cl, err := d.ClusterRepo.Get(ctx, clusterID)
if err != nil {
callLog.WithError(err)
return errResult("kill_application: cluster " + clusterID + ": " + err.Error()), nil
}
rmc := rm.New(d.HTTPClient, cl)
raw, err := rmc.KillApp(ctx, appID)
if err != nil {
callLog.WithError(err)
return errResult("kill_application: " + err.Error()), nil
}
callLog.WithResult(map[string]any{"bytes": len(raw)})
return textResult(string(raw)), nil
}