@@ -10,81 +10,117 @@ import (
1010 "fmt"
1111)
1212
13- // A ContentBlock is one of a TextContent, ImageContent, AudioContent
14- // ResourceLink, or EmbeddedResource.
15- // Use [NewTextContent], [NewImageContent], [NewAudioContent], [NewResourceLink]
16- // or [NewResourceContents] to create one.
13+ // A Content is a [TextContent], [ImageContent], [AudioContent] or
14+ // [EmbeddedResource].
1715//
18- // The Type field must be one of "text", "image", "audio", "resource_link" or "resource".
19- // The constructors above populate this field appropriately.
20- // Although at most one of Text, Data, ResourceLink and Resource should be non-zero,
21- // consumers of ContentBlock use the Type field to determine which value to use;
22- // values in the other fields are ignored.
23- // TODO(jba,rfindley): rethink this type. Each kind (text, image, etc.) should have its own
24- // meta and annotations, otherwise they're duplicated for Resource and ResourceContents.
25- type ContentBlock struct {
26- Meta map [string ]any `json:"_meta,omitempty"`
27- Type string `json:"type"`
28- Text string `json:"text,omitempty"`
29- MIMEType string `json:"mimeType,omitempty"`
30- Data []byte `json:"data,omitempty"`
31- ResourceLink * Resource `json:"resource_link,omitempty"`
32- Resource * ResourceContents `json:"resource,omitempty"`
33- Annotations * Annotations `json:"annotations,omitempty"`
34- }
35-
36- func (c * ContentBlock ) UnmarshalJSON (data []byte ) error {
37- type wireContent ContentBlock // for naive unmarshaling
38- var c2 wireContent
39- if err := json .Unmarshal (data , & c2 ); err != nil {
40- return err
41- }
42- switch c2 .Type {
43- case "text" , "image" , "audio" , "resource" , "resource_link" :
44- default :
45- return fmt .Errorf ("unrecognized content type %s" , c .Type )
46- }
47- * c = ContentBlock (c2 )
48- return nil
16+ // TODO(rfindley): add ResourceLink.
17+ type Content interface {
18+ MarshalJSON () ([]byte , error )
19+ fromWire (* wireContent )
4920}
5021
51- // NewTextContent creates a [ContentBlock] with text.
52- func NewTextContent (text string ) * ContentBlock {
53- return & ContentBlock {Type : "text" , Text : text }
22+ // TextContent is a textual content.
23+ type TextContent struct {
24+ Text string
25+ Meta Meta
26+ Annotations * Annotations
5427}
5528
56- // NewImageContent creates a [ContentBlock] with image data.
57- func NewImageContent (data []byte , mimeType string ) * ContentBlock {
58- return & ContentBlock {Type : "image" , Data : data , MIMEType : mimeType }
29+ func (c * TextContent ) MarshalJSON () ([]byte , error ) {
30+ return json .Marshal (& wireContent {
31+ Type : "text" ,
32+ Text : c .Text ,
33+ Meta : c .Meta ,
34+ Annotations : c .Annotations ,
35+ })
5936}
6037
61- // NewAudioContent creates a [ContentBlock] with audio data.
62- func NewAudioContent (data []byte , mimeType string ) * ContentBlock {
63- return & ContentBlock {Type : "audio" , Data : data , MIMEType : mimeType }
38+ func (c * TextContent ) fromWire (wire * wireContent ) {
39+ c .Text = wire .Text
40+ c .Meta = wire .Meta
41+ c .Annotations = wire .Annotations
6442}
6543
66- // NewResourceLink creates a [ContentBlock] with a [Resource].
67- func NewResourceLink (r * Resource ) * ContentBlock {
68- return & ContentBlock {Type : "resource_link" , ResourceLink : r }
44+ // ImageContent contains base64-encoded image data.
45+ type ImageContent struct {
46+ Meta Meta
47+ Annotations * Annotations
48+ Data []byte // base64-encoded
49+ MIMEType string
6950}
7051
71- // NewResourceContents creates a [ContentBlock] with an embedded resource (a [ResourceContents]).
72- func NewResourceContents (rc * ResourceContents ) * ContentBlock {
73- return & ContentBlock {Type : "resource" , Resource : rc }
52+ func (c * ImageContent ) MarshalJSON () ([]byte , error ) {
53+ return json .Marshal (& wireContent {
54+ Type : "image" ,
55+ MIMEType : c .MIMEType ,
56+ Data : c .Data ,
57+ Meta : c .Meta ,
58+ Annotations : c .Annotations ,
59+ })
7460}
7561
76- // ResourceContents represents the union of the spec's {Text,Blob}ResourceContents types.
77- // See https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-03-26/schema.ts#L524-L551
78- // for the inheritance structure.
62+ func (c * ImageContent ) fromWire (wire * wireContent ) {
63+ c .MIMEType = wire .MIMEType
64+ c .Data = wire .Data
65+ c .Meta = wire .Meta
66+ c .Annotations = wire .Annotations
67+ }
68+
69+ // AudioContent contains base64-encoded audio data.
70+ type AudioContent struct {
71+ Data []byte
72+ MIMEType string
73+ Meta Meta
74+ Annotations * Annotations
75+ }
76+
77+ func (c AudioContent ) MarshalJSON () ([]byte , error ) {
78+ return json .Marshal (& wireContent {
79+ Type : "audio" ,
80+ MIMEType : c .MIMEType ,
81+ Data : c .Data ,
82+ Meta : c .Meta ,
83+ Annotations : c .Annotations ,
84+ })
85+ }
86+
87+ func (c * AudioContent ) fromWire (wire * wireContent ) {
88+ c .MIMEType = wire .MIMEType
89+ c .Data = wire .Data
90+ c .Meta = wire .Meta
91+ c .Annotations = wire .Annotations
92+ }
7993
80- // A ResourceContents is either a TextResourceContents or a BlobResourceContents.
81- // Use [NewTextResourceContents] or [NextBlobResourceContents] to create one.
94+ // EmbeddedResource contains embedded resources.
95+ type EmbeddedResource struct {
96+ Resource * ResourceContents
97+ Meta Meta
98+ Annotations * Annotations
99+ }
100+
101+ func (c * EmbeddedResource ) MarshalJSON () ([]byte , error ) {
102+ return json .Marshal (& wireContent {
103+ Type : "resource" ,
104+ Resource : c .Resource ,
105+ Meta : c .Meta ,
106+ Annotations : c .Annotations ,
107+ })
108+ }
109+
110+ func (c * EmbeddedResource ) fromWire (wire * wireContent ) {
111+ c .Resource = wire .Resource
112+ c .Meta = wire .Meta
113+ c .Annotations = wire .Annotations
114+ }
115+
116+ // ResourceContents contains the contents of a specific resource or
117+ // sub-resource.
82118type ResourceContents struct {
83- Meta map [ string ] any `json:"_meta,omitempty "`
84- URI string `json:"uri"` // resource location; must not be empty
85- MIMEType string `json:"mimeType ,omitempty"`
86- Text string `json:"text "`
87- Blob [] byte `json:"blob ,omitempty"` // if nil, then text; else blob
119+ URI string `json:"uri "`
120+ MIMEType string `json:"mimeType,omitempty"`
121+ Text string `json:"text ,omitempty"`
122+ Blob [] byte `json:"blob,omitempty "`
123+ Meta Meta `json:"_meta ,omitempty"`
88124}
89125
90126func (r ResourceContents ) MarshalJSON () ([]byte , error ) {
@@ -114,25 +150,55 @@ func (r ResourceContents) MarshalJSON() ([]byte, error) {
114150 return json .Marshal (br )
115151}
116152
117- // NewTextResourceContents returns a [ResourceContents] containing text.
118- func NewTextResourceContents (uri , mimeType , text string ) * ResourceContents {
119- return & ResourceContents {
120- URI : uri ,
121- MIMEType : mimeType ,
122- Text : text ,
123- // Blob is nil, indicating this is a TextResourceContents.
153+ // wireContent is the wire format for content.
154+ // It represents the protocol types TextContent, ImageContent, AudioContent
155+ // and EmbeddedResource.
156+ // The Type field distinguishes them. In the protocol, each type has a constant
157+ // value for the field.
158+ // At most one of Text, Data, and Resource is non-zero.
159+ type wireContent struct {
160+ Type string `json:"type"`
161+ Text string `json:"text,omitempty"`
162+ MIMEType string `json:"mimeType,omitempty"`
163+ Data []byte `json:"data,omitempty"`
164+ Resource * ResourceContents `json:"resource,omitempty"`
165+ Meta Meta `json:"_meta,omitempty"`
166+ Annotations * Annotations `json:"annotations,omitempty"`
167+ }
168+
169+ func contentsFromWire (wires []* wireContent , allow map [string ]bool ) ([]Content , error ) {
170+ var blocks []Content
171+ for _ , wire := range wires {
172+ block , err := contentFromWire (wire , allow )
173+ if err != nil {
174+ return nil , err
175+ }
176+ blocks = append (blocks , block )
124177 }
178+ return blocks , nil
125179}
126180
127- // NewBlobResourceContents returns a [ResourceContents] containing a byte slice.
128- func NewBlobResourceContents (uri , mimeType string , blob []byte ) * ResourceContents {
129- // The only way to distinguish text from blob is a non-nil Blob field.
130- if blob == nil {
131- blob = []byte {}
181+ func contentFromWire (wire * wireContent , allow map [string ]bool ) (Content , error ) {
182+ if allow != nil && ! allow [wire .Type ] {
183+ return nil , fmt .Errorf ("invalid content type %q" , wire .Type )
132184 }
133- return & ResourceContents {
134- URI : uri ,
135- MIMEType : mimeType ,
136- Blob : blob ,
185+ switch wire .Type {
186+ case "text" :
187+ v := new (TextContent )
188+ v .fromWire (wire )
189+ return v , nil
190+ case "image" :
191+ v := new (ImageContent )
192+ v .fromWire (wire )
193+ return v , nil
194+ case "audio" :
195+ v := new (AudioContent )
196+ v .fromWire (wire )
197+ return v , nil
198+ case "resource" :
199+ v := new (EmbeddedResource )
200+ v .fromWire (wire )
201+ return v , nil
137202 }
203+ return nil , fmt .Errorf ("internal error: unrecognized content type %s" , wire .Type )
138204}
0 commit comments