|
@@ -5,6 +5,8 @@ import (
|
|
|
"crypto/rand"
|
|
"crypto/rand"
|
|
|
"encoding/hex"
|
|
"encoding/hex"
|
|
|
"fmt"
|
|
"fmt"
|
|
|
|
|
+ "math"
|
|
|
|
|
+ "reflect"
|
|
|
"strings"
|
|
"strings"
|
|
|
"sync"
|
|
"sync"
|
|
|
"time"
|
|
"time"
|
|
@@ -410,6 +412,12 @@ func (s *MCPServer) ProcessMCPRequest(ctx context.Context, request *MCPRequest)
|
|
|
return nil, err
|
|
return nil, err
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // The MCP SDK marshals tool output with encoding/json, which errors on
|
|
|
|
|
+ // non-finite floats. Upstream cost calculations can yield NaN or Inf (e.g.
|
|
|
|
|
+ // a 0/0 breakdown or overhead fraction), so scrub them before they reach the
|
|
|
|
|
+ // SDK and fail the whole tool call.
|
|
|
|
|
+ data = sanitizeNonFiniteFloats(data)
|
|
|
|
|
+
|
|
|
processingTime := time.Since(queryStart)
|
|
processingTime := time.Since(queryStart)
|
|
|
|
|
|
|
|
// 3. Construct Final Response
|
|
// 3. Construct Final Response
|
|
@@ -424,6 +432,68 @@ func (s *MCPServer) ProcessMCPRequest(ctx context.Context, request *MCPRequest)
|
|
|
return mcpResponse, nil
|
|
return mcpResponse, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// sanitizeNonFiniteFloats returns v with every non-finite float (NaN, +Inf,
|
|
|
|
|
+// -Inf) replaced by 0 so the value can be marshaled by encoding/json, which the
|
|
|
|
|
+// MCP SDK uses and which rejects non-finite floats. Callers must use the return
|
|
|
|
|
+// value, since value-type inputs are sanitized on a copy. It is best-effort:
|
|
|
|
|
+// any reflection panic is recovered and the original value returned unchanged.
|
|
|
|
|
+func sanitizeNonFiniteFloats(v any) (out any) {
|
|
|
|
|
+ out = v
|
|
|
|
|
+ defer func() {
|
|
|
|
|
+ if r := recover(); r != nil {
|
|
|
|
|
+ log.Warnf("mcp: sanitizeNonFiniteFloats recovered: %v", r)
|
|
|
|
|
+ out = v
|
|
|
|
|
+ }
|
|
|
|
|
+ }()
|
|
|
|
|
+ if v == nil {
|
|
|
|
|
+ return nil
|
|
|
|
|
+ }
|
|
|
|
|
+ // Work on an addressable copy so value-type inputs are sanitized too, not
|
|
|
|
|
+ // only pointers. For a pointer input this copies the pointer and mutates the
|
|
|
|
|
+ // pointed-to value in place; for a value input it yields a sanitized copy.
|
|
|
|
|
+ box := reflect.New(reflect.TypeOf(v))
|
|
|
|
|
+ box.Elem().Set(reflect.ValueOf(v))
|
|
|
|
|
+ sanitizeFloatsValue(box.Elem())
|
|
|
|
|
+ return box.Elem().Interface()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func sanitizeFloatsValue(v reflect.Value) {
|
|
|
|
|
+ switch v.Kind() {
|
|
|
|
|
+ case reflect.Ptr, reflect.Interface:
|
|
|
|
|
+ if !v.IsNil() {
|
|
|
|
|
+ sanitizeFloatsValue(v.Elem())
|
|
|
|
|
+ }
|
|
|
|
|
+ case reflect.Struct:
|
|
|
|
|
+ for i := 0; i < v.NumField(); i++ {
|
|
|
|
|
+ sanitizeFloatsValue(v.Field(i))
|
|
|
|
|
+ }
|
|
|
|
|
+ case reflect.Slice, reflect.Array:
|
|
|
|
|
+ for i := 0; i < v.Len(); i++ {
|
|
|
|
|
+ sanitizeFloatsValue(v.Index(i))
|
|
|
|
|
+ }
|
|
|
|
|
+ case reflect.Map:
|
|
|
|
|
+ for _, key := range v.MapKeys() {
|
|
|
|
|
+ elem := v.MapIndex(key)
|
|
|
|
|
+ // Map elements aren't addressable. Pointer/interface/slice/map
|
|
|
|
|
+ // values are mutated in place by recursing; value-type elements
|
|
|
|
|
+ // (e.g. a float or struct) must be rebuilt and reassigned.
|
|
|
|
|
+ switch elem.Kind() {
|
|
|
|
|
+ case reflect.Ptr, reflect.Interface, reflect.Slice, reflect.Map:
|
|
|
|
|
+ sanitizeFloatsValue(elem)
|
|
|
|
|
+ default:
|
|
|
|
|
+ tmp := reflect.New(elem.Type()).Elem()
|
|
|
|
|
+ tmp.Set(elem)
|
|
|
|
|
+ sanitizeFloatsValue(tmp)
|
|
|
|
|
+ v.SetMapIndex(key, tmp)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ case reflect.Float32, reflect.Float64:
|
|
|
|
|
+ if v.CanSet() && (math.IsNaN(v.Float()) || math.IsInf(v.Float(), 0)) {
|
|
|
|
|
+ v.SetFloat(0)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// validate is the singleton validator instance.
|
|
// validate is the singleton validator instance.
|
|
|
var validate = validator.New()
|
|
var validate = validator.New()
|
|
|
|
|
|