mcp: add Capabilities fields to ServerOptions and ClientOptions (#713)

This allows users to configure default capabilities, disable defaults,
or suppress listChanged notifications by setting ListChanged: false.

Key changes:
- Add ServerOptions.Capabilities and ClientOptions.Capabilities fields.
- Merge user-provided capabilities with dynamically-added ones (tools,
  prompts, resources, sampling, elicitation).
- Check ListChanged before sending list-changed notifications.
- Deprecate HasPrompts, HasResources, HasTools on ServerOptions.
- Remove unreleased ElicitationModes field from ClientOptions.
- Add clone methods to capability structs using shallowClone helper.
- Add synctest-based tests for notification behavior (go1.25).
- Add TestClientCapabilitiesOverWire and TestServerCapabilitiesOverWire
- Document the new Capabilities API in docs/

Updates #706
This commit is contained in:
Robert Findley 2025-12-11 20:17:46 -05:00 • committed by GitHub
commit 3430e2242e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -188,6 +188,10 @@ type RootCapabilities struct {
// this schema, but this is not a closed set: any client can define its own,
// additional capabilities.
type ClientCapabilities struct {
// NOTE: any addition to ClientCapabilities must also be reflected in
// [ClientCapabilities.clone].
// Experimental reports non-standard capabilities that the client supports.
Experimental map[string]any `json:"experimental,omitempty"`
// Roots describes the client's support for roots.
@ -200,7 +204,7 @@ type ClientCapabilities struct {
// changes to the roots list.
ListChanged bool `json:"listChanged,omitempty"`
} `json:"roots,omitempty"`
// RootsV2 is present if the client supports roots.
// RootsV2 is present if the client supports roots. When capabilities are explicitly configured via [ClientOptions.Capabilities]
RootsV2 *RootCapabilities `json:"-"`
// Sampling is present if the client supports sampling from an LLM.
Sampling *SamplingCapabilities `json:"sampling,omitempty"`
@ -208,6 +212,29 @@ type ClientCapabilities struct {
Elicitation *ElicitationCapabilities `json:"elicitation,omitempty"`
}
// clone returns a deep copy of the ClientCapabilities.
func (c *ClientCapabilities) clone() *ClientCapabilities {
cp := *c
cp.RootsV2 = shallowClone(c.RootsV2)
cp.Sampling = shallowClone(c.Sampling)
if c.Elicitation != nil {
x := *c.Elicitation
x.Form = shallowClone(c.Elicitation.Form)
x.URL = shallowClone(c.Elicitation.URL)
cp.Elicitation = &x
}
return &cp
}
// shallowClone returns a shallow clone of *p, or nil if p is nil.
func shallowClone[T any](p *T) *T {
if p == nil {
return nil
}
x := *p
return &x
}
func (c *ClientCapabilities) toV2() *clientCapabilitiesV2 {
return &clientCapabilitiesV2{
ClientCapabilities: *c,
@ -1259,6 +1286,10 @@ type ToolCapabilities struct {
// ServerCapabilities describes capabilities that a server supports.
type ServerCapabilities struct {
// NOTE: any addition to ServerCapabilities must also be reflected in
// [ServerCapabilities.clone].
// Experimental reports non-standard capabilities that the server supports.
Experimental map[string]any `json:"experimental,omitempty"`
// Completions is present if the server supports argument autocompletion
@ -1274,6 +1305,17 @@ type ServerCapabilities struct {
Tools *ToolCapabilities `json:"tools,omitempty"`
}
// clone returns a deep copy of the ServerCapabilities.
func (c *ServerCapabilities) clone() *ServerCapabilities {
cp := *c
cp.Completions = shallowClone(c.Completions)
cp.Logging = shallowClone(c.Logging)
cp.Prompts = shallowClone(c.Prompts)
cp.Resources = shallowClone(c.Resources)
cp.Tools = shallowClone(c.Tools)
return &cp
}
const (
methodCallTool = "tools/call"
notificationCancelled = "notifications/cancelled"