go-sdk/mcp/content.go
Rob Findley 4f6510b9ae internal/mcp: merge the protocol package into mcp
In accordance with the updated design, merge the protocol package into
the mcp package. To do so, rename protocol.Content to WireContent and
protocol.ResourceContents to WireResource.

Also, remove some stray logging in readResource.

Change-Id: I76f61ce8d67dd3ac520ef2e1959ade0a0785a480
Reviewed-on: https://go-review.googlesource.com/c/tools/+/672375
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
2025-05-13 14:43:46 -07:00

175 lines
4.7 KiB
Go

// Copyright 2025 The Go MCP SDK Authors. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package mcp
import (
"encoding/json"
"fmt"
)
// The []byte fields below are marked omitzero, not omitempty:
// we want to marshal an empty byte slice.
// WireContent is the wire format for content.
// 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 WireContent struct {
Type string `json:"type"`
Text string `json:"text,omitempty"`
MIMEType string `json:"mimeType,omitempty"`
Data []byte `json:"data,omitzero"`
Resource *WireResource `json:"resource,omitempty"`
Annotations *Annotations `json:"annotations,omitempty"`
}
// A WireResource 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.
type WireResource struct {
URI string `json:"uri,"`
MIMEType string `json:"mimeType,omitempty"`
Text string `json:"text"`
Blob []byte `json:"blob,omitzero"`
}
func (c *WireContent) UnmarshalJSON(data []byte) error {
type wireContent WireContent // for naive unmarshaling
var c2 wireContent
if err := json.Unmarshal(data, &c2); err != nil {
return err
}
switch c2.Type {
case "text", "image", "audio", "resource":
default:
return fmt.Errorf("unrecognized content type %s", c.Type)
}
*c = WireContent(c2)
return nil
}
// Content is the union of supported content types: [TextContent],
// [ImageContent], [AudioContent], and [ResourceContent].
//
// 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() WireContent
}
// TextContent is a textual content.
type TextContent struct {
Text string
}
func (c TextContent) ToWire() WireContent {
return WireContent{Type: "text", Text: c.Text}
}
// ImageContent contains base64-encoded image data.
type ImageContent struct {
Data []byte // base64-encoded
MIMEType string
}
func (c ImageContent) ToWire() WireContent {
return WireContent{Type: "image", MIMEType: c.MIMEType, Data: c.Data}
}
// AudioContent contains base64-encoded audio data.
type AudioContent struct {
Data []byte
MIMEType string
}
func (c AudioContent) ToWire() WireContent {
return WireContent{Type: "audio", MIMEType: c.MIMEType, Data: c.Data}
}
// ResourceContent contains embedded resources.
type ResourceContent struct {
Resource EmbeddedResource
}
func (r ResourceContent) ToWire() WireContent {
res := r.Resource.toWire()
return WireContent{Type: "resource", Resource: &res}
}
type EmbeddedResource interface {
toWire() WireResource
}
// 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
Text string
}
func (r TextResourceContents) toWire() WireResource {
return WireResource{
URI: r.URI,
MIMEType: r.MIMEType,
Text: r.Text,
// Blob is nil, indicating this is a TextResourceContents.
}
}
// A BlobResourceContents is the contents of a blob resource.
type BlobResourceContents struct {
URI string
MIMEType string
Blob []byte
}
func (r BlobResourceContents) toWire() WireResource {
return WireResource{
URI: r.URI,
MIMEType: r.MIMEType,
Blob: r.Blob,
}
}
// ContentFromWireContent converts content from the jsonrpc2 wire format to a
// typed Content value.
func ContentFromWireContent(c WireContent) Content {
switch c.Type {
case "text":
return TextContent{Text: c.Text}
case "image":
return ImageContent{Data: c.Data, MIMEType: c.MIMEType}
case "audio":
return AudioContent{Data: c.Data, MIMEType: c.MIMEType}
case "resource":
r := ResourceContent{}
if c.Resource != nil {
if c.Resource.Blob != nil {
r.Resource = BlobResourceContents{
URI: c.Resource.URI,
MIMEType: c.Resource.MIMEType,
Blob: c.Resource.Blob,
}
} else {
r.Resource = TextResourceContents{
URI: c.Resource.URI,
MIMEType: c.Resource.MIMEType,
Text: c.Resource.Text,
}
}
}
return r
default:
panic(fmt.Sprintf("unrecognized wire content type %q", c.Type))
}
}