From 7b100d7b30c73f2a8e034c7792932d30a7711e09 Mon Sep 17 00:00:00 2001 From: Jonathan Amsterdam Date: Tue, 13 May 2025 18:17:05 -0400 Subject: [PATCH] internal/mcp: document content constructors Also, make sure that NewBlobResourceContents creates a Blob. Change-Id: I2f0fe2ac6410d6b155deb2c8643a40bc73e25cd4 Reviewed-on: https://go-review.googlesource.com/c/tools/+/672418 Reviewed-by: Robert Findley LUCI-TryBot-Result: Go LUCI --- mcp/content.go | 58 +++++++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/mcp/content.go b/mcp/content.go index 668dfb6..cac6d13 100644 --- a/mcp/content.go +++ b/mcp/content.go @@ -10,16 +10,16 @@ import ( "fmt" ) -// The []byte fields below are marked omitzero, not omitempty: -// we want to marshal an empty byte slice. -// TODO(jba): figure out how to fix this for 1.23. - // Content is the wire format for content. // It represents the protocol types TextContent, ImageContent, AudioContent // 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. +// Use [NewTextContent], [NewImageContent], [NewAudioContent] or [NewResourceContent] +// to create one. +// +// The Type field must be one of "text", "image", "audio" or "resource". The +// constructors above populate this field appropriately. +// Although at most one of Text, Data, and Resource should be non-zero, consumers of Content +// use the Type field to determine which value to use; values in the other fields are ignored. type Content struct { Type string `json:"type"` Text string `json:"text,omitempty"` @@ -44,42 +44,47 @@ func (c *Content) UnmarshalJSON(data []byte) error { return nil } +// NewTextContent creates a [Content] with text. func NewTextContent(text string) Content { return Content{Type: "text", Text: text} } +// NewImageContent creates a [Content] with image data. func NewImageContent(data []byte, mimeType string) Content { return Content{Type: "image", Data: data, MIMEType: mimeType} } +// NewAudioContent creates a [Content] with audio data. func NewAudioContent(data []byte, mimeType string) Content { return Content{Type: "audio", Data: data, MIMEType: mimeType} } -func NewResourceContent(resource ResourceContents) Content { - return Content{Type: "resource", Resource: &resource} +// NewResourceContent creates a [Content] with an embedded resource. +func NewResourceContent(resource *ResourceContents) Content { + return Content{Type: "resource", Resource: resource} } -// A ResourceContents is either a TextResourceContents or a BlobResourceContents. +// ResourceContents represents the union of the spec's {Text,Blob}ResourceContents types. // See https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-03-26/schema.ts#L524-L551 // for the inheritance structure. -// If Blob is nil, this is a TextResourceContents; otherwise it's a BlobResourceContents. -// -// The URI field describes the resource location. + +// A ResourceContents is either a TextResourceContents or a BlobResourceContents. +// Use [NewTextResourceContents] or [NextBlobResourceContents] to create one. type ResourceContents struct { - URI string `json:"uri,"` + URI string `json:"uri"` // resource location; must not be empty MIMEType string `json:"mimeType,omitempty"` Text string `json:"text"` - Blob []byte `json:"blob,omitempty"` + Blob []byte `json:"blob,omitempty"` // if nil, then text; else blob } func (r ResourceContents) MarshalJSON() ([]byte, error) { + // If we could assume Go 1.24, we could use omitzero for Blob and avoid this method. if r.URI == "" { return nil, errors.New("ResourceContents missing URI") } if r.Blob == nil { // Text. Marshal normally. - type wireResourceContents ResourceContents + type wireResourceContents ResourceContents // (lacks MarshalJSON method) return json.Marshal((wireResourceContents)(r)) } // Blob. @@ -99,8 +104,9 @@ func (r ResourceContents) MarshalJSON() ([]byte, error) { return json.Marshal(br) } -func NewTextResourceContents(uri, mimeType, text string) ResourceContents { - return ResourceContents{ +// NewTextResourceContents returns a [ResourceContents] containing text. +func NewTextResourceContents(uri, mimeType, text string) *ResourceContents { + return &ResourceContents{ URI: uri, MIMEType: mimeType, Text: text, @@ -108,15 +114,13 @@ func NewTextResourceContents(uri, mimeType, text string) ResourceContents { } } -// A BlobResourceContents is the contents of a blob resource. -type BlobResourceContents struct { - URI string - MIMEType string - Blob []byte -} - -func NewBlobResourceContents(uri, mimeType string, blob []byte) ResourceContents { - return ResourceContents{ +// NewTextResourceContents returns a [ResourceContents] containing a byte slice. +func NewBlobResourceContents(uri, mimeType string, blob []byte) *ResourceContents { + // The only way to distinguish text from blob is a non-nil Blob field. + if blob == nil { + blob = []byte{} + } + return &ResourceContents{ URI: uri, MIMEType: mimeType, Blob: blob,