diff --git a/mcp/content.go b/mcp/content.go index 7af90fe7..adb2048e 100644 --- a/mcp/content.go +++ b/mcp/content.go @@ -307,6 +307,39 @@ type ResourceContents struct { Meta Meta `json:"_meta,omitempty"` } +func (r *ResourceContents) MarshalJSON() ([]byte, error) { + // Custom wire format to ensure the required "text" or "blob" field is + // always included, even when empty, so a text resource with empty + // content still matches the TextResourceContents branch of the spec's + // anyOf union instead of matching neither branch. + if r.Blob != nil { + wire := struct { + URI string `json:"uri"` + MIMEType string `json:"mimeType,omitempty"` + Blob []byte `json:"blob"` + Meta Meta `json:"_meta,omitempty"` + }{ + URI: r.URI, + MIMEType: r.MIMEType, + Blob: r.Blob, + Meta: r.Meta, + } + return json.Marshal(wire) + } + wire := struct { + URI string `json:"uri"` + MIMEType string `json:"mimeType,omitempty"` + Text string `json:"text"` + Meta Meta `json:"_meta,omitempty"` + }{ + URI: r.URI, + MIMEType: r.MIMEType, + Text: r.Text, + Meta: r.Meta, + } + return json.Marshal(wire) +} + // wireContent is the wire format for content. // It represents the protocol types TextContent, ImageContent, AudioContent, // ResourceLink, EmbeddedResource, ToolUseContent, and ToolResultContent. diff --git a/mcp/content_test.go b/mcp/content_test.go index 094950eb..39af5e20 100644 --- a/mcp/content_test.go +++ b/mcp/content_test.go @@ -160,8 +160,10 @@ func TestEmbeddedResource(t *testing.T) { `{"uri":"u","mimeType":"m","text":"t","_meta":{"key":"value"}}`, }, { + // Text is required by TextResourceContents in the spec, so it must + // always be present, even when empty. &mcp.ResourceContents{URI: "u"}, - `{"uri":"u"}`, + `{"uri":"u","text":""}`, }, { &mcp.ResourceContents{URI: "u", Blob: []byte{}},