Skip to content

Commit 6dfeba5

Browse files
committed
internal/mcp: adjust content types
- Change the names of some content-related types to better match the spec. Most notably, rename Resource to EmbeddedResource, because the former has a different meaning in the spec. - Use []byte intead of string where the spec says "base64-encoded data." - Unexport EmbeddedResource.ToWire. Change-Id: I9f65efaadadda1b3abc0e58c64a743af1a72852c Reviewed-on: https://go-review.googlesource.com/c/tools/+/671362 Reviewed-by: Robert Findley <[email protected]> TryBot-Bypass: Jonathan Amsterdam <[email protected]> Commit-Queue: Jonathan Amsterdam <[email protected]>
1 parent 3818858 commit 6dfeba5

File tree

3 files changed

+72
-62
lines changed

3 files changed

+72
-62
lines changed

internal/mcp/content.go

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
//
1616
// ToWire converts content to its jsonrpc2 wire format.
1717
type Content interface {
18+
// TODO: unexport this, and move the tests that use it to this package.
1819
ToWire() protocol.Content
1920
}
2021

@@ -29,64 +30,69 @@ func (c TextContent) ToWire() protocol.Content {
2930

3031
// ImageContent contains base64-encoded image data.
3132
type ImageContent struct {
32-
Data string
33-
MimeType string
33+
Data []byte // base64-encoded
34+
MIMEType string
3435
}
3536

3637
func (c ImageContent) ToWire() protocol.Content {
37-
return protocol.Content{Type: "image", MIMEType: c.MimeType, Data: c.Data}
38+
return protocol.Content{Type: "image", MIMEType: c.MIMEType, Data: c.Data}
3839
}
3940

4041
// AudioContent contains base64-encoded audio data.
4142
type AudioContent struct {
42-
Data string
43-
MimeType string
43+
Data []byte
44+
MIMEType string
4445
}
4546

4647
func (c AudioContent) ToWire() protocol.Content {
47-
return protocol.Content{Type: "audio", MIMEType: c.MimeType, Data: c.Data}
48+
return protocol.Content{Type: "audio", MIMEType: c.MIMEType, Data: c.Data}
4849
}
4950

5051
// ResourceContent contains embedded resources.
5152
type ResourceContent struct {
52-
Resource Resource
53+
Resource EmbeddedResource
5354
}
5455

5556
func (r ResourceContent) ToWire() protocol.Content {
56-
res := r.Resource.ToWire()
57+
res := r.Resource.toWire()
5758
return protocol.Content{Type: "resource", Resource: &res}
5859
}
5960

60-
type Resource interface {
61-
ToWire() protocol.Resource
61+
type EmbeddedResource interface {
62+
toWire() protocol.ResourceContents
6263
}
6364

64-
type TextResource struct {
65+
// The {Text,Blob}ResourceContents types match the protocol definitions,
66+
// but we represent both as a single type on the wire.
67+
68+
// A TextResourceContents is the contents of a text resource.
69+
type TextResourceContents struct {
6570
URI string
66-
MimeType string
71+
MIMEType string
6772
Text string
6873
}
6974

70-
func (r TextResource) ToWire() protocol.Resource {
71-
return protocol.Resource{
75+
func (r TextResourceContents) toWire() protocol.ResourceContents {
76+
return protocol.ResourceContents{
7277
URI: r.URI,
73-
MIMEType: r.MimeType,
78+
MIMEType: r.MIMEType,
7479
Text: r.Text,
80+
// Blob is nil, indicating this is a TextResourceContents.
7581
}
7682
}
7783

78-
type BlobResource struct {
84+
// A BlobResourceContents is the contents of a blob resource.
85+
type BlobResourceContents struct {
7986
URI string
80-
MimeType string
81-
Blob string
87+
MIMEType string
88+
Blob []byte
8289
}
8390

84-
func (r BlobResource) ToWire() protocol.Resource {
85-
blob := r.Blob
86-
return protocol.Resource{
91+
func (r BlobResourceContents) toWire() protocol.ResourceContents {
92+
return protocol.ResourceContents{
8793
URI: r.URI,
88-
MIMEType: r.MimeType,
89-
Blob: &blob,
94+
MIMEType: r.MIMEType,
95+
Blob: r.Blob,
9096
}
9197
}
9298

@@ -97,22 +103,22 @@ func ContentFromWireContent(c protocol.Content) Content {
97103
case "text":
98104
return TextContent{Text: c.Text}
99105
case "image":
100-
return ImageContent{Data: c.Data, MimeType: c.MIMEType}
106+
return ImageContent{Data: c.Data, MIMEType: c.MIMEType}
101107
case "audio":
102-
return AudioContent{Data: c.Data, MimeType: c.MIMEType}
108+
return AudioContent{Data: c.Data, MIMEType: c.MIMEType}
103109
case "resource":
104110
r := ResourceContent{}
105111
if c.Resource != nil {
106112
if c.Resource.Blob != nil {
107-
r.Resource = BlobResource{
113+
r.Resource = BlobResourceContents{
108114
URI: c.Resource.URI,
109-
MimeType: c.Resource.MIMEType,
110-
Blob: *c.Resource.Blob,
115+
MIMEType: c.Resource.MIMEType,
116+
Blob: c.Resource.Blob,
111117
}
112118
} else {
113-
r.Resource = TextResource{
119+
r.Resource = TextResourceContents{
114120
URI: c.Resource.URI,
115-
MimeType: c.Resource.MIMEType,
121+
MIMEType: c.Resource.MIMEType,
116122
Text: c.Resource.Text,
117123
}
118124
}

internal/mcp/content_test.go

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,24 @@ func TestContent(t *testing.T) {
1919
}{
2020
{mcp.TextContent{Text: "hello"}, protocol.Content{Type: "text", Text: "hello"}},
2121
{
22-
mcp.ImageContent{Data: "a1b2c3", MimeType: "image/png"},
23-
protocol.Content{Type: "image", Data: "a1b2c3", MIMEType: "image/png"},
22+
mcp.ImageContent{Data: []byte("a1b2c3"), MIMEType: "image/png"},
23+
protocol.Content{Type: "image", Data: []byte("a1b2c3"), MIMEType: "image/png"},
2424
},
2525
{
26-
mcp.AudioContent{Data: "a1b2c3", MimeType: "audio/wav"},
27-
protocol.Content{Type: "audio", Data: "a1b2c3", MIMEType: "audio/wav"},
26+
mcp.AudioContent{Data: []byte("a1b2c3"), MIMEType: "audio/wav"},
27+
protocol.Content{Type: "audio", Data: []byte("a1b2c3"), MIMEType: "audio/wav"},
2828
},
2929
{
3030
mcp.ResourceContent{
31-
Resource: mcp.TextResource{
31+
Resource: mcp.TextResourceContents{
3232
URI: "file://foo",
33-
MimeType: "text",
33+
MIMEType: "text",
3434
Text: "abc",
3535
},
3636
},
3737
protocol.Content{
3838
Type: "resource",
39-
Resource: &protocol.Resource{
39+
Resource: &protocol.ResourceContents{
4040
URI: "file://foo",
4141
MIMEType: "text",
4242
Text: "abc",
@@ -45,18 +45,18 @@ func TestContent(t *testing.T) {
4545
},
4646
{
4747
mcp.ResourceContent{
48-
Resource: mcp.BlobResource{
48+
Resource: mcp.BlobResourceContents{
4949
URI: "file://foo",
50-
MimeType: "text",
51-
Blob: "a1b2c3",
50+
MIMEType: "text",
51+
Blob: []byte("a1b2c3"),
5252
},
5353
},
5454
protocol.Content{
5555
Type: "resource",
56-
Resource: &protocol.Resource{
56+
Resource: &protocol.ResourceContents{
5757
URI: "file://foo",
5858
MIMEType: "text",
59-
Blob: ptr("a1b2c3"),
59+
Blob: []byte("a1b2c3"),
6060
},
6161
},
6262
},
@@ -69,7 +69,3 @@ func TestContent(t *testing.T) {
6969
}
7070
}
7171
}
72-
73-
func ptr[T any](t T) *T {
74-
return &t
75-
}

internal/mcp/protocol/content.go

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,35 @@ import (
99
"fmt"
1010
)
1111

12+
// The []byte fields below are marked omitzero, not omitempty:
13+
// we want to marshal an empty byte slice.
14+
1215
// Content is the wire format for content.
13-
//
14-
// The Type field distinguishes the type of the content.
15-
// At most one of Text, MIMEType, Data, and Resource is non-zero.
16+
// It represents the protocol types TextContent, ImageContent, AudioContent
17+
// and EmbeddedResource.
18+
// The Type field distinguishes them. In the protocol, each type has a constant
19+
// value for the field.
20+
// At most one of Text, Data, and Resource is non-zero.
1621
type Content struct {
17-
Type string `json:"type"`
18-
Text string `json:"text,omitempty"`
19-
MIMEType string `json:"mimeType,omitempty"`
20-
Data string `json:"data,omitempty"`
21-
Resource *Resource `json:"resource,omitempty"`
22+
Type string `json:"type"`
23+
Text string `json:"text,omitempty"`
24+
MIMEType string `json:"mimeType,omitempty"`
25+
Data []byte `json:"data,omitzero"`
26+
Resource *ResourceContents `json:"resource,omitempty"`
27+
Annotations *Annotations `json:"annotations,omitempty"`
2228
}
2329

24-
// Resource is the wire format for embedded resources.
30+
// A ResourceContents is either a TextResourceContents or a BlobResourceContents.
31+
// See https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-03-26/schema.ts#L524-L551
32+
// for the inheritance structure.
33+
// If Blob is nil, this is a TextResourceContents; otherwise it's a BlobResourceContents.
2534
//
26-
// The URI field describes the resource location. At most one of Text and Blob
27-
// is non-zero.
28-
type Resource struct {
29-
URI string `json:"uri,"`
30-
MIMEType string `json:"mimeType,omitempty"`
31-
Text string `json:"text"`
32-
Blob *string `json:"blob"` // blob is a pointer to distinguish empty from missing data
35+
// The URI field describes the resource location.
36+
type ResourceContents struct {
37+
URI string `json:"uri,"`
38+
MIMEType string `json:"mimeType,omitempty"`
39+
Text string `json:"text"`
40+
Blob []byte `json:"blob,omitzero"`
3341
}
3442

3543
func (c *Content) UnmarshalJSON(data []byte) error {

0 commit comments

Comments
 (0)