@@ -41,84 +41,138 @@ func isAcceptedError(err error) bool {
4141 return errors .As (err , & acceptedError )
4242}
4343
44- // numericToFloat64 normalizes numeric tool arguments to float64.
45- // MCP clients may send JSON numbers as float64 (default json.Unmarshal),
46- // native integer types (e.g. mcpcurl integer flags), or strings.
47- func numericToFloat64 (val any ) (float64 , error ) {
44+ // float64ToInt64 converts a float64 to int64, rejecting non-finite, fractional,
45+ // and out-of-range values, including those that would lose precision.
46+ func float64ToInt64 (f float64 ) (int64 , error ) {
47+ if math .IsNaN (f ) || math .IsInf (f , 0 ) {
48+ return 0 , fmt .Errorf ("non-finite numeric value" )
49+ }
50+ if f != math .Trunc (f ) {
51+ return 0 , fmt .Errorf ("non-integer numeric value: %v" , f )
52+ }
53+ if f > float64 (math .MaxInt64 ) || f < float64 (math .MinInt64 ) {
54+ return 0 , fmt .Errorf ("numeric value %v is too large to fit in int64" , f )
55+ }
56+ result := int64 (f )
57+ if float64 (result ) != f {
58+ return 0 , fmt .Errorf ("numeric value %v is too large to fit in int64" , f )
59+ }
60+ return result , nil
61+ }
62+
63+ // float64ToInt converts a float64 to int, rejecting non-finite, fractional,
64+ // and out-of-range values.
65+ func float64ToInt (f float64 ) (int , error ) {
66+ if math .IsNaN (f ) || math .IsInf (f , 0 ) {
67+ return 0 , fmt .Errorf ("non-finite numeric value" )
68+ }
69+ if f != math .Trunc (f ) {
70+ return 0 , fmt .Errorf ("non-integer numeric value: %v" , f )
71+ }
72+ if f > float64 (math .MaxInt ) || f < float64 (math .MinInt ) {
73+ return 0 , fmt .Errorf ("numeric value out of int range: %v" , f )
74+ }
75+ return int (f ), nil
76+ }
77+
78+ // toInt converts a value to int, handling float64, integer, and string representations.
79+ // Native integer types are preserved without a float64 round-trip so large values
80+ // are not corrupted. It rejects NaN, ±Inf, fractional values, and out-of-range values.
81+ func toInt (val any ) (int , error ) {
4882 switch v := val .(type ) {
49- case float64 :
50- return v , nil
51- case float32 :
52- return float64 (v ), nil
5383 case int :
54- return float64 ( v ) , nil
84+ return v , nil
5585 case int32 :
56- return float64 (v ), nil
86+ return int (v ), nil
5787 case int64 :
58- return float64 (v ), nil
88+ if v > int64 (math .MaxInt ) || v < int64 (math .MinInt ) {
89+ return 0 , fmt .Errorf ("numeric value out of int range: %v" , v )
90+ }
91+ return int (v ), nil
5992 case uint :
60- return float64 (v ), nil
93+ if v > uint (math .MaxInt ) {
94+ return 0 , fmt .Errorf ("numeric value out of int range: %v" , v )
95+ }
96+ return int (v ), nil
6197 case uint32 :
62- return float64 (v ), nil
98+ return int (v ), nil
6399 case uint64 :
64- return float64 (v ), nil
100+ if v > uint64 (math .MaxInt ) {
101+ return 0 , fmt .Errorf ("numeric value out of int range: %v" , v )
102+ }
103+ return int (v ), nil
104+ case float64 :
105+ return float64ToInt (v )
106+ case float32 :
107+ return float64ToInt (float64 (v ))
65108 case string :
66- f , err := strconv .ParseFloat (v , 64 )
109+ i , err := strconv .ParseInt (v , 10 , 0 )
67110 if err != nil {
68111 return 0 , fmt .Errorf ("invalid numeric value: %s" , v )
69112 }
70- return f , nil
113+ return int ( i ) , nil
71114 case json.Number :
115+ if i , err := v .Int64 (); err == nil {
116+ if i > int64 (math .MaxInt ) || i < int64 (math .MinInt ) {
117+ return 0 , fmt .Errorf ("numeric value out of int range: %v" , i )
118+ }
119+ return int (i ), nil
120+ }
72121 f , err := v .Float64 ()
73122 if err != nil {
74123 return 0 , fmt .Errorf ("invalid numeric value: %s" , v )
75124 }
76- return f , nil
125+ return float64ToInt ( f )
77126 default :
78127 return 0 , fmt .Errorf ("expected number, got %T" , val )
79128 }
80129}
81130
82- // toInt converts a value to int, handling float64, integer, and string representations.
83- // Some MCP clients send numeric values as strings or native integer types. It rejects
84- // NaN, ±Inf, fractional values, and values outside the int range.
85- func toInt (val any ) (int , error ) {
86- f , err := numericToFloat64 (val )
87- if err != nil {
88- return 0 , err
89- }
90- if math .IsNaN (f ) || math .IsInf (f , 0 ) {
91- return 0 , fmt .Errorf ("non-finite numeric value" )
92- }
93- if f != math .Trunc (f ) {
94- return 0 , fmt .Errorf ("non-integer numeric value: %v" , f )
95- }
96- if f > math .MaxInt || f < math .MinInt {
97- return 0 , fmt .Errorf ("numeric value out of int range: %v" , f )
98- }
99- return int (f ), nil
100- }
101-
102131// toInt64 converts a value to int64, handling float64, integer, and string representations.
103- // Some MCP clients send numeric values as strings or native integer types. It rejects
104- // NaN, ±Inf, fractional values, and values that lose precision in the float64→int64 conversion .
132+ // Native integer types are preserved without a float64 round-trip so large values
133+ // are not corrupted. It rejects NaN, ±Inf, fractional values, and out-of-range values .
105134func toInt64 (val any ) (int64 , error ) {
106- f , err := numericToFloat64 (val )
107- if err != nil {
108- return 0 , err
109- }
110- if math .IsNaN (f ) || math .IsInf (f , 0 ) {
111- return 0 , fmt .Errorf ("non-finite numeric value" )
112- }
113- if f != math .Trunc (f ) {
114- return 0 , fmt .Errorf ("non-integer numeric value: %v" , f )
115- }
116- result := int64 (f )
117- // Check round-trip to detect precision loss for large int64 values
118- if float64 (result ) != f {
119- return 0 , fmt .Errorf ("numeric value %v is too large to fit in int64" , f )
135+ switch v := val .(type ) {
136+ case int :
137+ return int64 (v ), nil
138+ case int32 :
139+ return int64 (v ), nil
140+ case int64 :
141+ return v , nil
142+ case uint :
143+ if uint64 (v ) > uint64 (math .MaxInt64 ) {
144+ return 0 , fmt .Errorf ("numeric value out of int64 range: %v" , v )
145+ }
146+ return int64 (v ), nil
147+ case uint32 :
148+ return int64 (v ), nil
149+ case uint64 :
150+ if v > uint64 (math .MaxInt64 ) {
151+ return 0 , fmt .Errorf ("numeric value out of int64 range: %v" , v )
152+ }
153+ return int64 (v ), nil
154+ case float64 :
155+ return float64ToInt64 (v )
156+ case float32 :
157+ return float64ToInt64 (float64 (v ))
158+ case string :
159+ i , err := strconv .ParseInt (v , 10 , 64 )
160+ if err != nil {
161+ return 0 , fmt .Errorf ("invalid numeric value: %s" , v )
162+ }
163+ return i , nil
164+ case json.Number :
165+ if i , err := v .Int64 (); err == nil {
166+ return i , nil
167+ }
168+ f , err := v .Float64 ()
169+ if err != nil {
170+ return 0 , fmt .Errorf ("invalid numeric value: %s" , v )
171+ }
172+ return float64ToInt64 (f )
173+ default :
174+ return 0 , fmt .Errorf ("expected number, got %T" , val )
120175 }
121- return result , nil
122176}
123177
124178// RequiredParam is a helper function that can be used to fetch a requested parameter from the request.
@@ -150,7 +204,7 @@ func RequiredParam[T comparable](args map[string]any, p string) (T, error) {
150204// RequiredInt is a helper function that can be used to fetch a requested parameter from the request.
151205// It does the following checks:
152206// 1. Checks if the parameter is present in the request.
153- // 2. Checks if the parameter is of the expected type (float64 or numeric string).
207+ // 2. Checks if the parameter is a valid integer (float64, native integer, or numeric string).
154208// 3. Checks if the parameter is not empty, i.e: non-zero value
155209func RequiredInt (args map [string ]any , p string ) (int , error ) {
156210 v , ok := args [p ]
@@ -173,7 +227,7 @@ func RequiredInt(args map[string]any, p string) (int, error) {
173227// RequiredBigInt is a helper function that can be used to fetch a requested parameter from the request.
174228// It does the following checks:
175229// 1. Checks if the parameter is present in the request.
176- // 2. Checks if the parameter is of the expected type (float64 or numeric string).
230+ // 2. Checks if the parameter is a valid integer (float64, native integer, or numeric string).
177231// 3. Checks if the parameter is not empty, i.e: non-zero value.
178232// 4. Validates that the float64 value can be safely converted to int64 without truncation.
179233func RequiredBigInt (args map [string ]any , p string ) (int64 , error ) {
@@ -217,7 +271,7 @@ func OptionalParam[T any](args map[string]any, p string) (T, error) {
217271// OptionalIntParam is a helper function that can be used to fetch a requested parameter from the request.
218272// It does the following checks:
219273// 1. Checks if the parameter is present in the request, if not, it returns its zero-value
220- // 2. If it is present, it checks if the parameter is of the expected type (float64 or numeric string) and returns it
274+ // 2. If it is present, it checks if the parameter is a valid integer (float64, native integer, or numeric string) and returns it
221275func OptionalIntParam (args map [string ]any , p string ) (int , error ) {
222276 val , ok := args [p ]
223277 if ! ok {
0 commit comments