@@ -9,6 +9,7 @@ package jsonschema
99import (
1010 "fmt"
1111 "reflect"
12+ "regexp"
1213
1314 "github.com/modelcontextprotocol/go-sdk/internal/util"
1415)
@@ -36,18 +37,24 @@ import (
3637// - complex numbers
3738// - unsafe pointers
3839//
39- // The types must not have cycles.
40+ // It will return an error if there is a cycle in the types.
41+ //
42+ // For recognizes struct field tags named "jsonschema".
43+ // A jsonschema tag on a field is used as the description for the corresponding property.
44+ // For future compatibility, descriptions must not start with "WORD=", where WORD is a
45+ // sequence of non-whitespace characters.
4046func For [T any ]() (* Schema , error ) {
4147 // TODO: consider skipping incompatible fields, instead of failing.
42- s , err := forType (reflect .TypeFor [T ]())
48+ seen := make (map [reflect.Type ]bool )
49+ s , err := forType (reflect .TypeFor [T ](), seen )
4350 if err != nil {
4451 var z T
4552 return nil , fmt .Errorf ("For[%T](): %w" , z , err )
4653 }
4754 return s , nil
4855}
4956
50- func forType (t reflect.Type ) (* Schema , error ) {
57+ func forType (t reflect.Type , seen map [reflect. Type ] bool ) (* Schema , error ) {
5158 // Follow pointers: the schema for *T is almost the same as for T, except that
5259 // an explicit JSON "null" is allowed for the pointer.
5360 allowNull := false
@@ -56,6 +63,16 @@ func forType(t reflect.Type) (*Schema, error) {
5663 t = t .Elem ()
5764 }
5865
66+ // Check for cycles
67+ // User defined types have a name, so we can skip those that are natively defined
68+ if t .Name () != "" {
69+ if seen [t ] {
70+ return nil , fmt .Errorf ("cycle detected for type %v" , t )
71+ }
72+ seen [t ] = true
73+ defer delete (seen , t )
74+ }
75+
5976 var (
6077 s = new (Schema )
6178 err error
@@ -81,14 +98,14 @@ func forType(t reflect.Type) (*Schema, error) {
8198 return nil , fmt .Errorf ("unsupported map key type %v" , t .Key ().Kind ())
8299 }
83100 s .Type = "object"
84- s .AdditionalProperties , err = forType (t .Elem ())
101+ s .AdditionalProperties , err = forType (t .Elem (), seen )
85102 if err != nil {
86103 return nil , fmt .Errorf ("computing map value schema: %v" , err )
87104 }
88105
89106 case reflect .Slice , reflect .Array :
90107 s .Type = "array"
91- s .Items , err = forType (t .Elem ())
108+ s .Items , err = forType (t .Elem (), seen )
92109 if err != nil {
93110 return nil , fmt .Errorf ("computing element schema: %v" , err )
94111 }
@@ -114,10 +131,20 @@ func forType(t reflect.Type) (*Schema, error) {
114131 if s .Properties == nil {
115132 s .Properties = make (map [string ]* Schema )
116133 }
117- s . Properties [ info . Name ] , err = forType (field .Type )
134+ fs , err : = forType (field .Type , seen )
118135 if err != nil {
119136 return nil , err
120137 }
138+ if tag , ok := field .Tag .Lookup ("jsonschema" ); ok {
139+ if tag == "" {
140+ return nil , fmt .Errorf ("empty jsonschema tag on struct field %s.%s" , t , field .Name )
141+ }
142+ if disallowedPrefixRegexp .MatchString (tag ) {
143+ return nil , fmt .Errorf ("tag must not begin with 'WORD=': %q" , tag )
144+ }
145+ fs .Description = tag
146+ }
147+ s .Properties [info .Name ] = fs
121148 if ! info .Settings ["omitempty" ] && ! info .Settings ["omitzero" ] {
122149 s .Required = append (s .Required , info .Name )
123150 }
@@ -132,3 +159,6 @@ func forType(t reflect.Type) (*Schema, error) {
132159 }
133160 return s , nil
134161}
162+
163+ // Disallow jsonschema tag values beginning "WORD=", for future expansion.
164+ var disallowedPrefixRegexp = regexp .MustCompile ("^[^ \t \n ]*=" )
0 commit comments