Skip to content

Commit ef96b43

Browse files
committed
mcp: ensure required fields are not omitted in ImageContent and AudioContent
ImageContent and AudioContent now use custom MarshalJSON methods with inline structs to ensure data and mimeType fields are always included in JSON output, even when empty. This matches the approach used for TextContent.Text and follows TypeScript schema requirements. Updated tests to verify empty fields are serialized as empty strings rather than being omitted.
1 parent de4b788 commit ef96b43

File tree

2 files changed

+47
-7
lines changed

2 files changed

+47
-7
lines changed

mcp/content.go

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,25 @@ type ImageContent struct {
5858
}
5959

6060
func (c *ImageContent) MarshalJSON() ([]byte, error) {
61-
return json.Marshal(&wireContent{
61+
// Custom wire format to ensure required fields are always included, even when empty.
62+
data := c.Data
63+
if data == nil {
64+
data = []byte{}
65+
}
66+
wire := struct {
67+
Type string `json:"type"`
68+
MIMEType string `json:"mimeType"`
69+
Data []byte `json:"data"`
70+
Meta Meta `json:"_meta,omitempty"`
71+
Annotations *Annotations `json:"annotations,omitempty"`
72+
}{
6273
Type: "image",
6374
MIMEType: c.MIMEType,
64-
Data: c.Data,
75+
Data: data,
6576
Meta: c.Meta,
6677
Annotations: c.Annotations,
67-
})
78+
}
79+
return json.Marshal(wire)
6880
}
6981

7082
func (c *ImageContent) fromWire(wire *wireContent) {
@@ -83,13 +95,25 @@ type AudioContent struct {
8395
}
8496

8597
func (c AudioContent) MarshalJSON() ([]byte, error) {
86-
return json.Marshal(&wireContent{
98+
// Custom wire format to ensure required fields are always included, even when empty.
99+
data := c.Data
100+
if data == nil {
101+
data = []byte{}
102+
}
103+
wire := struct {
104+
Type string `json:"type"`
105+
MIMEType string `json:"mimeType"`
106+
Data []byte `json:"data"`
107+
Meta Meta `json:"_meta,omitempty"`
108+
Annotations *Annotations `json:"annotations,omitempty"`
109+
}{
87110
Type: "audio",
88111
MIMEType: c.MIMEType,
89-
Data: c.Data,
112+
Data: data,
90113
Meta: c.Meta,
91114
Annotations: c.Annotations,
92-
})
115+
}
116+
return json.Marshal(wire)
93117
}
94118

95119
func (c *AudioContent) fromWire(wire *wireContent) {
@@ -169,7 +193,7 @@ type ResourceContents struct {
169193
}
170194

171195
func (r ResourceContents) MarshalJSON() ([]byte, error) {
172-
// If we could assume Go 1.24, we could use omitzero for Blob and avoid this method.
196+
// URI is a required field, so we return an error when it is missing.
173197
if r.URI == "" {
174198
return nil, errors.New("ResourceContents missing URI")
175199
}

mcp/content_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ func TestContent(t *testing.T) {
4545
},
4646
`{"type":"image","mimeType":"image/png","data":"YTFiMmMz"}`,
4747
},
48+
{
49+
&mcp.ImageContent{MIMEType: "image/png", Data: []byte{}},
50+
`{"type":"image","mimeType":"image/png","data":""}`,
51+
},
52+
{
53+
&mcp.ImageContent{Data: []byte("test")},
54+
`{"type":"image","mimeType":"","data":"dGVzdA=="}`,
55+
},
4856
{
4957
&mcp.ImageContent{
5058
Data: []byte("a1b2c3"),
@@ -61,6 +69,14 @@ func TestContent(t *testing.T) {
6169
},
6270
`{"type":"audio","mimeType":"audio/wav","data":"YTFiMmMz"}`,
6371
},
72+
{
73+
&mcp.AudioContent{MIMEType: "audio/wav", Data: []byte{}},
74+
`{"type":"audio","mimeType":"audio/wav","data":""}`,
75+
},
76+
{
77+
&mcp.AudioContent{Data: []byte("test")},
78+
`{"type":"audio","mimeType":"","data":"dGVzdA=="}`,
79+
},
6480
{
6581
&mcp.AudioContent{
6682
Data: []byte("a1b2c3"),

0 commit comments

Comments
 (0)