diff --git a/mcp/content.go b/mcp/content.go index 8ea7555..d77674b 100644 --- a/mcp/content.go +++ b/mcp/content.go @@ -15,6 +15,7 @@ import ( // // ToWire converts content to its jsonrpc2 wire format. type Content interface { + // TODO: unexport this, and move the tests that use it to this package. ToWire() protocol.Content } @@ -29,64 +30,69 @@ func (c TextContent) ToWire() protocol.Content { // ImageContent contains base64-encoded image data. type ImageContent struct { - Data string - MimeType string + Data []byte // base64-encoded + MIMEType string } func (c ImageContent) ToWire() protocol.Content { - return protocol.Content{Type: "image", MIMEType: c.MimeType, Data: c.Data} + return protocol.Content{Type: "image", MIMEType: c.MIMEType, Data: c.Data} } // AudioContent contains base64-encoded audio data. type AudioContent struct { - Data string - MimeType string + Data []byte + MIMEType string } func (c AudioContent) ToWire() protocol.Content { - return protocol.Content{Type: "audio", MIMEType: c.MimeType, Data: c.Data} + return protocol.Content{Type: "audio", MIMEType: c.MIMEType, Data: c.Data} } // ResourceContent contains embedded resources. type ResourceContent struct { - Resource Resource + Resource EmbeddedResource } func (r ResourceContent) ToWire() protocol.Content { - res := r.Resource.ToWire() + res := r.Resource.toWire() return protocol.Content{Type: "resource", Resource: &res} } -type Resource interface { - ToWire() protocol.Resource +type EmbeddedResource interface { + toWire() protocol.ResourceContents } -type TextResource struct { +// The {Text,Blob}ResourceContents types match the protocol definitions, +// but we represent both as a single type on the wire. + +// A TextResourceContents is the contents of a text resource. +type TextResourceContents struct { URI string - MimeType string + MIMEType string Text string } -func (r TextResource) ToWire() protocol.Resource { - return protocol.Resource{ +func (r TextResourceContents) toWire() protocol.ResourceContents { + return protocol.ResourceContents{ URI: r.URI, - MIMEType: r.MimeType, + MIMEType: r.MIMEType, Text: r.Text, + // Blob is nil, indicating this is a TextResourceContents. } } -type BlobResource struct { +// A BlobResourceContents is the contents of a blob resource. +type BlobResourceContents struct { URI string - MimeType string - Blob string + MIMEType string + Blob []byte } -func (r BlobResource) ToWire() protocol.Resource { - blob := r.Blob - return protocol.Resource{ +func (r BlobResourceContents) toWire() protocol.ResourceContents { + return protocol.ResourceContents{ URI: r.URI, - MIMEType: r.MimeType, - Blob: &blob, + MIMEType: r.MIMEType, + Blob: r.Blob, } } @@ -97,22 +103,22 @@ func ContentFromWireContent(c protocol.Content) Content { case "text": return TextContent{Text: c.Text} case "image": - return ImageContent{Data: c.Data, MimeType: c.MIMEType} + return ImageContent{Data: c.Data, MIMEType: c.MIMEType} case "audio": - return AudioContent{Data: c.Data, MimeType: c.MIMEType} + return AudioContent{Data: c.Data, MIMEType: c.MIMEType} case "resource": r := ResourceContent{} if c.Resource != nil { if c.Resource.Blob != nil { - r.Resource = BlobResource{ + r.Resource = BlobResourceContents{ URI: c.Resource.URI, - MimeType: c.Resource.MIMEType, - Blob: *c.Resource.Blob, + MIMEType: c.Resource.MIMEType, + Blob: c.Resource.Blob, } } else { - r.Resource = TextResource{ + r.Resource = TextResourceContents{ URI: c.Resource.URI, - MimeType: c.Resource.MIMEType, + MIMEType: c.Resource.MIMEType, Text: c.Resource.Text, } } diff --git a/mcp/content_test.go b/mcp/content_test.go index 0ba41a0..4abf35e 100644 --- a/mcp/content_test.go +++ b/mcp/content_test.go @@ -19,24 +19,24 @@ func TestContent(t *testing.T) { }{ {mcp.TextContent{Text: "hello"}, protocol.Content{Type: "text", Text: "hello"}}, { - mcp.ImageContent{Data: "a1b2c3", MimeType: "image/png"}, - protocol.Content{Type: "image", Data: "a1b2c3", MIMEType: "image/png"}, + mcp.ImageContent{Data: []byte("a1b2c3"), MIMEType: "image/png"}, + protocol.Content{Type: "image", Data: []byte("a1b2c3"), MIMEType: "image/png"}, }, { - mcp.AudioContent{Data: "a1b2c3", MimeType: "audio/wav"}, - protocol.Content{Type: "audio", Data: "a1b2c3", MIMEType: "audio/wav"}, + mcp.AudioContent{Data: []byte("a1b2c3"), MIMEType: "audio/wav"}, + protocol.Content{Type: "audio", Data: []byte("a1b2c3"), MIMEType: "audio/wav"}, }, { mcp.ResourceContent{ - Resource: mcp.TextResource{ + Resource: mcp.TextResourceContents{ URI: "file://foo", - MimeType: "text", + MIMEType: "text", Text: "abc", }, }, protocol.Content{ Type: "resource", - Resource: &protocol.Resource{ + Resource: &protocol.ResourceContents{ URI: "file://foo", MIMEType: "text", Text: "abc", @@ -45,18 +45,18 @@ func TestContent(t *testing.T) { }, { mcp.ResourceContent{ - Resource: mcp.BlobResource{ + Resource: mcp.BlobResourceContents{ URI: "file://foo", - MimeType: "text", - Blob: "a1b2c3", + MIMEType: "text", + Blob: []byte("a1b2c3"), }, }, protocol.Content{ Type: "resource", - Resource: &protocol.Resource{ + Resource: &protocol.ResourceContents{ URI: "file://foo", MIMEType: "text", - Blob: ptr("a1b2c3"), + Blob: []byte("a1b2c3"), }, }, }, @@ -69,7 +69,3 @@ func TestContent(t *testing.T) { } } } - -func ptr[T any](t T) *T { - return &t -} diff --git a/mcp/protocol/content.go b/mcp/protocol/content.go index 3b93f43..1202704 100644 --- a/mcp/protocol/content.go +++ b/mcp/protocol/content.go @@ -9,27 +9,35 @@ import ( "fmt" ) +// The []byte fields below are marked omitzero, not omitempty: +// we want to marshal an empty byte slice. + // Content is the wire format for content. -// -// The Type field distinguishes the type of the content. -// At most one of Text, MIMEType, Data, and Resource is non-zero. +// It represents the protocol types TextContent, ImageContent, AudioContent +// and EmbeddedResource. +// The Type field distinguishes them. In the protocol, each type has a constant +// value for the field. +// At most one of Text, Data, and Resource is non-zero. type Content struct { - Type string `json:"type"` - Text string `json:"text,omitempty"` - MIMEType string `json:"mimeType,omitempty"` - Data string `json:"data,omitempty"` - Resource *Resource `json:"resource,omitempty"` + Type string `json:"type"` + Text string `json:"text,omitempty"` + MIMEType string `json:"mimeType,omitempty"` + Data []byte `json:"data,omitzero"` + Resource *ResourceContents `json:"resource,omitempty"` + Annotations *Annotations `json:"annotations,omitempty"` } -// Resource is the wire format for embedded resources. +// A ResourceContents is either a TextResourceContents or a BlobResourceContents. +// See https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-03-26/schema.ts#L524-L551 +// for the inheritance structure. +// If Blob is nil, this is a TextResourceContents; otherwise it's a BlobResourceContents. // -// The URI field describes the resource location. At most one of Text and Blob -// is non-zero. -type Resource struct { - URI string `json:"uri,"` - MIMEType string `json:"mimeType,omitempty"` - Text string `json:"text"` - Blob *string `json:"blob"` // blob is a pointer to distinguish empty from missing data +// The URI field describes the resource location. +type ResourceContents struct { + URI string `json:"uri,"` + MIMEType string `json:"mimeType,omitempty"` + Text string `json:"text"` + Blob []byte `json:"blob,omitzero"` } func (c *Content) UnmarshalJSON(data []byte) error {