diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5c351b3f9..47bb47d97 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,7 +40,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: ["1.25", "1.26"] + go: ["1.26", "1.27"] steps: - name: Check out code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/go.mod b/go.mod index 3287a9578..190d9f093 100644 --- a/go.mod +++ b/go.mod @@ -1,20 +1,15 @@ module github.com/modelcontextprotocol/go-sdk -go 1.25.0 +go 1.26 require ( + github.com/go-json-experiment/json v0.0.0-20260820222146-c27c302e5fc3 github.com/golang-jwt/jwt/v5 v5.3.1 github.com/google/go-cmp v0.7.0 github.com/google/jsonschema-go v0.4.3 - github.com/segmentio/encoding v0.5.4 github.com/yosida95/uritemplate/v3 v3.0.2 golang.org/x/oauth2 v0.35.0 + golang.org/x/sync v0.20.0 golang.org/x/time v0.15.0 golang.org/x/tools v0.42.0 ) - -require ( - github.com/segmentio/asm v1.1.3 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.41.0 // indirect -) diff --git a/go.sum b/go.sum index c13454aad..7e20e83b8 100644 --- a/go.sum +++ b/go.sum @@ -1,21 +1,17 @@ +github.com/go-json-experiment/json v0.0.0-20260820222146-c27c302e5fc3 h1:UADEEmDKgfXbtnGJZ97beY5XLo9ZechG1nlU4KnRrkE= +github.com/go-json-experiment/json v0.0.0-20260820222146-c27c302e5fc3/go.mod h1:tphK2c80bpPhMOI4v6bIc2xWywPfbqi1Z06+RcrMkDg= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0= github.com/google/jsonschema-go v0.4.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= -github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= -github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0= -github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= diff --git a/internal/json/json.go b/internal/json/json.go index 3a0e52b2a..098e7a801 100644 --- a/internal/json/json.go +++ b/internal/json/json.go @@ -8,10 +8,15 @@ package json import ( "bytes" + "encoding/json" + "errors" "fmt" "io" + "reflect" - "github.com/segmentio/encoding/json" + jsonv2 "github.com/go-json-experiment/json" + "github.com/go-json-experiment/json/jsontext" + jsonv1 "github.com/go-json-experiment/json/v1" ) // defaultMaxDepth is the maximum JSON nesting depth accepted by [Unmarshal]. @@ -23,18 +28,42 @@ const defaultMaxDepth = 1000 // errMaxDepthExceeded is returned by [Unmarshal] when the input nests deeper than [defaultMaxDepth]. var errMaxDepthExceeded = fmt.Errorf("json: exceeded maximum nesting depth of %d", defaultMaxDepth) +var caseSensitiveV1 = jsonv2.JoinOptions( + jsonv1.DefaultOptionsV1(), + jsonv2.MatchCaseInsensitiveNames(false), +) + +// caseSensitiveV1UseNumber adds UseNumber, which v2 has no option for: a JSON +// number decoded into an any becomes a json.Number, so large IDs stay exact. +var caseSensitiveV1UseNumber = jsonv2.JoinOptions( + caseSensitiveV1, + jsonv2.WithUnmarshalers(jsonv2.UnmarshalFromFunc(func(dec *jsontext.Decoder, v *any) error { + if dec.PeekKind() != '0' { + return errors.ErrUnsupported + } + if p := reflect.ValueOf(*v); p.Kind() == reflect.Pointer && !p.IsNil() { + return errors.ErrUnsupported + } + val, err := dec.ReadValue() + if err != nil { + return err + } + *v = json.Number(string(val)) + return nil + })), +) + type Decoder struct { - dec *json.Decoder + dec *jsontext.Decoder + opts jsonv2.Options } func NewDecoder(r io.Reader) *Decoder { - dec := json.NewDecoder(r) - dec.DontMatchCaseInsensitiveStructFields() - return &Decoder{dec: dec} + return &Decoder{dec: jsontext.NewDecoder(r), opts: caseSensitiveV1} } func (d *Decoder) Decode(v any) error { - return d.dec.Decode(v) + return jsonv2.UnmarshalDecode(d.dec, v, d.opts) } func Unmarshal(data []byte, v any) error { @@ -44,12 +73,14 @@ func Unmarshal(data []byte, v any) error { return NewDecoder(bytes.NewReader(data)).Decode(v) } +func (d *Decoder) UseNumber() { d.opts = caseSensitiveV1UseNumber } + func UnmarshalUseNumber(data []byte, v any) error { if err := checkMaxDepth(data, defaultMaxDepth); err != nil { return err } dec := NewDecoder(bytes.NewReader(data)) - dec.dec.UseNumber() + dec.UseNumber() return dec.Decode(v) }