Repository: modelcontextprotocol/go-sdk

Issue:
Completion support
This issue tracks work on completion support, including 'Context' support added in https://github.com/modelcontextprotocol/modelcontextprotocol/pull/598.

Discussion:
@findleyr: CC @samthanawalla@samthanawalla: I will do this!

Solution patch:
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..be13052
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,12 @@
+### PR Tips
+
+Typically, PRs should consist of a single commit, and so should generally follow
+the [rules for Go commit messages](https://go.dev/wiki/CommitMessage), with the following
+changes and additions:
+
+- Markdown is allowed.
+
+- For a pervasive change, use "all" in the title instead of a package name.
+
+- The PR description should provide context (why this change?) and describe the changes
+  at a high level. Changes that are obvious from the diffs don't need to be mentioned.
diff --git a/examples/completion/main.go b/examples/completion/main.go
new file mode 100644
index 0000000..a24299b
--- /dev/null
+++ b/examples/completion/main.go
@@ -0,0 +1,49 @@
+// Copyright 2025 The Go MCP SDK Authors. All rights reserved.
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"context"
+	"fmt"
+	"log"
+
+	"github.com/modelcontextprotocol/go-sdk/mcp"
+)
+
+// This example demonstrates the minimal code to declare and assign
+// a CompletionHandler to an MCP Server's options.
+func main() {
+	// Define your custom CompletionHandler logic.
+	myCompletionHandler := func(_ context.Context, _ *mcp.ServerSession, params *mcp.CompleteParams) (*mcp.CompleteResult, error) {
+		// In a real application, you'd implement actual completion logic here.
+		// For this example, we return a fixed set of suggestions.
+		var suggestions []string
+		switch params.Ref.Type {
+		case "ref/prompt":
+			suggestions = []string{"suggestion1", "suggestion2", "suggestion3"}
+		case "ref/resource":
+			suggestions = []string{"suggestion4", "suggestion5", "suggestion6"}
+		default:
+			return nil, fmt.Errorf("unrecognized content type %s", params.Ref.Type)
+		}
+
+		return &mcp.CompleteResult{
+			Completion: mcp.CompletionResultDetails{
+				HasMore: false,
+				Total:   len(suggestions),
+				Values:  suggestions,
+			},
+		}, nil
+	}
+
+	// Create the MCP Server instance and assign the handler.
+	// No server running, just showing the configuration.
+	_ = mcp.NewServer("myServer", "v1.0.0", &mcp.ServerOptions{
+		CompletionHandler: myCompletionHandler,
+	})
+
+	log.Println("MCP Server instance created with a CompletionHandler assigned (but not running).")
+	log.Println("This example demonstrates configuration, not live interaction.")
+}
diff --git a/jsonschema/doc.go b/jsonschema/doc.go
index a53df32..f25b000 100644
--- a/jsonschema/doc.go
+++ b/jsonschema/doc.go
@@ -40,7 +40,7 @@ or as a value of this type:
 
 # Inference
 
-The [For] and [ForType] functions return a [Schema] describing the given Go type.
+The [For] function returns a [Schema] describing the given Go type.
 The type cannot contain any function or channel types, and any map types must have a string key.
 For example, calling For on the above Player type results in this schema:
 
