@@ -11,64 +11,59 @@ import (
1111 "github.com/google/jsonschema-go/jsonschema"
1212)
1313
14- // schemaCache provides concurrent-safe caching for JSON schemas.
15- // It caches both by reflect.Type (for auto-generated schemas) and
16- // by schema pointer (for pre-defined schemas).
14+ // A SchemaCache caches JSON schemas to avoid repeated reflection and resolution.
1715//
18- // This cache significantly improves performance for stateless server deployments
19- // where tools are re-registered on every request. Without caching, each AddTool
20- // call would trigger expensive reflection-based schema generation and resolution .
16+ // This is useful for stateless server deployments (one [Server] per request)
17+ // where tools are re-registered on every request. Without caching, each
18+ // [AddTool] call triggers expensive reflection-based schema generation.
2119//
22- // Create a cache using [NewSchemaCache] and pass it to [ServerOptions.SchemaCache].
23- type schemaCache struct {
24- // byType caches schemas generated from Go types via jsonschema.ForType.
25- // Key: reflect.Type, Value: *cachedSchema
26- byType sync.Map
27-
28- // bySchema caches resolved schemas for pre-defined Schema objects.
29- // Key: *jsonschema.Schema (pointer identity), Value: *jsonschema.Resolved
30- // This uses pointer identity because integrators typically reuse the same
31- // Tool objects across requests, so the schema pointer remains stable.
32- bySchema sync.Map
20+ // A SchemaCache is safe for concurrent use by multiple goroutines.
21+ //
22+ // # Trade-offs
23+ //
24+ // The cache is unbounded: it stores one entry per unique Go type or schema
25+ // pointer. For typical MCP servers with a fixed set of tools, memory usage
26+ // is negligible. However, if tool input types are generated dynamically,
27+ // the cache will grow without bound.
28+ //
29+ // The cache uses pointer identity for pre-defined schemas. If a schema's
30+ // contents change but the pointer remains the same, stale resolved schemas
31+ // may be returned. In practice, this is not an issue because tool schemas
32+ // are typically defined once at startup.
33+ type SchemaCache struct {
34+ byType sync.Map // reflect.Type -> *cachedSchema
35+ bySchema sync.Map // *jsonschema.Schema -> *jsonschema.Resolved
3336}
3437
35- // cachedSchema holds both the generated schema and its resolved form.
3638type cachedSchema struct {
3739 schema * jsonschema.Schema
3840 resolved * jsonschema.Resolved
3941}
4042
41- // NewSchemaCache creates a new schema cache for use with [ServerOptions.SchemaCache].
42- // Safe for concurrent use, unbounded.
43- func NewSchemaCache () * schemaCache {
44- return & schemaCache {}
43+ // NewSchemaCache creates a new [SchemaCache].
44+ func NewSchemaCache () * SchemaCache {
45+ return & SchemaCache {}
4546}
4647
47- // getByType retrieves a cached schema by Go type.
48- // Returns the schema, resolved schema, and whether the cache hit.
49- func (c * schemaCache ) getByType (t reflect.Type ) (* jsonschema.Schema , * jsonschema.Resolved , bool ) {
48+ func (c * SchemaCache ) getByType (t reflect.Type ) (* jsonschema.Schema , * jsonschema.Resolved , bool ) {
5049 if v , ok := c .byType .Load (t ); ok {
5150 cs := v .(* cachedSchema )
5251 return cs .schema , cs .resolved , true
5352 }
5453 return nil , nil , false
5554}
5655
57- // setByType caches a schema by Go type.
58- func (c * schemaCache ) setByType (t reflect.Type , schema * jsonschema.Schema , resolved * jsonschema.Resolved ) {
56+ func (c * SchemaCache ) setByType (t reflect.Type , schema * jsonschema.Schema , resolved * jsonschema.Resolved ) {
5957 c .byType .Store (t , & cachedSchema {schema : schema , resolved : resolved })
6058}
6159
62- // getBySchema retrieves a cached resolved schema by the original schema pointer.
63- // This is used when integrators provide pre-defined schemas (e.g., github-mcp-server pattern).
64- func (c * schemaCache ) getBySchema (schema * jsonschema.Schema ) (* jsonschema.Resolved , bool ) {
60+ func (c * SchemaCache ) getBySchema (schema * jsonschema.Schema ) (* jsonschema.Resolved , bool ) {
6561 if v , ok := c .bySchema .Load (schema ); ok {
6662 return v .(* jsonschema.Resolved ), true
6763 }
6864 return nil , false
6965}
7066
71- // setBySchema caches a resolved schema by the original schema pointer.
72- func (c * schemaCache ) setBySchema (schema * jsonschema.Schema , resolved * jsonschema.Resolved ) {
67+ func (c * SchemaCache ) setBySchema (schema * jsonschema.Schema , resolved * jsonschema.Resolved ) {
7368 c .bySchema .Store (schema , resolved )
7469}
0 commit comments