| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 |
- package helper
- import (
- "encoding/json"
- "fmt"
- "reflect"
- "github.com/mark3labs/mcp-go/mcp"
- )
- // GetStringSlice 从请求参数中读取字符串数组(结构化数组 schema,如 WithArray+WithStringItems)。
- // - 参数缺失或为 nil:返回 (nil, false)
- // - 参数为 []any:逐元素转字符串(仅接受 string 元素,遇到非字符串元素返回 ok=false)
- // - 其余类型:返回 ok=false
- //
- // 注意:本函数不接受 JSON 字符串形式的入参,参数应通过结构化数组 schema 传入。
- func GetStringSlice(req mcp.CallToolRequest, key string) ([]string, bool) {
- args := req.GetArguments()
- raw, exists := args[key]
- if !exists || raw == nil {
- return nil, false
- }
- arr, ok := raw.([]any)
- if !ok {
- return nil, false
- }
- out := make([]string, 0, len(arr))
- for _, item := range arr {
- s, ok := item.(string)
- if !ok {
- return nil, false
- }
- out = append(out, s)
- }
- return out, true
- }
- // JSONResult 将任意数据序列化为 JSON 文本返回,序列化前完成 PII 脱敏(A+B)。
- // 脱敏失败时返回错误,拒绝输出(fail-closed)。
- func JSONResult(data any) *mcp.CallToolResult {
- b, err := marshalSanitized(data)
- if err != nil {
- return mcp.NewToolResultError(err.Error())
- }
- return mcp.NewToolResultText(string(b))
- }
- // TextResult 直接返回文本
- func TextResult(text string) *mcp.CallToolResult {
- return mcp.NewToolResultText(text)
- }
- // ParamError 参数缺失错误
- func ParamError(name string) *mcp.CallToolResult {
- return mcp.NewToolResultError(fmt.Sprintf("参数 %s 必填", name))
- }
- // ParamInvalidError 参数格式错误
- func ParamInvalidError(name string, detail string) *mcp.CallToolResult {
- return mcp.NewToolResultError(fmt.Sprintf("参数 %s 格式错误: %s", name, detail))
- }
- // ServiceError 业务调用错误
- func ServiceError(action string, err error) *mcp.CallToolResult {
- return mcp.NewToolResultError(fmt.Sprintf("%s失败: %v", action, err))
- }
- // BatchValidationError 批量校验错误:返回结构化的 valid/invalid 分离结果,
- // 便于 Agent 精确定位哪些输入非法并只修正这些项,而无需重猜整批。
- // reason 为对非法项的统一说明(如 "SN 格式非法")。
- func BatchValidationError(action string, valid []string, invalid []string, reason string) *mcp.CallToolResult {
- return JSONResult(map[string]any{
- "error": true,
- "action": action,
- "reason": reason,
- "valid": valid,
- "invalid": invalid,
- "note": "invalid 中的项未通过校验,请修正后重试;valid 为已通过校验的项。",
- })
- }
- // EmptyResult 空结果
- func EmptyResult(msg string) *mcp.CallToolResult {
- return mcp.NewToolResultText(msg)
- }
- // PageResult 将分页查询结果包装为 AI 友好的结构:分页元信息
- // total(符合条件的总数)、pageSize、pageNum、returned(本页返回条数)与
- // hasMore(是否还有下一页)直接返回,便于 Agent 判断是否翻完、如何继续翻页;
- // data 正文(带数据)经 PII 脱敏后无条件落盘,只在返回里给出 localPath 与 summary
- // (不含数据正文),避免分页数据进入模型上下文。
- //
- // hasMore = pageNum*pageSize < total(pageNum 从 1 开始)。
- // hint 用于落盘文件名前缀。
- func PageResult(total, pageSize, pageNum uint, data any, hint string) *mcp.CallToolResult {
- returned := 0
- // 尽力统计本页条数:data 通常是切片
- switch d := data.(type) {
- case nil:
- returned = 0
- default:
- if arr, ok := toSliceLen(d); ok {
- returned = arr
- }
- }
- hasMore := uint(pageNum)*uint(pageSize) < uint(total)
- meta := map[string]any{
- "total": total,
- "pageSize": pageSize,
- "pageNum": pageNum,
- "returned": returned,
- "hasMore": hasMore,
- }
- // data 正文无条件落盘:脱敏 → 写文件 → 只回 localPath + summary。
- if returned == 0 {
- // 本页无数据:无需落盘,直接标注空数据。
- meta["data"] = nil
- meta["note"] = "本页无数据。"
- } else if b, err := marshalSanitized(data); err != nil {
- return mcp.NewToolResultError(err.Error())
- } else if path, werr := writeSpillFile(hint, b); werr != nil {
- return mcp.NewToolResultError(fmt.Sprintf("分页数据落盘失败: %v", werr))
- } else {
- dataMeta := map[string]any{
- "localPath": path,
- "bytes": len(b),
- }
- if summary := summarizeJSON(b); summary != nil {
- dataMeta["summary"] = summary
- }
- meta["data"] = dataMeta
- meta["note"] = "本页数据已写入本地文件,请使用文件读取工具按 data.localPath 读取完整内容。"
- }
- mb, err := json.MarshalIndent(meta, "", " ")
- if err != nil {
- return mcp.NewToolResultError(fmt.Sprintf("分页元信息序列化失败: %v", err))
- }
- return mcp.NewToolResultText(string(mb))
- }
- // toSliceLen 若 v 是切片或数组,返回其长度与 true;否则返回 (0,false)。
- func toSliceLen(v any) (int, bool) {
- rv := reflect.ValueOf(v)
- switch rv.Kind() {
- case reflect.Slice, reflect.Array:
- return rv.Len(), true
- default:
- return 0, false
- }
- }
|