mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-11-07 00:05:14 +00:00
* feat(mcp): add 1Panel MCP and enhance MCP credential management * feat(i18n): implement MCP document localization and remove legacy code
106 lines
2.4 KiB
Go
106 lines
2.4 KiB
Go
package mcp
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
)
|
|
|
|
type projectionNode struct {
|
|
leaf bool
|
|
children map[string]*projectionNode
|
|
}
|
|
|
|
func projectionTree(fields []string) (*projectionNode, error) {
|
|
root := &projectionNode{children: map[string]*projectionNode{}}
|
|
if len(fields) > 256 {
|
|
return nil, errors.New("too many response fields")
|
|
}
|
|
for _, field := range fields {
|
|
parts := strings.Split(field, ".")
|
|
if len(parts) > 16 {
|
|
return nil, errors.New("response path too deep")
|
|
}
|
|
node := root
|
|
for _, part := range parts {
|
|
if !fieldPart.MatchString(part) || node.leaf {
|
|
return nil, errors.New("invalid response path")
|
|
}
|
|
if node.children[part] == nil {
|
|
node.children[part] = &projectionNode{children: map[string]*projectionNode{}}
|
|
}
|
|
node = node.children[part]
|
|
}
|
|
if node.leaf || len(node.children) > 0 {
|
|
return nil, errors.New("conflicting response fields")
|
|
}
|
|
node.leaf = true
|
|
}
|
|
return root, nil
|
|
}
|
|
|
|
// ProjectResult never copies a complete business object. A manifest lists
|
|
// scalar leaves explicitly, and paths transparently traverse arrays.
|
|
func ProjectResult(value any, fields []string) (any, error) {
|
|
root, err := projectionTree(fields)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
raw, err := json.Marshal(value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var data any
|
|
if err = json.Unmarshal(raw, &data); err != nil {
|
|
return nil, err
|
|
}
|
|
return projectValue(data, root, 0)
|
|
}
|
|
func projectValue(value any, node *projectionNode, depth int) (any, error) {
|
|
if depth > 32 {
|
|
return nil, errors.New("response too deep")
|
|
}
|
|
if value == nil {
|
|
return nil, nil
|
|
}
|
|
if list, ok := value.([]any); ok {
|
|
out := make([]any, 0, len(list))
|
|
for _, item := range list {
|
|
v, err := projectValue(item, node, depth+1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, v)
|
|
}
|
|
return out, nil
|
|
}
|
|
if node.leaf {
|
|
switch value.(type) {
|
|
case string, float64, bool:
|
|
return value, nil
|
|
default:
|
|
return nil, errors.New("response field must be a scalar leaf")
|
|
}
|
|
}
|
|
if len(node.children) == 0 {
|
|
return map[string]any{}, nil
|
|
}
|
|
obj, ok := value.(map[string]any)
|
|
if !ok {
|
|
return nil, errors.New("response shape changed")
|
|
}
|
|
out := map[string]any{}
|
|
for key, child := range node.children {
|
|
v, exists := obj[key]
|
|
if !exists {
|
|
continue
|
|
}
|
|
selected, err := projectValue(v, child, depth+1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out[key] = selected
|
|
}
|
|
return out, nil
|
|
}
|