From 6520c617aadad394cc3a6779e27850b39306c3ff Mon Sep 17 00:00:00 2001 From: arimu1 <19286898+arimu1@users.noreply.github.com> Date: Sat, 1 Aug 2026 13:22:47 +0700 Subject: [PATCH 1/4] mcp: add ServerOptions.DefaultCacheable Allow servers to override the Cacheable defaults stamped on SDK-generated results (discover, list methods, resources/read). Nil keeps historical public/0 defaults. Fixes #1094 --- mcp/protocol.go | 9 +++- mcp/server.go | 21 +++++++--- mcp/server_test.go | 102 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 7 deletions(-) diff --git a/mcp/protocol.go b/mcp/protocol.go index 47cb9cbd..d6f35edc 100644 --- a/mcp/protocol.go +++ b/mcp/protocol.go @@ -1192,7 +1192,14 @@ func (c Cacheable) GetTTLMs() int { return c.TTLMs } func (c Cacheable) GetCacheScope() string { return c.CacheScope } // setDefaultCacheableValues sets the default values for the cacheable fields. -func (c *Cacheable) setDefaultCacheableValues() { +// If defaults is non-nil, its TTLMs and CacheScope are copied onto c. +// Otherwise CacheScope is set to "public" (TTLMs stays 0 unless already set). +func (c *Cacheable) setDefaultCacheableValues(defaults *Cacheable) { + if defaults != nil { + c.TTLMs = defaults.TTLMs + c.CacheScope = defaults.CacheScope + return + } c.CacheScope = "public" } diff --git a/mcp/server.go b/mcp/server.go index c189a8ee..a029eb13 100644 --- a/mcp/server.go +++ b/mcp/server.go @@ -169,6 +169,15 @@ type ServerOptions struct { // GetSessionID is not consulted when [StreamableHTTPOptions.Stateless] is // true, since stateless servers do not maintain sessions. GetSessionID func() string + + // DefaultCacheable, if non-nil, supplies the [Cacheable] values stamped on + // SDK-generated results (server/discover, list methods, and resources/read + // after the handler returns). If nil, those results use the historical + // defaults: CacheScope "public" and TTLMs 0. + // + // Receiving middleware can still overwrite Cacheable on a per-result basis + // after the SDK stamps these values. + DefaultCacheable *Cacheable } // NewServer creates a new MCP server. The resulting server has no features: @@ -851,7 +860,7 @@ func (s *Server) listPrompts(_ context.Context, req *ListPromptsRequest) (*ListP if err != nil { return nil, err } - res.setDefaultCacheableValues() + res.setDefaultCacheableValues(s.opts.DefaultCacheable) return res, nil } @@ -913,7 +922,7 @@ func (s *Server) discover(_ context.Context, req *ServerRequest[*DiscoverParams] Capabilities: s.capabilities(), Instructions: s.opts.Instructions, } - res.setDefaultCacheableValues() + res.setDefaultCacheableValues(s.opts.DefaultCacheable) return res, nil } @@ -949,7 +958,7 @@ func (s *Server) listTools(_ context.Context, req *ListToolsRequest) (*ListTools if err != nil { return nil, err } - res.setDefaultCacheableValues() + res.setDefaultCacheableValues(s.opts.DefaultCacheable) return res, nil } @@ -997,7 +1006,7 @@ func (s *Server) listResources(_ context.Context, req *ListResourcesRequest) (*L if err != nil { return nil, err } - res.setDefaultCacheableValues() + res.setDefaultCacheableValues(s.opts.DefaultCacheable) return res, nil } @@ -1017,7 +1026,7 @@ func (s *Server) listResourceTemplates(_ context.Context, req *ListResourceTempl if err != nil { return nil, err } - res.setDefaultCacheableValues() + res.setDefaultCacheableValues(s.opts.DefaultCacheable) return res, nil } @@ -1038,7 +1047,7 @@ func (s *Server) readResource(ctx context.Context, req *ReadResourceRequest) (*R if err := handleMultiRoundTripResult(req.Session, s.opts.Logger, res); err != nil { return nil, err } - res.setDefaultCacheableValues() + res.setDefaultCacheableValues(s.opts.DefaultCacheable) if res.resultType == resultTypeInputRequired { return res, nil } diff --git a/mcp/server_test.go b/mcp/server_test.go index 75d7455c..5bbc2d2c 100644 --- a/mcp/server_test.go +++ b/mcp/server_test.go @@ -1691,3 +1691,105 @@ func TestServerSession_RejectsServerInitiated(t *testing.T) { } } } + +func TestServerDefaultCacheable(t *testing.T) { + ctx := context.Background() + + for _, tc := range []struct { + name string + defaults *Cacheable + want Cacheable + }{ + { + name: "historical defaults", + want: Cacheable{TTLMs: 0, CacheScope: "public"}, + }, + { + name: "private with TTL", + defaults: &Cacheable{TTLMs: 60_000, CacheScope: "private"}, + want: Cacheable{TTLMs: 60_000, CacheScope: "private"}, + }, + } { + t.Run(tc.name, func(t *testing.T) { + server := NewServer(testImpl, &ServerOptions{DefaultCacheable: tc.defaults}) + AddTool(server, &Tool{Name: "t", Description: "d"}, + func(context.Context, *CallToolRequest, struct{}) (*CallToolResult, any, error) { + return &CallToolResult{Content: []Content{&TextContent{Text: "ok"}}}, nil, nil + }) + server.AddPrompt(&Prompt{Name: "p"}, func(context.Context, *GetPromptRequest) (*GetPromptResult, error) { + return &GetPromptResult{}, nil + }) + server.AddResource(&Resource{URI: "test://r", Name: "r"}, func(context.Context, *ReadResourceRequest) (*ReadResourceResult, error) { + return &ReadResourceResult{Contents: []*ResourceContents{{URI: "test://r", Text: "x"}}}, nil + }) + server.AddResourceTemplate(&ResourceTemplate{URITemplate: "test://{id}", Name: "rt"}, + func(context.Context, *ReadResourceRequest) (*ReadResourceResult, error) { + return &ReadResourceResult{Contents: []*ResourceContents{{Text: "x"}}}, nil + }) + + ct, st := NewInMemoryTransports() + if _, err := server.Connect(ctx, st, nil); err != nil { + t.Fatal(err) + } + cs, err := NewClient(testImpl, nil).Connect(ctx, ct, nil) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = cs.Close() }) + + check := func(t *testing.T, label string, got Cacheable) { + t.Helper() + if got != tc.want { + t.Errorf("%s Cacheable = %+v, want %+v", label, got, tc.want) + } + } + + tools, err := cs.ListTools(ctx, nil) + if err != nil { + t.Fatal(err) + } + check(t, "ListTools", tools.Cacheable) + + prompts, err := cs.ListPrompts(ctx, nil) + if err != nil { + t.Fatal(err) + } + check(t, "ListPrompts", prompts.Cacheable) + + resources, err := cs.ListResources(ctx, nil) + if err != nil { + t.Fatal(err) + } + check(t, "ListResources", resources.Cacheable) + + templates, err := cs.ListResourceTemplates(ctx, nil) + if err != nil { + t.Fatal(err) + } + check(t, "ListResourceTemplates", templates.Cacheable) + + read, err := cs.ReadResource(ctx, &ReadResourceParams{URI: "test://r"}) + if err != nil { + t.Fatal(err) + } + check(t, "ReadResource", read.Cacheable) + }) + } +} + +func TestSetDefaultCacheableValues(t *testing.T) { + t.Run("nil defaults", func(t *testing.T) { + c := Cacheable{TTLMs: 42} + c.setDefaultCacheableValues(nil) + if c.TTLMs != 42 || c.CacheScope != "public" { + t.Fatalf("got %+v, want TTLMs preserved and CacheScope public", c) + } + }) + t.Run("explicit defaults", func(t *testing.T) { + c := Cacheable{TTLMs: 1, CacheScope: "public"} + c.setDefaultCacheableValues(&Cacheable{TTLMs: 60_000, CacheScope: "private"}) + if c.TTLMs != 60_000 || c.CacheScope != "private" { + t.Fatalf("got %+v, want private/60000", c) + } + }) +} From f7a32f75a19fa28ee86281c6df6377d355e1a856 Mon Sep 17 00:00:00 2001 From: arimu1 <19286898+arimu1@users.noreply.github.com> Date: Tue, 4 Aug 2026 06:30:30 +0700 Subject: [PATCH 2/4] test: remove TestServerDefaultCacheable per review --- mcp/server_test.go | 85 ---------------------------------------------- 1 file changed, 85 deletions(-) diff --git a/mcp/server_test.go b/mcp/server_test.go index 5bbc2d2c..6a3b9349 100644 --- a/mcp/server_test.go +++ b/mcp/server_test.go @@ -1692,91 +1692,6 @@ func TestServerSession_RejectsServerInitiated(t *testing.T) { } } -func TestServerDefaultCacheable(t *testing.T) { - ctx := context.Background() - - for _, tc := range []struct { - name string - defaults *Cacheable - want Cacheable - }{ - { - name: "historical defaults", - want: Cacheable{TTLMs: 0, CacheScope: "public"}, - }, - { - name: "private with TTL", - defaults: &Cacheable{TTLMs: 60_000, CacheScope: "private"}, - want: Cacheable{TTLMs: 60_000, CacheScope: "private"}, - }, - } { - t.Run(tc.name, func(t *testing.T) { - server := NewServer(testImpl, &ServerOptions{DefaultCacheable: tc.defaults}) - AddTool(server, &Tool{Name: "t", Description: "d"}, - func(context.Context, *CallToolRequest, struct{}) (*CallToolResult, any, error) { - return &CallToolResult{Content: []Content{&TextContent{Text: "ok"}}}, nil, nil - }) - server.AddPrompt(&Prompt{Name: "p"}, func(context.Context, *GetPromptRequest) (*GetPromptResult, error) { - return &GetPromptResult{}, nil - }) - server.AddResource(&Resource{URI: "test://r", Name: "r"}, func(context.Context, *ReadResourceRequest) (*ReadResourceResult, error) { - return &ReadResourceResult{Contents: []*ResourceContents{{URI: "test://r", Text: "x"}}}, nil - }) - server.AddResourceTemplate(&ResourceTemplate{URITemplate: "test://{id}", Name: "rt"}, - func(context.Context, *ReadResourceRequest) (*ReadResourceResult, error) { - return &ReadResourceResult{Contents: []*ResourceContents{{Text: "x"}}}, nil - }) - - ct, st := NewInMemoryTransports() - if _, err := server.Connect(ctx, st, nil); err != nil { - t.Fatal(err) - } - cs, err := NewClient(testImpl, nil).Connect(ctx, ct, nil) - if err != nil { - t.Fatal(err) - } - t.Cleanup(func() { _ = cs.Close() }) - - check := func(t *testing.T, label string, got Cacheable) { - t.Helper() - if got != tc.want { - t.Errorf("%s Cacheable = %+v, want %+v", label, got, tc.want) - } - } - - tools, err := cs.ListTools(ctx, nil) - if err != nil { - t.Fatal(err) - } - check(t, "ListTools", tools.Cacheable) - - prompts, err := cs.ListPrompts(ctx, nil) - if err != nil { - t.Fatal(err) - } - check(t, "ListPrompts", prompts.Cacheable) - - resources, err := cs.ListResources(ctx, nil) - if err != nil { - t.Fatal(err) - } - check(t, "ListResources", resources.Cacheable) - - templates, err := cs.ListResourceTemplates(ctx, nil) - if err != nil { - t.Fatal(err) - } - check(t, "ListResourceTemplates", templates.Cacheable) - - read, err := cs.ReadResource(ctx, &ReadResourceParams{URI: "test://r"}) - if err != nil { - t.Fatal(err) - } - check(t, "ReadResource", read.Cacheable) - }) - } -} - func TestSetDefaultCacheableValues(t *testing.T) { t.Run("nil defaults", func(t *testing.T) { c := Cacheable{TTLMs: 42} From 5e4a22b69daf3f5fc1052c9fa5de79852e730929 Mon Sep 17 00:00:00 2001 From: arimu1 <19286898+arimu1@users.noreply.github.com> Date: Tue, 4 Aug 2026 21:27:03 +0700 Subject: [PATCH 3/4] fix(mcp): default CacheScope to public when defaults omit it ServerOptions.DefaultCacheable may set only TTLMs. Empty CacheScope on defaults must still fall back to "public" instead of stamping "". Signed-off-by: arimu1 <19286898+arimu1@users.noreply.github.com> --- mcp/protocol.go | 11 +++++++---- mcp/server_test.go | 9 +++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/mcp/protocol.go b/mcp/protocol.go index d6f35edc..b1a8a76b 100644 --- a/mcp/protocol.go +++ b/mcp/protocol.go @@ -1192,13 +1192,16 @@ func (c Cacheable) GetTTLMs() int { return c.TTLMs } func (c Cacheable) GetCacheScope() string { return c.CacheScope } // setDefaultCacheableValues sets the default values for the cacheable fields. -// If defaults is non-nil, its TTLMs and CacheScope are copied onto c. -// Otherwise CacheScope is set to "public" (TTLMs stays 0 unless already set). +// When defaults is non-nil, TTLMs is copied and CacheScope is copied only when +// non-empty. An empty CacheScope on defaults (or a nil defaults pointer) falls +// back to "public", matching the protocol default for an absent cacheScope. func (c *Cacheable) setDefaultCacheableValues(defaults *Cacheable) { if defaults != nil { c.TTLMs = defaults.TTLMs - c.CacheScope = defaults.CacheScope - return + if defaults.CacheScope != "" { + c.CacheScope = defaults.CacheScope + return + } } c.CacheScope = "public" } diff --git a/mcp/server_test.go b/mcp/server_test.go index 6a3b9349..65ee6463 100644 --- a/mcp/server_test.go +++ b/mcp/server_test.go @@ -1707,4 +1707,13 @@ func TestSetDefaultCacheableValues(t *testing.T) { t.Fatalf("got %+v, want private/60000", c) } }) + t.Run("defaults with empty CacheScope fall back to public", func(t *testing.T) { + // ServerOptions.DefaultCacheable may set only TTLMs; CacheScope must still + // become "public" rather than remaining the empty string. + c := Cacheable{} + c.setDefaultCacheableValues(&Cacheable{TTLMs: 60_000}) + if c.TTLMs != 60_000 || c.CacheScope != "public" { + t.Fatalf("got %+v, want TTLMs 60000 and CacheScope public", c) + } + }) } From 15b7b8f720c78d008e0f18f1961e1cb99c9afc0e Mon Sep 17 00:00:00 2001 From: arimu1 <19286898+arimu1@users.noreply.github.com> Date: Wed, 5 Aug 2026 20:15:35 +0700 Subject: [PATCH 4/4] mcp: make ServerOptions.DefaultCacheable a request callback Replace the static *Cacheable option with a callback that receives context and Request so cache policy can vary by method, session, tenant, or auth. Empty CacheScope from the callback still falls back to "public". Remove TestSetDefaultCacheableValues per review; cover behavior via TestServerDefaultCacheable. Fixes #1094 --- mcp/protocol.go | 2 + mcp/server.go | 54 ++++++++++++++------- mcp/server_test.go | 117 +++++++++++++++++++++++++++++++++++---------- 3 files changed, 131 insertions(+), 42 deletions(-) diff --git a/mcp/protocol.go b/mcp/protocol.go index b1a8a76b..97a9475d 100644 --- a/mcp/protocol.go +++ b/mcp/protocol.go @@ -1195,6 +1195,8 @@ func (c Cacheable) GetCacheScope() string { return c.CacheScope } // When defaults is non-nil, TTLMs is copied and CacheScope is copied only when // non-empty. An empty CacheScope on defaults (or a nil defaults pointer) falls // back to "public", matching the protocol default for an absent cacheScope. +// Used by [Server.applyDefaultCacheable] after optionally invoking +// ServerOptions.DefaultCacheable. func (c *Cacheable) setDefaultCacheableValues(defaults *Cacheable) { if defaults != nil { c.TTLMs = defaults.TTLMs diff --git a/mcp/server.go b/mcp/server.go index a029eb13..a32bac37 100644 --- a/mcp/server.go +++ b/mcp/server.go @@ -170,14 +170,20 @@ type ServerOptions struct { // true, since stateless servers do not maintain sessions. GetSessionID func() string - // DefaultCacheable, if non-nil, supplies the [Cacheable] values stamped on - // SDK-generated results (server/discover, list methods, and resources/read - // after the handler returns). If nil, those results use the historical - // defaults: CacheScope "public" and TTLMs 0. + // DefaultCacheable, if non-nil, is called for each SDK-generated + // cacheable result (server/discover, *_list, resources/read) to + // determine the Cacheable values stamped on the response. The + // request is provided so policy can vary by method, session, + // tenant, or authentication state. // - // Receiving middleware can still overwrite Cacheable on a per-result basis - // after the SDK stamps these values. - DefaultCacheable *Cacheable + // If nil, SDK-generated results use CacheScope "public" and + // TTLMs 0, matching the historical defaults. + // + // If the callback returns an empty CacheScope, the SDK still stamps + // "public" (the protocol default for an absent cacheScope). + // Receiving middleware can still overwrite Cacheable on a + // per-result basis after the SDK stamps these values. + DefaultCacheable func(ctx context.Context, req Request) Cacheable } // NewServer creates a new MCP server. The resulting server has no features: @@ -845,7 +851,7 @@ func (s *Server) Sessions() iter.Seq[*ServerSession] { return slices.Values(clients) } -func (s *Server) listPrompts(_ context.Context, req *ListPromptsRequest) (*ListPromptsResult, error) { +func (s *Server) listPrompts(ctx context.Context, req *ListPromptsRequest) (*ListPromptsResult, error) { s.mu.Lock() defer s.mu.Unlock() if req.Params == nil { @@ -860,7 +866,7 @@ func (s *Server) listPrompts(_ context.Context, req *ListPromptsRequest) (*ListP if err != nil { return nil, err } - res.setDefaultCacheableValues(s.opts.DefaultCacheable) + s.applyDefaultCacheable(ctx, req, &res.Cacheable) return res, nil } @@ -890,7 +896,7 @@ func (s *Server) getPrompt(ctx context.Context, req *GetPromptRequest) (*GetProm // the server's capabilities, the server's identity, and the server's // instructions, allowing clients to negotiate without performing the legacy // initialize handshake. -func (s *Server) discover(_ context.Context, req *ServerRequest[*DiscoverParams]) (*DiscoverResult, error) { +func (s *Server) discover(ctx context.Context, req *ServerRequest[*DiscoverParams]) (*DiscoverResult, error) { req.Session.mu.Lock() versions := req.Session.supportedVersions req.Session.mu.Unlock() @@ -922,7 +928,7 @@ func (s *Server) discover(_ context.Context, req *ServerRequest[*DiscoverParams] Capabilities: s.capabilities(), Instructions: s.opts.Instructions, } - res.setDefaultCacheableValues(s.opts.DefaultCacheable) + s.applyDefaultCacheable(ctx, req, &res.Cacheable) return res, nil } @@ -943,7 +949,7 @@ func filterSupportedVersions(t Transport) []string { return out } -func (s *Server) listTools(_ context.Context, req *ListToolsRequest) (*ListToolsResult, error) { +func (s *Server) listTools(ctx context.Context, req *ListToolsRequest) (*ListToolsResult, error) { s.mu.Lock() defer s.mu.Unlock() if req.Params == nil { @@ -958,7 +964,7 @@ func (s *Server) listTools(_ context.Context, req *ListToolsRequest) (*ListTools if err != nil { return nil, err } - res.setDefaultCacheableValues(s.opts.DefaultCacheable) + s.applyDefaultCacheable(ctx, req, &res.Cacheable) return res, nil } @@ -991,7 +997,7 @@ func (s *Server) callTool(ctx context.Context, req *CallToolRequest) (*CallToolR return res, err } -func (s *Server) listResources(_ context.Context, req *ListResourcesRequest) (*ListResourcesResult, error) { +func (s *Server) listResources(ctx context.Context, req *ListResourcesRequest) (*ListResourcesResult, error) { s.mu.Lock() defer s.mu.Unlock() if req.Params == nil { @@ -1006,11 +1012,11 @@ func (s *Server) listResources(_ context.Context, req *ListResourcesRequest) (*L if err != nil { return nil, err } - res.setDefaultCacheableValues(s.opts.DefaultCacheable) + s.applyDefaultCacheable(ctx, req, &res.Cacheable) return res, nil } -func (s *Server) listResourceTemplates(_ context.Context, req *ListResourceTemplatesRequest) (*ListResourceTemplatesResult, error) { +func (s *Server) listResourceTemplates(ctx context.Context, req *ListResourceTemplatesRequest) (*ListResourceTemplatesResult, error) { s.mu.Lock() defer s.mu.Unlock() if req.Params == nil { @@ -1026,7 +1032,7 @@ func (s *Server) listResourceTemplates(_ context.Context, req *ListResourceTempl if err != nil { return nil, err } - res.setDefaultCacheableValues(s.opts.DefaultCacheable) + s.applyDefaultCacheable(ctx, req, &res.Cacheable) return res, nil } @@ -1047,7 +1053,7 @@ func (s *Server) readResource(ctx context.Context, req *ReadResourceRequest) (*R if err := handleMultiRoundTripResult(req.Session, s.opts.Logger, res); err != nil { return nil, err } - res.setDefaultCacheableValues(s.opts.DefaultCacheable) + s.applyDefaultCacheable(ctx, req, &res.Cacheable) if res.resultType == resultTypeInputRequired { return res, nil } @@ -1066,6 +1072,18 @@ func (s *Server) readResource(ctx context.Context, req *ReadResourceRequest) (*R return res, nil } +// applyDefaultCacheable stamps Cacheable defaults from ServerOptions.DefaultCacheable +// onto c. When the option is nil, historical public/0 defaults apply. An empty +// CacheScope from the callback still falls back to "public". +func (s *Server) applyDefaultCacheable(ctx context.Context, req Request, c *Cacheable) { + var defaults *Cacheable + if s.opts.DefaultCacheable != nil { + d := s.opts.DefaultCacheable(ctx, req) + defaults = &d + } + c.setDefaultCacheableValues(defaults) +} + // lookupResourceHandler returns the resource handler and MIME type for the resource or // resource template matching uri. If none, the last return value is false. func (s *Server) lookupResourceHandler(uri string) (ResourceHandler, string, bool) { diff --git a/mcp/server_test.go b/mcp/server_test.go index 65ee6463..d90ae875 100644 --- a/mcp/server_test.go +++ b/mcp/server_test.go @@ -1692,28 +1692,97 @@ func TestServerSession_RejectsServerInitiated(t *testing.T) { } } -func TestSetDefaultCacheableValues(t *testing.T) { - t.Run("nil defaults", func(t *testing.T) { - c := Cacheable{TTLMs: 42} - c.setDefaultCacheableValues(nil) - if c.TTLMs != 42 || c.CacheScope != "public" { - t.Fatalf("got %+v, want TTLMs preserved and CacheScope public", c) - } - }) - t.Run("explicit defaults", func(t *testing.T) { - c := Cacheable{TTLMs: 1, CacheScope: "public"} - c.setDefaultCacheableValues(&Cacheable{TTLMs: 60_000, CacheScope: "private"}) - if c.TTLMs != 60_000 || c.CacheScope != "private" { - t.Fatalf("got %+v, want private/60000", c) - } - }) - t.Run("defaults with empty CacheScope fall back to public", func(t *testing.T) { - // ServerOptions.DefaultCacheable may set only TTLMs; CacheScope must still - // become "public" rather than remaining the empty string. - c := Cacheable{} - c.setDefaultCacheableValues(&Cacheable{TTLMs: 60_000}) - if c.TTLMs != 60_000 || c.CacheScope != "public" { - t.Fatalf("got %+v, want TTLMs 60000 and CacheScope public", c) - } - }) +func TestServerDefaultCacheable(t *testing.T) { + ctx := context.Background() + + for _, tc := range []struct { + name string + callback func(context.Context, Request) Cacheable + want Cacheable + }{ + { + name: "historical defaults", + want: Cacheable{TTLMs: 0, CacheScope: "public"}, + }, + { + name: "private with TTL", + callback: func(context.Context, Request) Cacheable { + return Cacheable{TTLMs: 60_000, CacheScope: "private"} + }, + want: Cacheable{TTLMs: 60_000, CacheScope: "private"}, + }, + { + // Callback may set only TTLMs; empty CacheScope must still become "public". + name: "empty CacheScope falls back to public", + callback: func(context.Context, Request) Cacheable { + return Cacheable{TTLMs: 60_000} + }, + want: Cacheable{TTLMs: 60_000, CacheScope: "public"}, + }, + } { + t.Run(tc.name, func(t *testing.T) { + server := NewServer(testImpl, &ServerOptions{DefaultCacheable: tc.callback}) + AddTool(server, &Tool{Name: "t", Description: "d"}, + func(context.Context, *CallToolRequest, struct{}) (*CallToolResult, any, error) { + return &CallToolResult{Content: []Content{&TextContent{Text: "ok"}}}, nil, nil + }) + server.AddPrompt(&Prompt{Name: "p"}, func(context.Context, *GetPromptRequest) (*GetPromptResult, error) { + return &GetPromptResult{}, nil + }) + server.AddResource(&Resource{URI: "test://r", Name: "r"}, func(context.Context, *ReadResourceRequest) (*ReadResourceResult, error) { + return &ReadResourceResult{Contents: []*ResourceContents{{URI: "test://r", Text: "x"}}}, nil + }) + server.AddResourceTemplate(&ResourceTemplate{URITemplate: "test://{id}", Name: "rt"}, + func(context.Context, *ReadResourceRequest) (*ReadResourceResult, error) { + return &ReadResourceResult{Contents: []*ResourceContents{{Text: "x"}}}, nil + }) + + ct, st := NewInMemoryTransports() + if _, err := server.Connect(ctx, st, nil); err != nil { + t.Fatal(err) + } + cs, err := NewClient(testImpl, nil).Connect(ctx, ct, nil) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = cs.Close() }) + + check := func(t *testing.T, label string, got Cacheable) { + t.Helper() + if got != tc.want { + t.Errorf("%s Cacheable = %+v, want %+v", label, got, tc.want) + } + } + + tools, err := cs.ListTools(ctx, nil) + if err != nil { + t.Fatal(err) + } + check(t, "ListTools", tools.Cacheable) + + prompts, err := cs.ListPrompts(ctx, nil) + if err != nil { + t.Fatal(err) + } + check(t, "ListPrompts", prompts.Cacheable) + + resources, err := cs.ListResources(ctx, nil) + if err != nil { + t.Fatal(err) + } + check(t, "ListResources", resources.Cacheable) + + templates, err := cs.ListResourceTemplates(ctx, nil) + if err != nil { + t.Fatal(err) + } + check(t, "ListResourceTemplates", templates.Cacheable) + + read, err := cs.ReadResource(ctx, &ReadResourceParams{URI: "test://r"}) + if err != nil { + t.Fatal(err) + } + check(t, "ReadResource", read.Cacheable) + }) + } }