package mcp import ( "encoding/json" "fmt" "context" "github.com/sundynix/sundynix-shared/contract" ) // chartSeries 是一条数据系列。 type chartSeries struct { Name string `json:"name,omitempty"` Data []float64 `json:"data"` } // chartSpec 是图表的结构化规范(工具产出,前端据此渲染 SVG,不在后端出图)。 type chartSpec struct { Type string `json:"type"` // bar / line / pie Title string `json:"title,omitempty"` // Labels []string `json:"labels"` // x 轴/扇区标签 Series []chartSeries `json:"series"` // 一条或多条数据系列(pie 取第一条) } // chart 工具:只校验并返回规范化图表 JSON(渲染交前端)。职责单一、零图片传输。 // 返回内容即一段 chart JSON;工具说明会指示 agent 在最终答复里用 ```chart 围栏原样包裹它。 func (g *Gateway) chart(_ context.Context, call *contract.ToolCall) *contract.ToolResult { // 用 JSON round-trip 把 args 收进类型化结构(args 里 labels/series 是 []any,手解繁琐)。 raw, _ := json.Marshal(call.Args) var in chartSpec if err := json.Unmarshal(raw, &in); err != nil { return &contract.ToolResult{OK: false, Error: "chart: 参数解析失败 —— " + err.Error()} } switch in.Type { case "bar", "line", "pie": case "": in.Type = "bar" default: return &contract.ToolResult{OK: false, Error: "chart: type 仅支持 bar / line / pie"} } if len(in.Labels) == 0 { return &contract.ToolResult{OK: false, Error: "chart: labels 必填"} } if len(in.Series) == 0 || len(in.Series[0].Data) == 0 { return &contract.ToolResult{OK: false, Error: "chart: series 至少一条且 data 非空"} } for i, s := range in.Series { if len(s.Data) != len(in.Labels) { return &contract.ToolResult{OK: false, Error: fmt.Sprintf("chart: 第 %d 条系列 data 长度(%d) 与 labels 长度(%d) 不一致", i+1, len(s.Data), len(in.Labels))} } } if in.Type == "pie" { in.Series = in.Series[:1] // pie 只用第一条系列 } out, _ := json.Marshal(in) // 提示 agent:把这段 JSON 用 ```chart 围栏原样放进最终答复,前端会渲染成图。 return &contract.ToolResult{OK: true, Content: string(out)} }