Skip to content

Commit 7d3b538

Browse files
SamMorrowDrumsomgitsadsalmaleksia
committed
mcp: export SchemaCache type and improve documentation
Export SchemaCache (was schemaCache) so users can properly type-annotate variables. Add trade-offs documentation covering memory behavior and pointer identity caching. Add brief mention in server.md pointing to the type documentation. Remove benchmarks (functional tests suffice). Clean up redundant comments throughout. Co-authored-by: Adam Holt <[email protected]> Co-authored-by: Ksenia Bobrova <[email protected]>
1 parent 68942f7 commit 7d3b538

File tree

6 files changed

+85
-251
lines changed

6 files changed

+85
-251
lines changed

docs/server.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,23 @@ _See [mcp/tool_example_test.go](../mcp/tool_example_test.go) for the full
400400
example, or [examples/server/toolschemas](examples/server/toolschemas/main.go)
401401
for more examples of customizing tool schemas._
402402

403+
**Stateless server deployments:** Some deployments create a new
404+
[`Server`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Server)
405+
for each incoming request, re-registering tools every time. To avoid repeated
406+
schema generation, create a
407+
[`SchemaCache`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#SchemaCache)
408+
and share it across server instances:
409+
410+
```go
411+
var schemaCache = mcp.NewSchemaCache() // create once at startup
412+
413+
func handleRequest(w http.ResponseWriter, r *http.Request) {
414+
s := mcp.NewServer(impl, &mcp.ServerOptions{SchemaCache: schemaCache})
415+
mcp.AddTool(s, myTool, myHandler)
416+
// ...
417+
}
418+
```
419+
403420
## Utilities
404421

405422
### Completion

internal/docs/server.src.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,23 @@ _See [mcp/tool_example_test.go](../mcp/tool_example_test.go) for the full
193193
example, or [examples/server/toolschemas](examples/server/toolschemas/main.go)
194194
for more examples of customizing tool schemas._
195195

196+
**Stateless server deployments:** Some deployments create a new
197+
[`Server`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Server)
198+
for each incoming request, re-registering tools every time. To avoid repeated
199+
schema generation, create a
200+
[`SchemaCache`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#SchemaCache)
201+
and share it across server instances:
202+
203+
```go
204+
var schemaCache = mcp.NewSchemaCache() // create once at startup
205+
206+
func handleRequest(w http.ResponseWriter, r *http.Request) {
207+
s := mcp.NewServer(impl, &mcp.ServerOptions{SchemaCache: schemaCache})
208+
mcp.AddTool(s, myTool, myHandler)
209+
// ...
210+
}
211+
```
212+
196213
## Utilities
197214

198215
### Completion

mcp/schema_cache.go

Lines changed: 27 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
3638
type 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
}

mcp/schema_cache_benchmark_test.go

Lines changed: 0 additions & 160 deletions
This file was deleted.

0 commit comments

Comments
 (0)