Skip to content

Commit 9091440

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 9091440

File tree

2 files changed

+47
-11
lines changed

2 files changed

+47
-11
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 & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,6 @@ func TestContent(t *testing.T) {
2626
&mcp.TextContent{Text: ""},
2727
`{"type":"text","text":""}`,
2828
},
29-
{
30-
&mcp.TextContent{},
31-
`{"type":"text","text":""}`,
32-
},
3329
{
3430
&mcp.TextContent{
3531
Text: "hello",
@@ -45,6 +41,14 @@ func TestContent(t *testing.T) {
4541
},
4642
`{"type":"image","mimeType":"image/png","data":"YTFiMmMz"}`,
4743
},
44+
{
45+
&mcp.ImageContent{MIMEType: "image/png", Data: []byte{}},
46+
`{"type":"image","mimeType":"image/png","data":""}`,
47+
},
48+
{
49+
&mcp.ImageContent{Data: []byte("test")},
50+
`{"type":"image","mimeType":"","data":"dGVzdA=="}`,
51+
},
4852
{
4953
&mcp.ImageContent{
5054
Data: []byte("a1b2c3"),
@@ -61,6 +65,14 @@ func TestContent(t *testing.T) {
6165
},
6266
`{"type":"audio","mimeType":"audio/wav","data":"YTFiMmMz"}`,
6367
},
68+
{
69+
&mcp.AudioContent{MIMEType: "audio/wav", Data: []byte{}},
70+
`{"type":"audio","mimeType":"audio/wav","data":""}`,
71+
},
72+
{
73+
&mcp.AudioContent{Data: []byte("test")},
74+
`{"type":"audio","mimeType":"","data":"dGVzdA=="}`,
75+
},
6476
{
6577
&mcp.AudioContent{
6678
Data: []byte("a1b2c3"),

0 commit comments

Comments
 (0)