diff --git a/jsonschema/infer.go b/jsonschema/infer.go
index 20ec134..1334bdf 100644
--- a/jsonschema/infer.go
+++ b/jsonschema/infer.go
@@ -15,40 +15,39 @@ import (
 
 // For constructs a JSON schema object for the given type argument.
 //
-// It is a convenience for ForType.
-func For[T any]() (*Schema, error) {
-	return ForType(reflect.TypeFor[T]())
-}
-
-// ForType constructs a JSON schema object for the given type.
 // It translates Go types into compatible JSON schema types, as follows:
-//   - strings have schema type "string"
-//   - bools have schema type "boolean"
-//   - signed and unsigned integer types have schema type "integer"
-//   - floating point types have schema type "number"
-//   - slices and arrays have schema type "array", and a corresponding schema
-//     for items
-//   - maps with string key have schema type "object", and corresponding
-//     schema for additionalProperties
-//   - structs have schema type "object", and disallow additionalProperties.
+//   - Strings have schema type "string".
+//   - Bools have schema type "boolean".
+//   - Signed and unsigned integer types have schema type "integer".
+//   - Floating point types have schema type "number".
+//   - Slices and arrays have schema type "array", and a corresponding schema
+//     for items.
+//   - Maps with string key have schema type "object", and corresponding
+//     schema for additionalProperties.
+//   - Structs have schema type "object", and disallow additionalProperties.
 //     Their properties are derived from exported struct fields, using the
-//     struct field json name. Fields that are marked "omitempty" are
+//     struct field JSON name. Fields that are marked "omitempty" are
 //     considered optional; all other fields become required properties.
 //
-// It returns an error if t contains (possibly recursively) any of the following Go
+// For returns an error if t contains (possibly recursively) any of the following Go
 // types, as they are incompatible with the JSON schema spec.
 //   - maps with key other than 'string'
 //   - function types
 //   - complex numbers
 //   - unsafe pointers
 //
-// The cannot be any cycles in the types.
-// TODO(rfindley): we could perhaps just skip these incompatible fields.
-func ForType(t reflect.Type) (*Schema, error) {
-	return typeSchema(t)
+// The types must not have cycles.
+func For[T any]() (*Schema, error) {
+	// TODO: consider skipping incompatible fields, instead of failing.
+	s, err := forType(reflect.TypeFor[T]())
+	if err != nil {
+		var z T
+		return nil, fmt.Errorf("For[%T](): %w", z, err)
+	}
+	return s, nil
 }
 
-func typeSchema(t reflect.Type) (*Schema, error) {
+func forType(t reflect.Type) (*Schema, error) {
 	// Follow pointers: the schema for *T is almost the same as for T, except that
 	// an explicit JSON "null" is allowed for the pointer.
 	allowNull := false
@@ -82,14 +81,14 @@ func typeSchema(t reflect.Type) (*Schema, error) {
 			return nil, fmt.Errorf("unsupported map key type %v", t.Key().Kind())
 		}
 		s.Type = "object"
-		s.AdditionalProperties, err = typeSchema(t.Elem())
+		s.AdditionalProperties, err = forType(t.Elem())
 		if err != nil {
 			return nil, fmt.Errorf("computing map value schema: %v", err)
 		}
 
 	case reflect.Slice, reflect.Array:
 		s.Type = "array"
-		s.Items, err = typeSchema(t.Elem())
+		s.Items, err = forType(t.Elem())
 		if err != nil {
 			return nil, fmt.Errorf("computing element schema: %v", err)
 		}
@@ -115,7 +114,7 @@ func typeSchema(t reflect.Type) (*Schema, error) {
 			if s.Properties == nil {
 				s.Properties = make(map[string]*Schema)
 			}
-			s.Properties[info.Name], err = typeSchema(field.Type)
+			s.Properties[info.Name], err = forType(field.Type)
 			if err != nil {
 				return nil, err
 			}
diff --git a/jsonschema/schema.go b/jsonschema/schema.go
index 1a73cc6..26623f1 100644
--- a/jsonschema/schema.go
+++ b/jsonschema/schema.go
@@ -127,6 +127,9 @@ type Schema struct {
 	// https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7
 	Format string `json:"format,omitempty"`
 
+	// Extra allows for additional keywords beyond those specified.
+	Extra map[string]any `json:"-"`
+
 	// computed fields
 
 	// This schema's base schema.
@@ -236,7 +239,7 @@ func (s *Schema) MarshalJSON() ([]byte, error) {
 		Type:                 typ,
 		schemaWithoutMethods: (*schemaWithoutMethods)(s),
 	}
-	return json.Marshal(ms)
+	return marshalStructWithMap(&ms, "Extra")
 }
 
 func (s *Schema) UnmarshalJSON(data []byte) error {
@@ -269,7 +272,7 @@ func (s *Schema) UnmarshalJSON(data []byte) error {
 	}{
 		schemaWithoutMethods: (*schemaWithoutMethods)(s),
 	}
-	if err := json.Unmarshal(data, &ms); err != nil {
+	if err := unmarshalStructWithMap(data, &ms, "Extra"); err != nil {
 		return err
 	}
 	// Unmarshal "type" as either Type or Types.
diff --git a/jsonschema/util.go b/jsonschema/util.go
index f884aff..71c3443 100644
--- a/jsonschema/util.go
+++ b/jsonschema/util.go
@@ -15,6 +15,9 @@ import (
 	"math/big"
 	"reflect"
 	"slices"
+	"sync"
+
+	"github.com/modelcontextprotocol/go-sdk/internal/util"
 )
 
 // Equal reports whether two Go values representing JSON values are equal according
@@ -282,3 +285,136 @@ func assert(cond bool, msg string) {
 		panic("assertion failed: " + msg)
 	}
 }
+
+// marshalStructWithMap marshals its first argument to JSON, treating the field named
+// mapField as an embedded map. The first argument must be a pointer to
+// a struct. The underlying type of mapField must be a map[string]any, and it must have
+// a "-" json tag, meaning it will not be marshaled.
+//
+// For example, given this struct:
+//
+//	type S struct {
+//	   A int
+//	   Extra map[string] any `json:"-"`
+//	}
+//
+// and this value:
+//
+//	s := S{A: 1, Extra: map[string]any{"B": 2}}
+//
+// the call marshalJSONWithMap(s, "Extra") would return
+//
+//	{"A": 1, "B": 2}
+//
+// It is an error if the map contains the same key as another struct field's
+// JSON name.
+//
+// marshalStructWithMap calls json.Marshal on a value of type T, so T must not
+// have a MarshalJSON method that calls this function, on pain of infinite regress.
+//
+// Note that there is a similar function in mcp/util.go, but they are not the same.
+// Here the function requires `-` json tag, does not clear the mapField map,
+// and handles embedded struct due to the implementation of jsonNames in this package.
+//
+// TODO: avoid this restriction on T by forcing it to marshal in a default way.
+// See https://go.dev/play/p/EgXKJHxEx_R.
+func marshalStructWithMap[T any](s *T, mapField string) ([]byte, error) {
+	// Marshal the struct and the map separately, and concatenate the bytes.
+	// This strategy is dramatically less complicated than
+	// constructing a synthetic struct or map with the combined keys.
+	if s == nil {
+		return []byte("null"), nil
+	}
+	s2 := *s
+	vMapField := reflect.ValueOf(&s2).Elem().FieldByName(mapField)
+	mapVal := vMapField.Interface().(map[string]any)
+
+	// Check for duplicates.
+	names := jsonNames(reflect.TypeFor[T]())
+	for key := range mapVal {
+		if names[key] {
+			return nil, fmt.Errorf("map key %q duplicates struct field", key)
+		}
+	}
+
+	structBytes, err := json.Marshal(s2)
+	if err != nil {
+		return nil, fmt.Errorf("marshalStructWithMap(%+v): %w", s, err)
+	}
+	if len(mapVal) == 0 {
+		return structBytes, nil
+	}
+	mapBytes, err := json.Marshal(mapVal)
+	if err != nil {
+		return nil, err
+	}
+	if len(structBytes) == 2 { // must be "{}"
+		return mapBytes, nil
+	}
+	// "{X}" + "{Y}" => "{X,Y}"
+	res := append(structBytes[:len(structBytes)-1], ',')
+	res = append(res, mapBytes[1:]...)
+	return res, nil
+}
+
+// unmarshalStructWithMap is the inverse of marshalStructWithMap.
+// T has the same restrictions as in that function.
+//
+// Note that there is a similar function in mcp/util.go, but they are not the same.
+// Here jsonNames also returns fields from embedded structs, hence this function
+// handles embedded structs as well.
+func unmarshalStructWithMap[T any](data []byte, v *T, mapField string) error {
+	// Unmarshal into the struct, ignoring unknown fields.
+	if err := json.Unmarshal(data, v); err != nil {
+		return err
+	}
+	// Unmarshal into the map.
+	m := map[string]any{}
+	if err := json.Unmarshal(data, &m); err != nil {
+		return err
+	}
+	// Delete from the map the fields of the struct.
+	for n := range jsonNames(reflect.TypeFor[T]()) {
+		delete(m, n)
+	}
+	if len(m) != 0 {
+		reflect.ValueOf(v).Elem().FieldByName(mapField).Set(reflect.ValueOf(m))
+	}
+	return nil
+}
+
+var jsonNamesMap sync.Map // from reflect.Type to map[string]bool
+
+// jsonNames returns the set of JSON object keys that t will marshal into,
+// including fields from embedded structs in t.
+// t must be a struct type.
+//
+// Note that there is a similar function in mcp/util.go, but they are not the same
+// Here the function recurses over embedded structs and includes fields from them.
+func jsonNames(t reflect.Type) map[string]bool {
+	// Lock not necessary: at worst we'll duplicate work.
+	if val, ok := jsonNamesMap.Load(t); ok {
+		return val.(map[string]bool)
+	}
+	m := map[string]bool{}
+	for i := range t.NumField() {
+		field := t.Field(i)
+		// handle embedded structs
+		if field.Anonymous {
+			fieldType := field.Type
+			if fieldType.Kind() == reflect.Ptr {
+				fieldType = fieldType.Elem()
+			}
+			for n := range jsonNames(fieldType) {
+				m[n] = true
+			}
+			continue
+		}
+		info := util.FieldJSONInfo(field)
+		if !info.Omit {
+			m[info.Name] = true
+		}
+	}
+	jsonNamesMap.Store(t, m)
+	return m
+}
diff --git a/jsonschema/validate.go b/jsonschema/validate.go
index 669e45a..a04e42b 100644
--- a/jsonschema/validate.go
+++ b/jsonschema/validate.go
@@ -25,6 +25,7 @@ const draft202012 = "https://json-schema.org/draft/2020-12/schema"
 
 // Validate validates the instance, which must be a JSON value, against the schema.
 // It returns nil if validation is successful or an error if it is not.
+// If the schema type is "object", instance can be a map[string]any or a struct.
 func (rs *Resolved) Validate(instance any) error {
 	if s := rs.root.Schema; s != "" && s != draft202012 {
 		return fmt.Errorf("cannot validate version %s, only %s", s, draft202012)
diff --git a/mcp/client.go b/mcp/client.go
index 856df56..a156ae2 100644
--- a/mcp/client.go
+++ b/mcp/client.go
@@ -257,6 +257,7 @@ func (c *Client) AddReceivingMiddleware(middleware ...Middleware[*ClientSession]
 
 // clientMethodInfos maps from the RPC method name to serverMethodInfos.
 var clientMethodInfos = map[string]methodInfo{
+	methodComplete:                  newMethodInfo(sessionMethod((*ClientSession).Complete)),
 	methodPing:                      newMethodInfo(sessionMethod((*ClientSession).ping)),
 	methodListRoots:                 newMethodInfo(clientMethod((*Client).listRoots)),
 	methodCreateMessage:             newMethodInfo(clientMethod((*Client).createMessage)),
@@ -352,6 +353,10 @@ func (cs *ClientSession) ReadResource(ctx context.Context, params *ReadResourceP
 	return handleSend[*ReadResourceResult](ctx, cs, methodReadResource, orZero[Params](params))
 }
 
+func (cs *ClientSession) Complete(ctx context.Context, params *CompleteParams) (*CompleteResult, error) {
+	return handleSend[*CompleteResult](ctx, cs, methodComplete, orZero[Params](params))
+}
+
 func (c *Client) callToolChangedHandler(ctx context.Context, s *ClientSession, params *ToolListChangedParams) (Result, error) {
 	return callNotificationHandler(ctx, c.opts.ToolListChangedHandler, s, params)
 }
diff --git a/mcp/content.go b/mcp/content.go
index 43f7fdc..ed7f6f9 100644
--- a/mcp/content.go
+++ b/mcp/content.go
@@ -10,10 +10,8 @@ import (
 	"fmt"
 )
 
-// A Content is a [TextContent], [ImageContent], [AudioContent] or
-// [EmbeddedResource].
-//
-// TODO(rfindley): add ResourceLink.
+// A Content is a [TextContent], [ImageContent], [AudioContent],
+// [ResourceLink], or [EmbeddedResource].
 type Content interface {
 	MarshalJSON() ([]byte, error)
 	fromWire(*wireContent)
@@ -91,6 +89,43 @@ func (c *AudioContent) fromWire(wire *wireContent) {
 	c.Annotations = wire.Annotations
 }
 
+// ResourceLink is a link to a resource
+type ResourceLink struct {
+	URI         string
+	Name        string
+	Title       string
+	Description string
+	MIMEType    string
+	Size        *int64
+	Meta        Meta
+	Annotations *Annotations
+}
+
+func (c *ResourceLink) MarshalJSON() ([]byte, error) {
+	return json.Marshal(&wireContent{
+		Type:        "resource_link",
+		URI:         c.URI,
+		Name:        c.Name,
+		Title:       c.Title,
+		Description: c.Description,
+		MIMEType:    c.MIMEType,
+		Size:        c.Size,
+		Meta:        c.Meta,
+		Annotations: c.Annotations,
+	})
+}
+
+func (c *ResourceLink) fromWire(wire *wireContent) {
+	c.URI = wire.URI
+	c.Name = wire.Name
+	c.Title = wire.Title
+	c.Description = wire.Description
+	c.MIMEType = wire.MIMEType
+	c.Size = wire.Size
+	c.Meta = wire.Meta
+	c.Annotations = wire.Annotations
+}
+
 // EmbeddedResource contains embedded resources.
 type EmbeddedResource struct {
 	Resource    *ResourceContents
@@ -151,17 +186,22 @@ func (r ResourceContents) MarshalJSON() ([]byte, error) {
 }
 
 // wireContent is the wire format for content.
-// It represents the protocol types TextContent, ImageContent, AudioContent
-// and EmbeddedResource.
+// It represents the protocol types TextContent, ImageContent, AudioContent,
+// ResourceLink, and EmbeddedResource.
 // The Type field distinguishes them. In the protocol, each type has a constant
 // value for the field.
-// At most one of Text, Data, and Resource is non-zero.
+// At most one of Text, Data, Resource, and URI is non-zero.
 type wireContent struct {
 	Type        string            `json:"type"`
 	Text        string            `json:"text,omitempty"`
 	MIMEType    string            `json:"mimeType,omitempty"`
 	Data        []byte            `json:"data,omitempty"`
 	Resource    *ResourceContents `json:"resource,omitempty"`
+	URI         string            `json:"uri,omitempty"`
+	Name        string            `json:"name,omitempty"`
+	Title       string            `json:"title,omitempty"`
+	Description string            `json:"description,omitempty"`
+	Size        *int64            `json:"size,omitempty"`
 	Meta        Meta              `json:"_meta,omitempty"`
 	Annotations *Annotations      `json:"annotations,omitempty"`
 }
@@ -195,6 +235,10 @@ func contentFromWire(wire *wireContent, allow map[string]bool) (Content, error)
 		v := new(AudioContent)
 		v.fromWire(wire)
 		return v, nil
+	case "resource_link":
+		v := new(ResourceLink)
+		v.fromWire(wire)
+		return v, nil
 	case "resource":
 		v := new(EmbeddedResource)
 		v.fromWire(wire)
diff --git a/mcp/protocol.go b/mcp/protocol.go
index babfd55..eba9e73 100644
--- a/mcp/protocol.go
+++ b/mcp/protocol.go
@@ -12,6 +12,7 @@ package mcp
 
 import (
 	"encoding/json"
+	"fmt"
 
 	"github.com/modelcontextprotocol/go-sdk/jsonschema"
 )
@@ -39,70 +40,18 @@ type Annotations struct {
 	Priority float64 `json:"priority,omitempty"`
 }
 
-type CallToolParams struct {
-	// This property is reserved by the protocol to allow clients and servers to
-	// attach additional metadata to their responses.
-	Meta      `json:"_meta,omitempty"`
-	Arguments any    `json:"arguments,omitempty"`
-	Name      string `json:"name"`
-}
-
-func (x *CallToolParams) GetProgressToken() any  { return getProgressToken(x) }
-func (x *CallToolParams) SetProgressToken(t any) { setProgressToken(x, t) }
+type CallToolParams = CallToolParamsFor[any]
 
 type CallToolParamsFor[In any] struct {
 	// This property is reserved by the protocol to allow clients and servers to
 	// attach additional metadata to their responses.
 	Meta      `json:"_meta,omitempty"`
-	Arguments In     `json:"arguments,omitempty"`
 	Name      string `json:"name"`
+	Arguments In     `json:"arguments,omitempty"`
 }
 
 // The server's response to a tool call.
-type CallToolResult struct {
-	// This property is reserved by the protocol to allow clients and servers to
-	// attach additional metadata to their responses.
-	Meta `json:"_meta,omitempty"`
-	// A list of content objects that represent the unstructured result of the tool
-	// call.
-	Content []Content `json:"content"`
-	// Whether the tool call ended in an error.
-	//
-	// If not set, this is assumed to be false (the call was successful).
-	//
-	// Any errors that originate from the tool should be reported inside the result
-	// object, with isError set to true, not as an MCP protocol-level error
-	// response. Otherwise, the LLM would not be able to see that an error occurred
-	// and self-correct.
-	//
-	// However, any errors in finding the tool, an error indicating that the
-	// server does not support tool calls, or any other exceptional conditions,
-	// should be reported as an MCP error response.
-	IsError bool `json:"isError,omitempty"`
-	// An optional JSON object that represents the structured result of the tool
-	// call.
-	// TODO(jba,rfindley): should this be any?
-	StructuredContent map[string]any `json:"structuredContent,omitempty"`
-}
-
-// UnmarshalJSON handles the unmarshalling of content into the Content
-// interface.
-func (x *CallToolResult) UnmarshalJSON(data []byte) error {
-	type res CallToolResult // avoid recursion
-	var wire struct {
-		res
-		Content []*wireContent `json:"content"`
-	}
-	if err := json.Unmarshal(data, &wire); err != nil {
-		return err
-	}
-	var err error
-	if wire.res.Content, err = contentsFromWire(wire.Content, nil); err != nil {
-		return err
-	}
-	*x = CallToolResult(wire.res)
-	return nil
-}
+type CallToolResult = CallToolResultFor[any]
 
 type CallToolResultFor[Out any] struct {
 	// This property is reserved by the protocol to allow clients and servers to
@@ -111,6 +60,9 @@ type CallToolResultFor[Out any] struct {
 	// A list of content objects that represent the unstructured result of the tool
 	// call.
 	Content []Content `json:"content"`
+	// An optional JSON object that represents the structured result of the tool
+	// call.
+	StructuredContent Out `json:"structuredContent,omitempty"`
 	// Whether the tool call ended in an error.
 	//
 	// If not set, this is assumed to be false (the call was successful).
@@ -124,9 +76,6 @@ type CallToolResultFor[Out any] struct {
 	// server does not support tool calls, or any other exceptional conditions,
 	// should be reported as an MCP error response.
 	IsError bool `json:"isError,omitempty"`
-	// An optional JSON object that represents the structured result of the tool
-	// call.
-	StructuredContent Out `json:"structuredContent,omitempty"`
 }
 
 // UnmarshalJSON handles the unmarshalling of content into the Content
@@ -148,6 +97,9 @@ func (x *CallToolResultFor[Out]) UnmarshalJSON(data []byte) error {
 	return nil
 }
 
+func (x *CallToolParamsFor[Out]) GetProgressToken() any  { return getProgressToken(x) }
+func (x *CallToolParamsFor[Out]) SetProgressToken(t any) { setProgressToken(x, t) }
+
 type CancelledParams struct {
 	// This property is reserved by the protocol to allow clients and servers to
 	// attach additional metadata to their responses.
@@ -182,6 +134,93 @@ type ClientCapabilities struct {
 	Elicitation *ElicitationCapabilities `json:"elicitation,omitempty"`
 }
 
+type CompleteParamsArgument struct {
+	// The name of the argument
+	Name string `json:"name"`
+	// The value of the argument to use for completion matching.
+	Value string `json:"value"`
+}
+
+// CompleteContext represents additional, optional context for completions.
+type CompleteContext struct {
+	// Previously-resolved variables in a URI template or prompt.
+	Arguments map[string]string `json:"arguments,omitempty"`
+}
+
+// CompleteReference represents a completion reference type (ref/prompt ref/resource).
+// The Type field determines which other fields are relevant.
+type CompleteReference struct {
+	Type string `json:"type"`
+	// Name is relevant when Type is "ref/prompt".
+	Name string `json:"name,omitempty"`
+	// URI is relevant when Type is "ref/resource".
+	URI string `json:"uri,omitempty"`
+}
+
+func (r *CompleteReference) UnmarshalJSON(data []byte) error {
+	type wireCompleteReference CompleteReference // for naive unmarshaling
+	var r2 wireCompleteReference
+	if err := json.Unmarshal(data, &r2); err != nil {
+		return err
+	}
+	switch r2.Type {
+	case "ref/prompt", "ref/resource":
+		if r2.Type == "ref/prompt" && r2.URI != "" {
+			return fmt.Errorf("reference of type %q must not have a URI set", r2.Type)
+		}
+		if r2.Type == "ref/resource" && r2.Name != "" {
+			return fmt.Errorf("reference of type %q must not have a Name set", r2.Type)
+		}
+	default:
+		return fmt.Errorf("unrecognized content type %q", r2.Type)
+	}
+	*r = CompleteReference(r2)
+	return nil
+}
+
+func (r *CompleteReference) MarshalJSON() ([]byte, error) {
+	// Validation for marshalling: ensure consistency before converting to JSON.
+	switch r.Type {
+	case "ref/prompt":
+		if r.URI != "" {
+			return nil, fmt.Errorf("reference of type %q must not have a URI set for marshalling", r.Type)
+		}
+	case "ref/resource":
+		if r.Name != "" {
+			return nil, fmt.Errorf("reference of type %q must not have a Name set for marshalling", r.Type)
+		}
+	default:
+		return nil, fmt.Errorf("unrecognized reference type %q for marshalling", r.Type)
+	}
+
+	type wireReference CompleteReference
+	return json.Marshal(wireReference(*r))
+}
+
+type CompleteParams struct {
+	// This property is reserved by the protocol to allow clients and servers to
+	// attach additional metadata to their responses.
+	Meta `json:"_meta,omitempty"`
+	// The argument's information
+	Argument CompleteParamsArgument `json:"argument"`
+	Context  *CompleteContext       `json:"context,omitempty"`
+	Ref      *CompleteReference     `json:"ref"`
+}
+
+type CompletionResultDetails struct {
+	HasMore bool     `json:"hasMore,omitempty"`
+	Total   int      `json:"total,omitempty"`
+	Values  []string `json:"values"`
+}
+
+// The server's response to a completion/complete request
+type CompleteResult struct {
+	// This property is reserved by the protocol to allow clients and servers to
+	// attach additional metadata to their responses.
+	Meta       `json:"_meta,omitempty"`
+	Completion CompletionResultDetails `json:"completion"`
+}
+
 type CreateMessageParams struct {
 	// This property is reserved by the protocol to allow clients and servers to
 	// attach additional metadata to their responses.
@@ -836,6 +875,9 @@ type implementation struct {
 	Version string `json:"version"`
 }
 
+// Present if the server supports argument autocompletion suggestions.
+type completionCapabilities struct{}
+
 // Present if the server supports sending log messages to the client.
 type loggingCapabilities struct{}
 
@@ -858,7 +900,7 @@ type resourceCapabilities struct {
 // additional capabilities.
 type serverCapabilities struct {
 	// Present if the server supports argument autocompletion suggestions.
-	Completions struct{} `json:"completions,omitempty"`
+	Completions *completionCapabilities `json:"completions,omitempty"`
 	// Experimental, non-standard capabilities that the server supports.
 	Experimental map[string]struct{} `json:"experimental,omitempty"`
 	// Present if the server supports sending log messages to the client.
diff --git a/mcp/server.go b/mcp/server.go
index 44ec7aa..2bc0c8b 100644
--- a/mcp/server.go
+++ b/mcp/server.go
@@ -58,6 +58,8 @@ type ServerOptions struct {
 	RootsListChangedHandler func(context.Context, *ServerSession, *RootsListChangedParams)
 	// If non-nil, called when "notifications/progress" is received.
 	ProgressNotificationHandler func(context.Context, *ServerSession, *ProgressNotificationParams)
+	// If non-nil, called when "completion/complete" is received.
+	CompletionHandler func(context.Context, *ServerSession, *CompleteParams) (*CompleteResult, error)
 	// If non-zero, defines an interval for regular "ping" requests.
 	// If the peer fails to respond to pings originating from the keepalive check,
 	// the session is automatically closed.
@@ -231,6 +233,13 @@ func (s *Server) RemoveResourceTemplates(uriTemplates ...string) {
 		func() bool { return s.resourceTemplates.remove(uriTemplates...) })
 }
 
+func (s *Server) complete(ctx context.Context, ss *ServerSession, params *CompleteParams) (Result, error) {
+	if s.opts.CompletionHandler == nil {
+		return nil, jsonrpc2.ErrMethodNotFound
+	}
+	return s.opts.CompletionHandler(ctx, ss, params)
+}
+
 // changeAndNotify is called when a feature is added or removed.
 // It calls change, which should do the work and report whether a change actually occurred.
 // If there was a change, it notifies a snapshot of the sessions.
@@ -570,6 +579,7 @@ func (s *Server) AddReceivingMiddleware(middleware ...Middleware[*ServerSession]
 
 // serverMethodInfos maps from the RPC method name to serverMethodInfos.
 var serverMethodInfos = map[string]methodInfo{
+	methodComplete:               newMethodInfo(serverMethod((*Server).complete)),
 	methodInitialize:             newMethodInfo(sessionMethod((*ServerSession).initialize)),
 	methodPing:                   newMethodInfo(sessionMethod((*ServerSession).ping)),
 	methodListPrompts:            newMethodInfo(serverMethod((*Server).listPrompts)),
@@ -653,6 +663,7 @@ func (ss *ServerSession) initialize(ctx context.Context, params *InitializeParam
 		// reject unsupported features.
 		ProtocolVersion: version,
 		Capabilities: &serverCapabilities{
+			Completions: &completionCapabilities{},
 			Prompts: &promptCapabilities{
 				ListChanged: true,
 			},
diff --git a/mcp/tool.go b/mcp/tool.go
index 5bf9440..a6f228e 100644
--- a/mcp/tool.go
+++ b/mcp/tool.go
@@ -17,7 +17,7 @@ import (
 // A ToolHandler handles a call to tools/call.
 // [CallToolParams.Arguments] will contain a map[string]any that has been validated
 // against the input schema.
-// Perhaps this should be an alias for ToolHandlerFor[map[string]any, map[string]any].
+// TODO: Perhaps this should be an alias for ToolHandlerFor[map[string]any, map[string]any]?
 type ToolHandler func(context.Context, *ServerSession, *CallToolParamsFor[map[string]any]) (*CallToolResult, error)
 
 // A ToolHandlerFor handles a call to tools/call with typed arguments and results.



Test patch:
diff --git a/jsonschema/schema_test.go b/jsonschema/schema_test.go
index 0d3ea09..4ceb1ee 100644
--- a/jsonschema/schema_test.go
+++ b/jsonschema/schema_test.go
@@ -26,6 +26,7 @@ func TestGoRoundTrip(t *testing.T) {
 		{Const: Ptr(any(map[string]any{}))},
 		{Default: mustMarshal(1)},
 		{Default: mustMarshal(nil)},
+		{Extra: map[string]any{"test": "value"}},
 	} {
 		data, err := json.Marshal(s)
 		if err != nil {
@@ -64,11 +65,19 @@ func TestJSONRoundTrip(t *testing.T) {
 			`{"$vocabulary":{"b":true, "a":false}}`,
 			`{"$vocabulary":{"a":false,"b":true}}`,
 		},
-		{`{"unk":0}`, `{}`}, // unknown fields are dropped, unfortunately
+		{`{"unk":0}`, `{"unk":0}`}, // unknown fields are not dropped
+		{
+			// known and unknown fields are not dropped
+			// note that the order will be by the declaration order in the anonymous struct inside MarshalJSON
+			`{"comment":"test","type":"example","unk":0}`,
+			`{"type":"example","comment":"test","unk":0}`,
+		},
+		{`{"extra":0}`, `{"extra":0}`}, // extra is not a special keyword and should not be dropped
+		{`{"Extra":0}`, `{"Extra":0}`}, // Extra is not a special keyword and should not be dropped
 	} {
 		var s Schema
 		mustUnmarshal(t, []byte(tt.in), &s)
-		data, err := json.Marshal(s)
+		data, err := json.Marshal(&s)
 		if err != nil {
 			t.Fatal(err)
 		}
diff --git a/jsonschema/util_test.go b/jsonschema/util_test.go
index 189cb26..03ccb4d 100644
--- a/jsonschema/util_test.go
+++ b/jsonschema/util_test.go
@@ -8,7 +8,11 @@ import (
 	"encoding/json"
 	"hash/maphash"
 	"reflect"
+	"strings"
 	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
 )
 
 func TestEqual(t *testing.T) {
@@ -125,3 +129,58 @@ func TestHash(t *testing.T) {
 	}
 	_ = hash(null)
 }
+
+func TestMarshalStructWithMap(t *testing.T) {
+	type S struct {
+		A int
+		B string `json:"b,omitempty"`
+		u bool
+		M map[string]any `json:"-"`
+	}
+	t.Run("basic", func(t *testing.T) {
+		s := S{A: 1, B: "two", M: map[string]any{"!@#": true}}
+		got, err := marshalStructWithMap(&s, "M")
+		if err != nil {
+			t.Fatal(err)
+		}
+		want := `{"A":1,"b":"two","!@#":true}`
+		if g := string(got); g != want {
+			t.Errorf("\ngot  %s\nwant %s", g, want)
+		}
+
+		var un S
+		if err := unmarshalStructWithMap(got, &un, "M"); err != nil {
+			t.Fatal(err)
+		}
+		if diff := cmp.Diff(s, un, cmpopts.IgnoreUnexported(S{})); diff != "" {
+			t.Errorf("mismatch (-want, +got):\n%s", diff)
+		}
+	})
+	t.Run("duplicate", func(t *testing.T) {
+		s := S{A: 1, B: "two", M: map[string]any{"b": "dup"}}
+		_, err := marshalStructWithMap(&s, "M")
+		if err == nil || !strings.Contains(err.Error(), "duplicate") {
+			t.Errorf("got %v, want error with 'duplicate'", err)
+		}
+	})
+	t.Run("embedded", func(t *testing.T) {
+		type Embedded struct {
+			A     int
+			B     int
+			Extra map[string]any `json:"-"`
+		}
+		type S struct {
+			C int
+			Embedded
+		}
+		s := S{C: 1, Embedded: Embedded{A: 2, B: 3, Extra: map[string]any{"d": 4, "e": 5}}}
+		got, err := marshalStructWithMap(&s, "Extra")
+		if err != nil {
+			t.Fatal(err)
+		}
+		want := `{"C":1,"A":2,"B":3,"d":4,"e":5}`
+		if g := string(got); g != want {
+			t.Errorf("got %v, want %v", g, want)
+		}
+	})
+}
diff --git a/mcp/content_test.go b/mcp/content_test.go
index 5270f99..5ee6f66 100644
--- a/mcp/content_test.go
+++ b/mcp/content_test.go
@@ -7,7 +7,6 @@ package mcp_test
 import (
 	"encoding/json"
 	"fmt"
-	"log"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
@@ -83,6 +82,24 @@ func TestContent(t *testing.T) {
 			},
 			`{"type":"resource","resource":{"uri":"file://foo","mimeType":"text","text":"abc"},"_meta":{"key":"value"},"annotations":{"priority":1}}`,
 		},
+		{
+			&mcp.ResourceLink{
+				URI:  "file:///path/to/file.txt",
+				Name: "file.txt",
+			},
+			`{"type":"resource_link","uri":"file:///path/to/file.txt","name":"file.txt"}`,
+		},
+		{
+			&mcp.ResourceLink{
+				URI:         "https://example.com/resource",
+				Name:        "Example Resource",
+				Title:       "A comprehensive example resource",
+				Description: "This resource demonstrates all fields",
+				MIMEType:    "text/plain",
+				Meta:        mcp.Meta{"custom": "metadata"},
+			},
+			`{"type":"resource_link","mimeType":"text/plain","uri":"https://example.com/resource","name":"Example Resource","title":"A comprehensive example resource","description":"This resource demonstrates all fields","_meta":{"custom":"metadata"}}`,
+		},
 	}
 
 	for _, test := range tests {
@@ -94,7 +111,6 @@ func TestContent(t *testing.T) {
 			t.Errorf("json.Marshal(%v) mismatch (-want +got):\n%s", test.in, diff)
 		}
 		result := fmt.Sprintf(`{"content":[%s]}`, string(got))
-		log.Println(result)
 		var out mcp.CallToolResult
 		if err := json.Unmarshal([]byte(result), &out); err != nil {
 			t.Fatal(err)
diff --git a/mcp/protocol_test.go b/mcp/protocol_test.go
index 823c409..dba80a8 100644
--- a/mcp/protocol_test.go
+++ b/mcp/protocol_test.go
@@ -70,6 +70,423 @@ func TestParamsMeta(t *testing.T) {
 	p.SetProgressToken(int64(1))
 }
 
+func TestCompleteReference(t *testing.T) {
+	marshalTests := []struct {
+		name    string
+		in      CompleteReference // The Go struct to marshal
+		want    string            // The expected JSON string output
+		wantErr bool              // True if json.Marshal is expected to return an error
+	}{
+		{
+			name:    "ValidPrompt",
+			in:      CompleteReference{Type: "ref/prompt", Name: "my_prompt"},
+			want:    `{"type":"ref/prompt","name":"my_prompt"}`,
+			wantErr: false,
+		},
+		{
+			name:    "ValidResource",
+			in:      CompleteReference{Type: "ref/resource", URI: "file:///path/to/resource.txt"},
+			want:    `{"type":"ref/resource","uri":"file:///path/to/resource.txt"}`,
+			wantErr: false,
+		},
+		{
+			name:    "ValidPromptEmptyName",
+			in:      CompleteReference{Type: "ref/prompt", Name: ""},
+			want:    `{"type":"ref/prompt"}`,
+			wantErr: false,
+		},
+		{
+			name:    "ValidResourceEmptyURI",
+			in:      CompleteReference{Type: "ref/resource", URI: ""},
+			want:    `{"type":"ref/resource"}`,
+			wantErr: false,
+		},
+		// Error cases for MarshalJSON
+		{
+			name:    "InvalidType",
+			in:      CompleteReference{Type: "ref/unknown", Name: "something"},
+			wantErr: true,
+		},
+		{
+			name:    "PromptWithURI",
+			in:      CompleteReference{Type: "ref/prompt", Name: "my_prompt", URI: "unexpected_uri"},
+			wantErr: true,
+		},
+		{
+			name:    "ResourceWithName",
+			in:      CompleteReference{Type: "ref/resource", URI: "my_uri", Name: "unexpected_name"},
+			wantErr: true,
+		},
+		{
+			name:    "MissingTypeField",
+			in:      CompleteReference{Name: "missing"}, // Type is ""
+			wantErr: true,
+		},
+	}
+
+	// Define test cases specifically for Unmarshalling
+	unmarshalTests := []struct {
+		name    string
+		in      string            // The JSON string input
+		want    CompleteReference // The expected Go struct output
+		wantErr bool              // True if json.Unmarshal is expected to return an error
+	}{
+		{
+			name:    "ValidPrompt",
+			in:      `{"type":"ref/prompt","name":"my_prompt"}`,
+			want:    CompleteReference{Type: "ref/prompt", Name: "my_prompt"},
+			wantErr: false,
+		},
+		{
+			name:    "ValidResource",
+			in:      `{"type":"ref/resource","uri":"file:///path/to/resource.txt"}`,
+			want:    CompleteReference{Type: "ref/resource", URI: "file:///path/to/resource.txt"},
+			wantErr: false,
+		},
+		// Error cases for UnmarshalJSON
+		{
+			name:    "UnrecognizedType",
+			in:      `{"type":"ref/unknown","name":"something"}`,
+			want:    CompleteReference{}, // placeholder, as unmarshal will fail
+			wantErr: true,
+		},
+		{
+			name:    "PromptWithURI",
+			in:      `{"type":"ref/prompt","name":"my_prompt","uri":"unexpected_uri"}`,
+			want:    CompleteReference{}, // placeholder
+			wantErr: true,
+		},
+		{
+			name:    "ResourceWithName",
+			in:      `{"type":"ref/resource","uri":"my_uri","name":"unexpected_name"}`,
+			want:    CompleteReference{}, // placeholder
+			wantErr: true,
+		},
+		{
+			name:    "MissingType",
+			in:      `{"name":"missing"}`,
+			want:    CompleteReference{}, // placeholder
+			wantErr: true,
+		},
+		{
+			name:    "InvalidJSON",
+			in:      `invalid json`,
+			want:    CompleteReference{}, // placeholder
+			wantErr: true,                // json.Unmarshal will fail natively
+		},
+	}
+
+	// Run Marshal Tests
+	for _, test := range marshalTests {
+		t.Run("Marshal/"+test.name, func(t *testing.T) {
+			gotBytes, err := json.Marshal(&test.in)
+			if (err != nil) != test.wantErr {
+				t.Errorf("json.Marshal(%v) got error %v (want error %t)", test.in, err, test.wantErr)
+			}
+			if !test.wantErr { // Only check JSON output if marshal was expected to succeed
+				if diff := cmp.Diff(test.want, string(gotBytes)); diff != "" {
+					t.Errorf("json.Marshal(%v) mismatch (-want +got):\n%s", test.in, diff)
+				}
+			}
+		})
+	}
+
+	// Run Unmarshal Tests
+	for _, test := range unmarshalTests {
+		t.Run("Unmarshal/"+test.name, func(t *testing.T) {
+			var got CompleteReference
+			err := json.Unmarshal([]byte(test.in), &got)
+
+			if (err != nil) != test.wantErr {
+				t.Errorf("json.Unmarshal(%q) got error %v (want error %t)", test.in, err, test.wantErr)
+			}
+			if !test.wantErr { // Only check content if unmarshal was expected to succeed
+				if diff := cmp.Diff(test.want, got); diff != "" {
+					t.Errorf("json.Unmarshal(%q) mismatch (-want +got):\n%s", test.in, diff)
+				}
+			}
+		})
+	}
+}
+func TestCompleteParams(t *testing.T) {
+	// Define test cases specifically for Marshalling
+	marshalTests := []struct {
+		name string
+		in   CompleteParams
+		want string // Expected JSON output
+	}{
+		{
+			name: "BasicPromptCompletion",
+			in: CompleteParams{
+				Ref: &CompleteReference{
+					Type: "ref/prompt",
+					Name: "my_prompt",
+				},
+				Argument: CompleteParamsArgument{
+					Name:  "language",
+					Value: "go",
+				},
+			},
+			want: `{"argument":{"name":"language","value":"go"},"ref":{"type":"ref/prompt","name":"my_prompt"}}`,
+		},
+		{
+			name: "ResourceCompletionRequest",
+			in: CompleteParams{
+				Ref: &CompleteReference{
+					Type: "ref/resource",
+					URI:  "file:///src/main.java",
+				},
+				Argument: CompleteParamsArgument{
+					Name:  "class",
+					Value: "MyClas",
+				},
+			},
+			want: `{"argument":{"name":"class","value":"MyClas"},"ref":{"type":"ref/resource","uri":"file:///src/main.java"}}`,
+		},
+		{
+			name: "PromptCompletionEmptyArgumentValue",
+			in: CompleteParams{
+				Ref: &CompleteReference{
+					Type: "ref/prompt",
+					Name: "another_prompt",
+				},
+				Argument: CompleteParamsArgument{
+					Name:  "query",
+					Value: "",
+				},
+			},
+			want: `{"argument":{"name":"query","value":""},"ref":{"type":"ref/prompt","name":"another_prompt"}}`,
+		},
+		{
+			name: "PromptCompletionWithContext",
+			in: CompleteParams{
+				Ref: &CompleteReference{
+					Type: "ref/prompt",
+					Name: "my_prompt",
+				},
+				Argument: CompleteParamsArgument{
+					Name:  "language",
+					Value: "go",
+				},
+				Context: &CompleteContext{
+					Arguments: map[string]string{
+						"framework": "mcp",
+						"language":  "python",
+					},
+				},
+			},
+			want: `{"argument":{"name":"language","value":"go"},"context":{"arguments":{"framework":"mcp","language":"python"}},"ref":{"type":"ref/prompt","name":"my_prompt"}}`,
+		},
+		{
+			name: "PromptCompletionEmptyContextArguments",
+			in: CompleteParams{
+				Ref: &CompleteReference{
+					Type: "ref/prompt",
+					Name: "my_prompt",
+				},
+				Argument: CompleteParamsArgument{
+					Name:  "language",
+					Value: "go",
+				},
+				Context: &CompleteContext{
+					Arguments: map[string]string{},
+				},
+			},
+			want: `{"argument":{"name":"language","value":"go"},"context":{},"ref":{"type":"ref/prompt","name":"my_prompt"}}`,
+		},
+	}
+
+	// Define test cases specifically for Unmarshalling
+	unmarshalTests := []struct {
+		name string
+		in   string         // JSON string input
+		want CompleteParams // Expected Go struct output
+	}{
+		{
+			name: "BasicPromptCompletion",
+			in:   `{"argument":{"name":"language","value":"go"},"ref":{"type":"ref/prompt","name":"my_prompt"}}`,
+			want: CompleteParams{
+				Ref:      &CompleteReference{Type: "ref/prompt", Name: "my_prompt"},
+				Argument: CompleteParamsArgument{Name: "language", Value: "go"},
+			},
+		},
+		{
+			name: "ResourceCompletionRequest",
+			in:   `{"argument":{"name":"class","value":"MyClas"},"ref":{"type":"ref/resource","uri":"file:///src/main.java"}}`,
+			want: CompleteParams{
+				Ref:      &CompleteReference{Type: "ref/resource", URI: "file:///src/main.java"},
+				Argument: CompleteParamsArgument{Name: "class", Value: "MyClas"},
+			},
+		},
+		{
+			name: "PromptCompletionWithContext",
+			in:   `{"argument":{"name":"language","value":"go"},"context":{"arguments":{"framework":"mcp","language":"python"}},"ref":{"type":"ref/prompt","name":"my_prompt"}}`,
+			want: CompleteParams{
+				Ref:      &CompleteReference{Type: "ref/prompt", Name: "my_prompt"},
+				Argument: CompleteParamsArgument{Name: "language", Value: "go"},
+				Context: &CompleteContext{Arguments: map[string]string{
+					"framework": "mcp",
+					"language":  "python",
+				}},
+			},
+		},
+		{
+			name: "PromptCompletionEmptyContextArguments",
+			in:   `{"argument":{"name":"language","value":"go"},"context":{"arguments":{}},"ref":{"type":"ref/prompt","name":"my_prompt"}}`,
+			want: CompleteParams{
+				Ref:      &CompleteReference{Type: "ref/prompt", Name: "my_prompt"},
+				Argument: CompleteParamsArgument{Name: "language", Value: "go"},
+				Context:  &CompleteContext{Arguments: map[string]string{}},
+			},
+		},
+		{
+			name: "PromptCompletionNilContext", // JSON `null` for context
+			in:   `{"argument":{"name":"language","value":"go"},"context":null,"ref":{"type":"ref/prompt","name":"my_prompt"}}`,
+			want: CompleteParams{
+				Ref:      &CompleteReference{Type: "ref/prompt", Name: "my_prompt"},
+				Argument: CompleteParamsArgument{Name: "language", Value: "go"},
+				Context:  nil, // Should unmarshal to nil pointer
+			},
+		},
+	}
+
+	// Run Marshal Tests
+	for _, test := range marshalTests {
+		t.Run("Marshal/"+test.name, func(t *testing.T) {
+			got, err := json.Marshal(&test.in) // Marshal takes a pointer
+			if err != nil {
+				t.Fatalf("json.Marshal(CompleteParams) failed: %v", err)
+			}
+			if diff := cmp.Diff(test.want, string(got)); diff != "" {
+				t.Errorf("CompleteParams marshal mismatch (-want +got):\n%s", diff)
+			}
+		})
+	}
+
+	// Run Unmarshal Tests
+	for _, test := range unmarshalTests {
+		t.Run("Unmarshal/"+test.name, func(t *testing.T) {
+			var got CompleteParams
+			if err := json.Unmarshal([]byte(test.in), &got); err != nil {
+				t.Fatalf("json.Unmarshal(CompleteParams) failed: %v", err)
+			}
+			if diff := cmp.Diff(test.want, got); diff != "" {
+				t.Errorf("CompleteParams unmarshal mismatch (-want +got):\n%s", diff)
+			}
+		})
+	}
+}
+
+func TestCompleteResult(t *testing.T) {
+	// Define test cases specifically for Marshalling
+	marshalTests := []struct {
+		name string
+		in   CompleteResult
+		want string // Expected JSON output
+	}{
+		{
+			name: "BasicCompletionResult",
+			in: CompleteResult{
+				Completion: CompletionResultDetails{
+					Values:  []string{"golang", "google", "goroutine"},
+					Total:   10,
+					HasMore: true,
+				},
+			},
+			want: `{"completion":{"hasMore":true,"total":10,"values":["golang","google","goroutine"]}}`,
+		},
+		{
+			name: "CompletionResultNoTotalNoHasMore",
+			in: CompleteResult{
+				Completion: CompletionResultDetails{
+					Values:  []string{"only"},
+					HasMore: false,
+					Total:   0,
+				},
+			},
+			want: `{"completion":{"values":["only"]}}`,
+		},
+		{
+			name: "CompletionResultEmptyValues",
+			in: CompleteResult{
+				Completion: CompletionResultDetails{
+					Values:  []string{},
+					Total:   0,
+					HasMore: false,
+				},
+			},
+			want: `{"completion":{"values":[]}}`,
+		},
+	}
+
+	// Define test cases specifically for Unmarshalling
+	unmarshalTests := []struct {
+		name string
+		in   string         // JSON string input
+		want CompleteResult // Expected Go struct output
+	}{
+		{
+			name: "BasicCompletionResult",
+			in:   `{"completion":{"hasMore":true,"total":10,"values":["golang","google","goroutine"]}}`,
+			want: CompleteResult{
+				Completion: CompletionResultDetails{
+					Values:  []string{"golang", "google", "goroutine"},
+					Total:   10,
+					HasMore: true,
+				},
+			},
+		},
+		{
+			name: "CompletionResultNoTotalNoHasMore",
+			in:   `{"completion":{"values":["only"]}}`,
+			want: CompleteResult{
+				Completion: CompletionResultDetails{
+					Values:  []string{"only"},
+					HasMore: false,
+					Total:   0,
+				},
+			},
+		},
+		{
+			name: "CompletionResultEmptyValues",
+			in:   `{"completion":{"hasMore":false,"total":0,"values":[]}}`,
+			want: CompleteResult{
+				Completion: CompletionResultDetails{
+					Values:  []string{},
+					Total:   0,
+					HasMore: false,
+				},
+			},
+		},
+	}
+
+	// Run Marshal Tests
+	for _, test := range marshalTests {
+		t.Run("Marshal/"+test.name, func(t *testing.T) {
+			got, err := json.Marshal(&test.in) // Marshal takes a pointer
+			if err != nil {
+				t.Fatalf("json.Marshal(CompleteResult) failed: %v", err)
+			}
+			if diff := cmp.Diff(test.want, string(got)); diff != "" {
+				t.Errorf("CompleteResult marshal mismatch (-want +got):\n%s", diff)
+			}
+		})
+	}
+
+	// Run Unmarshal Tests
+	for _, test := range unmarshalTests {
+		t.Run("Unmarshal/"+test.name, func(t *testing.T) {
+			var got CompleteResult
+			if err := json.Unmarshal([]byte(test.in), &got); err != nil {
+				t.Fatalf("json.Unmarshal(CompleteResult) failed: %v", err)
+			}
+			if diff := cmp.Diff(test.want, got); diff != "" {
+				t.Errorf("CompleteResult unmarshal mismatch (-want +got):\n%s", diff)
+			}
+		})
+	}
+}
+
 func TestContentUnmarshal(t *testing.T) {
 	// Verify that types with a Content field round-trip properly.
 	roundtrip := func(in, out any) {
diff --git a/mcp/streamable_test.go b/mcp/streamable_test.go
index d496f83..963d614 100644
--- a/mcp/streamable_test.go
+++ b/mcp/streamable_test.go
@@ -143,10 +143,11 @@ func TestStreamableServerTransport(t *testing.T) {
 	initReq := req(1, "initialize", &InitializeParams{})
 	initResp := resp(1, &InitializeResult{
 		Capabilities: &serverCapabilities{
-			Logging:   &loggingCapabilities{},
-			Prompts:   &promptCapabilities{ListChanged: true},
-			Resources: &resourceCapabilities{ListChanged: true},
-			Tools:     &toolCapabilities{ListChanged: true},
+			Completions: &completionCapabilities{},
+			Logging:     &loggingCapabilities{},
+			Prompts:     &promptCapabilities{ListChanged: true},
+			Resources:   &resourceCapabilities{ListChanged: true},
+			Tools:       &toolCapabilities{ListChanged: true},
 		},
 		ProtocolVersion: "2025-03-26",
 		ServerInfo:      &implementation{Name: "testServer", Version: "v1.0.0"},