@@ -6,133 +6,129 @@ package mcp
66
77import (
88 "encoding/json"
9- "errors"
109 "fmt"
1110)
1211
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.
17- //
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
12+ // A Content is a [TextContent], [ImageContent], [AudioContent] or
13+ // [EmbeddedResource].
14+ type Content interface {
15+ MarshalJSON () ([]byte , error )
16+ fromWire (* wireContent )
17+ }
18+
19+ // TextContent is a textual content.
20+ type TextContent struct {
21+ Text string
22+ }
23+
24+ func (c * TextContent ) MarshalJSON () ([]byte , error ) {
25+ return json .Marshal (& wireContent {Type : "text" , Text : c .Text })
4926}
5027
51- // NewTextContent creates a [ContentBlock] with text.
52- func NewTextContent (text string ) * ContentBlock {
53- return & ContentBlock {Type : "text" , Text : text }
28+ func (c * TextContent ) fromWire (wire * wireContent ) {
29+ c .Text = wire .Text
5430}
5531
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 }
32+ // ImageContent contains base64-encoded image data.
33+ type ImageContent struct {
34+ Data []byte // base64-encoded
35+ MIMEType string
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 * ImageContent ) MarshalJSON () ([]byte , error ) {
39+ return json .Marshal (& wireContent {Type : "image" , MIMEType : c .MIMEType , Data : c .Data })
6440}
6541
66- // NewResourceLink creates a [ContentBlock] with a [Resource].
67- func NewResourceLink ( r * Resource ) * ContentBlock {
68- return & ContentBlock { Type : "resource_link" , ResourceLink : r }
42+ func ( c * ImageContent ) fromWire ( wire * wireContent ) {
43+ c . MIMEType = wire . MIMEType
44+ c . Data = wire . Data
6945}
7046
71- // NewResourceContents creates a [ContentBlock] with an embedded resource (a [ResourceContents]).
72- func NewResourceContents (rc * ResourceContents ) * ContentBlock {
73- return & ContentBlock {Type : "resource" , Resource : rc }
47+ // AudioContent contains base64-encoded audio data.
48+ type AudioContent struct {
49+ Data []byte
50+ MIMEType string
7451}
7552
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.
53+ func ( c AudioContent ) MarshalJSON () ([] byte , error ) {
54+ return json . Marshal ( & wireContent { Type : "audio" , MIMEType : c . MIMEType , Data : c . Data })
55+ }
7956
80- // A ResourceContents is either a TextResourceContents or a BlobResourceContents.
81- // Use [NewTextResourceContents] or [NextBlobResourceContents] to create one.
57+ func (c * AudioContent ) fromWire (wire * wireContent ) {
58+ c .MIMEType = wire .MIMEType
59+ c .Data = wire .Data
60+ }
61+
62+ // EmbeddedResource contains embedded resources.
63+ type EmbeddedResource struct {
64+ Resource * ResourceContents
65+ }
66+
67+ func (r * EmbeddedResource ) MarshalJSON () ([]byte , error ) {
68+ return json .Marshal (& wireContent {Type : "resource" , Resource : r .Resource })
69+ }
70+
71+ func (c * EmbeddedResource ) fromWire (wire * wireContent ) {
72+ c .Resource = wire .Resource
73+ }
74+
75+ // ResourceContents contains the contents of a specific resource or
76+ // sub-resource.
8277type 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
78+ URI string `json:"uri,"`
79+ MIMEType string `json:"mimeType,omitempty"`
80+ Text string `json:"text,omitempty"`
81+ Blob []byte `json:"blob,omitempty"`
8882}
8983
90- func (r ResourceContents ) MarshalJSON () ([]byte , error ) {
91- // If we could assume Go 1.24, we could use omitzero for Blob and avoid this method.
92- if r .URI == "" {
93- return nil , errors .New ("ResourceContents missing URI" )
94- }
95- if r .Blob == nil {
96- // Text. Marshal normally.
97- type wireResourceContents ResourceContents // (lacks MarshalJSON method)
98- return json .Marshal ((wireResourceContents )(r ))
99- }
100- // Blob.
101- if r .Text != "" {
102- return nil , errors .New ("ResourceContents has non-zero Text and Blob fields" )
103- }
104- // r.Blob may be the empty slice, so marshal with an alternative definition.
105- br := struct {
106- URI string `json:"uri,omitempty"`
107- MIMEType string `json:"mimeType,omitempty"`
108- Blob []byte `json:"blob"`
109- }{
110- URI : r .URI ,
111- MIMEType : r .MIMEType ,
112- Blob : r .Blob ,
113- }
114- return json .Marshal (br )
84+ // wireContent is the wire format for content.
85+ // It represents the protocol types TextContent, ImageContent, AudioContent
86+ // and EmbeddedResource.
87+ // The Type field distinguishes them. In the protocol, each type has a constant
88+ // value for the field.
89+ // At most one of Text, Data, and Resource is non-zero.
90+ type wireContent struct {
91+ Type string `json:"type"`
92+ Text string `json:"text,omitempty"`
93+ MIMEType string `json:"mimeType,omitempty"`
94+ Data []byte `json:"data,omitempty"`
95+ Resource * ResourceContents `json:"resource,omitempty"`
96+ Annotations * Annotations `json:"annotations,omitempty"`
11597}
11698
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.
99+ func contentsFromWire (wires []* wireContent , allow map [string ]bool ) ([]Content , error ) {
100+ var blocks []Content
101+ for _ , wire := range wires {
102+ block , err := contentFromWire (wire , allow )
103+ if err != nil {
104+ return nil , err
105+ }
106+ blocks = append (blocks , block )
124107 }
108+ return blocks , nil
125109}
126110
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 {}
111+ func contentFromWire (wire * wireContent , allow map [string ]bool ) (Content , error ) {
112+ if allow != nil && ! allow [wire .Type ] {
113+ return nil , fmt .Errorf ("invalid content type %q" , wire .Type )
132114 }
133- return & ResourceContents {
134- URI : uri ,
135- MIMEType : mimeType ,
136- Blob : blob ,
115+ switch wire .Type {
116+ case "text" :
117+ v := new (TextContent )
118+ v .fromWire (wire )
119+ return v , nil
120+ case "image" :
121+ v := new (ImageContent )
122+ v .fromWire (wire )
123+ return v , nil
124+ case "audio" :
125+ v := new (AudioContent )
126+ v .fromWire (wire )
127+ return v , nil
128+ case "resource" :
129+ v := new (EmbeddedResource )
130+ v .fromWire (wire )
131+ return v , nil
137132 }
133+ return nil , fmt .Errorf ("internal error: unrecognized content type %s" , wire .Type )
138134}
0 commit comments