@@ -20,15 +20,34 @@ import (
2020 "github.com/modelcontextprotocol/go-sdk/internal/util"
2121)
2222
23- // The value of the "$schema" keyword for the version that we can validate.
24- const draft202012 = "https://json-schema.org/draft/2020-12/schema"
23+ // The values of the "$schema" keyword for the versions that we can validate.
24+ const (
25+ draft07 = "http://json-schema.org/draft-07/schema#"
26+ draft07Sec = "https://json-schema.org/draft-07/schema#"
27+ draft202012 = "https://json-schema.org/draft/2020-12/schema"
28+ )
29+
30+ // isValidSchemaVersion checks if the given schema version is supported
31+ func isValidSchemaVersion (version string ) bool {
32+ return version == "" || version == draft07 || version == draft07Sec || version == draft202012
33+ }
34+
35+ // isDraft07 checks if the schema version is draft-07
36+ func isDraft07 (version string ) bool {
37+ return version == draft07 || version == draft07Sec
38+ }
39+
40+ // isDraft202012 checks if the schema version is draft 2020-12
41+ func isDraft202012 (version string ) bool {
42+ return version == "" || version == draft202012 // empty defaults to 2020-12
43+ }
2544
2645// Validate validates the instance, which must be a JSON value, against the schema.
2746// It returns nil if validation is successful or an error if it is not.
2847// If the schema type is "object", instance can be a map[string]any or a struct.
2948func (rs * Resolved ) Validate (instance any ) error {
30- if s := rs .root .Schema ; s != "" && s != draft202012 {
31- return fmt .Errorf ("cannot validate version %s, only %s " , s , draft202012 )
49+ if s := rs .root .Schema ; ! isValidSchemaVersion ( s ) {
50+ return fmt .Errorf ("cannot validate version %s, supported versions: draft-07 and draft 2020-12 " , s )
3251 }
3352 st := & state {rs : rs }
3453 return st .validate (reflect .ValueOf (instance ), st .rs .root , nil )
@@ -40,8 +59,8 @@ func (rs *Resolved) Validate(instance any) error {
4059// TODO(jba): account for dynamic refs. This algorithm simple-mindedly
4160// treats each schema with a default as its own root.
4261func (rs * Resolved ) validateDefaults () error {
43- if s := rs .root .Schema ; s != "" && s != draft202012 {
44- return fmt .Errorf ("cannot validate version %s, only %s " , s , draft202012 )
62+ if s := rs .root .Schema ; ! isValidSchemaVersion ( s ) {
63+ return fmt .Errorf ("cannot validate version %s, supported versions: draft-07 and draft 2020-12 " , s )
4564 }
4665 st := & state {rs : rs }
4766 for s := range rs .root .all () {
@@ -298,6 +317,8 @@ func (st *state) validate(instance reflect.Value, schema *Schema, callerAnns *an
298317 // arrays
299318 // TODO(jba): consider arrays of structs.
300319 if instance .Kind () == reflect .Array || instance .Kind () == reflect .Slice {
320+ // Handle both draft-07 and draft 2020-12 array validation using the same logic
321+ // Draft-07 items arrays are converted to prefixItems during unmarshaling
301322 // https://json-schema.org/draft/2020-12/json-schema-core#section-10.3.1
302323 // This validate call doesn't collect annotations for the items of the instance; they are separate
303324 // instances in their own right.
@@ -312,6 +333,8 @@ func (st *state) validate(instance reflect.Value, schema *Schema, callerAnns *an
312333 }
313334 anns .noteEndIndex (min (len (schema .PrefixItems ), instance .Len ()))
314335
336+ // For draft 2020-12: items applies to remaining items after prefixItems
337+ // For draft-07: additionalItems applies to remaining items after items array
315338 if schema .Items != nil {
316339 for i := len (schema .PrefixItems ); i < instance .Len (); i ++ {
317340 if err := st .validate (instance .Index (i ), schema .Items , nil ); err != nil {
@@ -320,6 +343,14 @@ func (st *state) validate(instance reflect.Value, schema *Schema, callerAnns *an
320343 }
321344 // Note that all the items in this array have been validated.
322345 anns .allItems = true
346+ } else if schema .AdditionalItems != nil {
347+ // Draft-07 style: use additionalItems for remaining items
348+ for i := len (schema .PrefixItems ); i < instance .Len (); i ++ {
349+ if err := st .validate (instance .Index (i ), schema .AdditionalItems , nil ); err != nil {
350+ return err
351+ }
352+ }
353+ anns .allItems = true
323354 }
324355
325356 nContains := 0
@@ -524,6 +555,43 @@ func (st *state) validate(instance reflect.Value, schema *Schema, callerAnns *an
524555 }
525556 }
526557 }
558+
559+ // Draft-07 dependencies (combines both property and schema dependencies)
560+ if schema .Dependencies != nil {
561+ for dprop , dep := range schema .Dependencies {
562+ if hasProperty (dprop ) {
563+ switch v := dep .(type ) {
564+ case []interface {}:
565+ // Array of strings - property dependencies
566+ var reqs []string
567+ for _ , item := range v {
568+ if str , ok := item .(string ); ok {
569+ reqs = append (reqs , str )
570+ }
571+ }
572+ if m := missingProperties (reqs ); len (m ) > 0 {
573+ return fmt .Errorf ("dependencies[%q]: missing properties %q" , dprop , m )
574+ }
575+ case map [string ]interface {}:
576+ // Schema object - schema dependencies
577+ // Convert map to Schema and resolve it properly
578+ if data , err := json .Marshal (v ); err == nil {
579+ var depSchema Schema
580+ if err := json .Unmarshal (data , & depSchema ); err == nil {
581+ // Resolve the dependency schema
582+ resolved , err := depSchema .Resolve (nil )
583+ if err != nil {
584+ return fmt .Errorf ("dependencies[%q]: failed to resolve schema: %w" , dprop , err )
585+ }
586+ if err := resolved .Validate (instance .Interface ()); err != nil {
587+ return fmt .Errorf ("dependencies[%q]: %w" , dprop , err )
588+ }
589+ }
590+ }
591+ }
592+ }
593+ }
594+ }
527595 if schema .UnevaluatedProperties != nil && ! anns .allProperties {
528596 // This looks a lot like AdditionalProperties, but depends on in-place keywords like allOf
529597 // in addition to sibling keywords.
0 commit comments