mcp,design: revert 'content' back to an interface type (#29)

After some experience with the flattened version of Content, we see that
it can easily lead to incorrect usage and is a harder API to read.
Therefore, this CL changes back to a design similar to what we had prior
to https://go.dev/cl/672415, though opting to promote content
unmarshalling to the protocol types that use it, which leads to an
overall simpler api.
This commit is contained in:
Robert Findley 2025-06-25 13:09:05 -04:00 • committed by GitHub
commit 204b59483b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 333 additions and 159 deletions

View file

@ -70,7 +70,9 @@ func TestCmdTransport(t *testing.T) {
log.Fatal(err)
}
want := &mcp.CallToolResult{
Content: []*mcp.ContentBlock{{Type: "text", Text: "Hi user"}},
Content: []mcp.Content{
&mcp.TextContent{Text: "Hi user"},
},
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("greet returned unexpected content (-want +got):\n%s", diff)

View file

@ -10,81 +10,117 @@ import (
"fmt"
)
// A ContentBlock is one of a TextContent, ImageContent, AudioContent
// ResourceLink, or EmbeddedResource.
// Use [NewTextContent], [NewImageContent], [NewAudioContent], [NewResourceLink]
// or [NewResourceContents] to create one.
// A Content is a [TextContent], [ImageContent], [AudioContent] or
// [EmbeddedResource].
//
// The Type field must be one of "text", "image", "audio", "resource_link" or "resource".
// The constructors above populate this field appropriately.
// Although at most one of Text, Data, ResourceLink and Resource should be non-zero,
// consumers of ContentBlock use the Type field to determine which value to use;
// values in the other fields are ignored.
// TODO(jba,rfindley): rethink this type. Each kind (text, image, etc.) should have its own
// meta and annotations, otherwise they're duplicated for Resource and ResourceContents.
type ContentBlock struct {
Meta map[string]any `json:"_meta,omitempty"`
Type string `json:"type"`
Text string `json:"text,omitempty"`
MIMEType string `json:"mimeType,omitempty"`
Data []byte `json:"data,omitempty"`
ResourceLink *Resource `json:"resource_link,omitempty"`
Resource *ResourceContents `json:"resource,omitempty"`
Annotations *Annotations `json:"annotations,omitempty"`
// TODO(rfindley): add ResourceLink.
type Content interface {
MarshalJSON() ([]byte, error)
fromWire(*wireContent)
}
func (c *ContentBlock) UnmarshalJSON(data []byte) error {
type wireContent ContentBlock // for naive unmarshaling
var c2 wireContent
if err := json.Unmarshal(data, &c2); err != nil {
return err
}
switch c2.Type {
case "text", "image", "audio", "resource", "resource_link":
default:
return fmt.Errorf("unrecognized content type %s", c.Type)
}
*c = ContentBlock(c2)
return nil
// TextContent is a textual content.
type TextContent struct {
Text string
Meta Meta
Annotations *Annotations
}
// NewTextContent creates a [ContentBlock] with text.
func NewTextContent(text string) *ContentBlock {
return &ContentBlock{Type: "text", Text: text}
func (c *TextContent) MarshalJSON() ([]byte, error) {
return json.Marshal(&wireContent{
Type: "text",
Text: c.Text,
Meta: c.Meta,
Annotations: c.Annotations,
})
}
// NewImageContent creates a [ContentBlock] with image data.
func NewImageContent(data []byte, mimeType string) *ContentBlock {
return &ContentBlock{Type: "image", Data: data, MIMEType: mimeType}
func (c *TextContent) fromWire(wire *wireContent) {
c.Text = wire.Text
c.Meta = wire.Meta
c.Annotations = wire.Annotations
}
// NewAudioContent creates a [ContentBlock] with audio data.
func NewAudioContent(data []byte, mimeType string) *ContentBlock {
return &ContentBlock{Type: "audio", Data: data, MIMEType: mimeType}
// ImageContent contains base64-encoded image data.
type ImageContent struct {
Meta Meta
Annotations *Annotations
Data []byte // base64-encoded
MIMEType string
}
// NewResourceLink creates a [ContentBlock] with a [Resource].
func NewResourceLink(r *Resource) *ContentBlock {
return &ContentBlock{Type: "resource_link", ResourceLink: r}
func (c *ImageContent) MarshalJSON() ([]byte, error) {
return json.Marshal(&wireContent{
Type: "image",
MIMEType: c.MIMEType,
Data: c.Data,
Meta: c.Meta,
Annotations: c.Annotations,
})
}
// NewResourceContents creates a [ContentBlock] with an embedded resource (a [ResourceContents]).
func NewResourceContents(rc *ResourceContents) *ContentBlock {
return &ContentBlock{Type: "resource", Resource: rc}
func (c *ImageContent) fromWire(wire *wireContent) {
c.MIMEType = wire.MIMEType
c.Data = wire.Data
c.Meta = wire.Meta
c.Annotations = wire.Annotations
}
// ResourceContents represents the union of the spec's {Text,Blob}ResourceContents types.
// See https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-03-26/schema.ts#L524-L551
// for the inheritance structure.
// AudioContent contains base64-encoded audio data.
type AudioContent struct {
Data []byte
MIMEType string
Meta Meta
Annotations *Annotations
}
// A ResourceContents is either a TextResourceContents or a BlobResourceContents.
// Use [NewTextResourceContents] or [NextBlobResourceContents] to create one.
func (c AudioContent) MarshalJSON() ([]byte, error) {
return json.Marshal(&wireContent{
Type: "audio",
MIMEType: c.MIMEType,
Data: c.Data,
Meta: c.Meta,
Annotations: c.Annotations,
})
}
func (c *AudioContent) fromWire(wire *wireContent) {
c.MIMEType = wire.MIMEType
c.Data = wire.Data
c.Meta = wire.Meta
c.Annotations = wire.Annotations
}
// EmbeddedResource contains embedded resources.
type EmbeddedResource struct {
Resource *ResourceContents
Meta Meta
Annotations *Annotations
}
func (c *EmbeddedResource) MarshalJSON() ([]byte, error) {
return json.Marshal(&wireContent{
Type: "resource",
Resource: c.Resource,
Meta: c.Meta,
Annotations: c.Annotations,
})
}
func (c *EmbeddedResource) fromWire(wire *wireContent) {
c.Resource = wire.Resource
c.Meta = wire.Meta
c.Annotations = wire.Annotations
}
// ResourceContents contains the contents of a specific resource or
// sub-resource.
type ResourceContents struct {
Meta map[string]any `json:"_meta,omitempty"`
URI string `json:"uri"` // resource location; must not be empty
MIMEType string `json:"mimeType,omitempty"`
Text string `json:"text"`
Blob []byte `json:"blob,omitempty"` // if nil, then text; else blob
URI string `json:"uri"`
MIMEType string `json:"mimeType,omitempty"`
Text string `json:"text,omitempty"`
Blob []byte `json:"blob,omitempty"`
Meta Meta `json:"_meta,omitempty"`
}
func (r ResourceContents) MarshalJSON() ([]byte, error) {
@ -114,25 +150,55 @@ func (r ResourceContents) MarshalJSON() ([]byte, error) {
return json.Marshal(br)
}
// NewTextResourceContents returns a [ResourceContents] containing text.
func NewTextResourceContents(uri, mimeType, text string) *ResourceContents {
return &ResourceContents{
URI: uri,
MIMEType: mimeType,
Text: text,
// Blob is nil, indicating this is a TextResourceContents.
}
// 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,omitempty"`
Resource *ResourceContents `json:"resource,omitempty"`
Meta Meta `json:"_meta,omitempty"`
Annotations *Annotations `json:"annotations,omitempty"`
}
// NewBlobResourceContents returns a [ResourceContents] containing a byte slice.
func NewBlobResourceContents(uri, mimeType string, blob []byte) *ResourceContents {
// The only way to distinguish text from blob is a non-nil Blob field.
if blob == nil {
blob = []byte{}
}
return &ResourceContents{
URI: uri,
MIMEType: mimeType,
Blob: blob,
func contentsFromWire(wires []*wireContent, allow map[string]bool) ([]Content, error) {
var blocks []Content
for _, wire := range wires {
block, err := contentFromWire(wire, allow)
if err != nil {
return nil, err
}
blocks = append(blocks, block)
}
return blocks, nil
}
func contentFromWire(wire *wireContent, allow map[string]bool) (Content, error) {
if allow != nil && !allow[wire.Type] {
return nil, fmt.Errorf("invalid content type %q", wire.Type)
}
switch wire.Type {
case "text":
v := new(TextContent)
v.fromWire(wire)
return v, nil
case "image":
v := new(ImageContent)
v.fromWire(wire)
return v, nil
case "audio":
v := new(AudioContent)
v.fromWire(wire)
return v, nil
case "resource":
v := new(EmbeddedResource)
v.fromWire(wire)
return v, nil
}
return nil, fmt.Errorf("internal error: unrecognized content type %s", wire.Type)
}

View file

@ -6,6 +6,8 @@ package mcp_test
import (
"encoding/json"
"fmt"
"log"
"testing"
"github.com/google/go-cmp/cmp"
@ -14,30 +16,73 @@ import (
func TestContent(t *testing.T) {
tests := []struct {
in *mcp.ContentBlock
in mcp.Content
want string // json serialization
}{
{mcp.NewTextContent("hello"), `{"type":"text","text":"hello"}`},
{
mcp.NewImageContent([]byte("a1b2c3"), "image/png"),
&mcp.TextContent{Text: "hello"},
`{"type":"text","text":"hello"}`,
},
{
&mcp.TextContent{
Text: "hello",
Meta: mcp.Meta{"key": "value"},
Annotations: &mcp.Annotations{Priority: 1.0},
},
`{"type":"text","text":"hello","_meta":{"key":"value"},"annotations":{"priority":1}}`,
},
{
&mcp.ImageContent{
Data: []byte("a1b2c3"),
MIMEType: "image/png",
},
`{"type":"image","mimeType":"image/png","data":"YTFiMmMz"}`,
},
{
mcp.NewAudioContent([]byte("a1b2c3"), "audio/wav"),
&mcp.ImageContent{
Data: []byte("a1b2c3"),
MIMEType: "image/png",
Meta: mcp.Meta{"key": "value"},
Annotations: &mcp.Annotations{Priority: 1.0},
},
`{"type":"image","mimeType":"image/png","data":"YTFiMmMz","_meta":{"key":"value"},"annotations":{"priority":1}}`,
},
{
&mcp.AudioContent{
Data: []byte("a1b2c3"),
MIMEType: "audio/wav",
},
`{"type":"audio","mimeType":"audio/wav","data":"YTFiMmMz"}`,
},
{
mcp.NewResourceContents(
mcp.NewTextResourceContents("file://foo", "text", "abc"),
),
&mcp.AudioContent{
Data: []byte("a1b2c3"),
MIMEType: "audio/wav",
Meta: mcp.Meta{"key": "value"},
Annotations: &mcp.Annotations{Priority: 1.0},
},
`{"type":"audio","mimeType":"audio/wav","data":"YTFiMmMz","_meta":{"key":"value"},"annotations":{"priority":1}}`,
},
{
&mcp.EmbeddedResource{
Resource: &mcp.ResourceContents{URI: "file://foo", MIMEType: "text", Text: "abc"},
},
`{"type":"resource","resource":{"uri":"file://foo","mimeType":"text","text":"abc"}}`,
},
{
mcp.NewResourceContents(
mcp.NewBlobResourceContents("file://foo", "image/png", []byte("a1b2c3")),
),
&mcp.EmbeddedResource{
Resource: &mcp.ResourceContents{URI: "file://foo", MIMEType: "image/png", Blob: []byte("a1b2c3")},
},
`{"type":"resource","resource":{"uri":"file://foo","mimeType":"image/png","blob":"YTFiMmMz"}}`,
},
{
&mcp.EmbeddedResource{
Resource: &mcp.ResourceContents{URI: "file://foo", MIMEType: "text", Text: "abc"},
Meta: mcp.Meta{"key": "value"},
Annotations: &mcp.Annotations{Priority: 1.0},
},
`{"type":"resource","resource":{"uri":"file://foo","mimeType":"text","text":"abc"},"_meta":{"key":"value"},"annotations":{"priority":1}}`,
},
}
for _, test := range tests {
@ -48,39 +93,41 @@ func TestContent(t *testing.T) {
if diff := cmp.Diff(test.want, string(got)); diff != "" {
t.Errorf("json.Marshal(%v) mismatch (-want +got):\n%s", test.in, diff)
}
var out *mcp.ContentBlock
if err := json.Unmarshal(got, &out); err != nil {
result := fmt.Sprintf(`{"content":[%s]}`, string(got))
log.Println(result)
var out mcp.CallToolResult
if err := json.Unmarshal([]byte(result), &out); err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(test.in, out); diff != "" {
if diff := cmp.Diff(test.in, out.Content[0]); diff != "" {
t.Errorf("json.Unmarshal(%q) mismatch (-want +got):\n%s", string(got), diff)
}
}
}
func TestResourceContents(t *testing.T) {
func TestEmbeddedResource(t *testing.T) {
for _, tt := range []struct {
rc mcp.ResourceContents
rc *mcp.ResourceContents
want string // marshaled JSON
}{
{
mcp.ResourceContents{URI: "u", Text: "t"},
&mcp.ResourceContents{URI: "u", Text: "t"},
`{"uri":"u","text":"t"}`,
},
{
mcp.ResourceContents{URI: "u", MIMEType: "m", Text: "t"},
`{"uri":"u","mimeType":"m","text":"t"}`,
&mcp.ResourceContents{URI: "u", MIMEType: "m", Text: "t", Meta: mcp.Meta{"key": "value"}},
`{"uri":"u","mimeType":"m","text":"t","_meta":{"key":"value"}}`,
},
{
mcp.ResourceContents{URI: "u", Text: "", Blob: nil},
`{"uri":"u","text":""}`,
&mcp.ResourceContents{URI: "u"},
`{"uri":"u"}`,
},
{
mcp.ResourceContents{URI: "u", Blob: []byte{}},
&mcp.ResourceContents{URI: "u", Blob: []byte{}},
`{"uri":"u","blob":""}`,
},
{
mcp.ResourceContents{URI: "u", Blob: []byte{1}},
&mcp.ResourceContents{URI: "u", Blob: []byte{1}},
`{"uri":"u","blob":"AQ=="}`,
},
} {
@ -91,10 +138,11 @@ func TestResourceContents(t *testing.T) {
if got := string(data); got != tt.want {
t.Errorf("%#v:\ngot %s\nwant %s", tt.rc, got, tt.want)
}
var urc mcp.ResourceContents
if err := json.Unmarshal(data, &urc); err != nil {
urc := new(mcp.ResourceContents)
if err := json.Unmarshal(data, urc); err != nil {
t.Fatal(err)
}
// Since Blob is omitempty, the empty slice changes to nil.
if diff := cmp.Diff(tt.rc, urc); diff != "" {
t.Errorf("mismatch (-want, +got):\n%s", diff)
}

View file

@ -20,8 +20,8 @@ type SayHiParams struct {
func SayHi(ctx context.Context, cc *ServerSession, params *CallToolParamsFor[SayHiParams]) (*CallToolResultFor[any], error) {
return &CallToolResultFor[any]{
Content: []*ContentBlock{
NewTextContent("Hi " + params.Name),
Content: []Content{
&TextContent{Text: "Hi " + params.Name},
},
}, nil
}

View file

@ -34,7 +34,7 @@ func sayHi(ctx context.Context, ss *ServerSession, params *CallToolParamsFor[hiP
if err := ss.Ping(ctx, nil); err != nil {
return nil, fmt.Errorf("ping failed: %v", err)
}
return &CallToolResultFor[any]{Content: []*ContentBlock{NewTextContent("hi " + params.Arguments.Name)}}, nil
return &CallToolResultFor[any]{Content: []Content{&TextContent{Text: "hi " + params.Arguments.Name}}}, nil
}
func TestEndToEnd(t *testing.T) {
@ -142,7 +142,7 @@ func TestEndToEnd(t *testing.T) {
wantReview := &GetPromptResult{
Description: "Code review prompt",
Messages: []*PromptMessage{{
Content: NewTextContent("Please review the following code: 1+1"),
Content: &TextContent{Text: "Please review the following code: 1+1"},
Role: "user",
}},
}
@ -195,7 +195,9 @@ func TestEndToEnd(t *testing.T) {
t.Fatal(err)
}
wantHi := &CallToolResult{
Content: []*ContentBlock{{Type: "text", Text: "hi user"}},
Content: []Content{
&TextContent{Text: "hi user"},
},
}
if diff := cmp.Diff(wantHi, gotHi); diff != "" {
t.Errorf("tools/call 'greet' mismatch (-want +got):\n%s", diff)
@ -212,7 +214,9 @@ func TestEndToEnd(t *testing.T) {
}
wantFail := &CallToolResult{
IsError: true,
Content: []*ContentBlock{{Type: "text", Text: errTestFailure.Error()}},
Content: []Content{
&TextContent{Text: errTestFailure.Error()},
},
}
if diff := cmp.Diff(wantFail, gotFail); diff != "" {
t.Errorf("tools/call 'fail' mismatch (-want +got):\n%s", diff)
@ -451,7 +455,7 @@ var (
return &GetPromptResult{
Description: "Code review prompt",
Messages: []*PromptMessage{
{Role: "user", Content: NewTextContent("Please review the following code: " + params.Arguments["Code"])},
{Role: "user", Content: &TextContent{Text: "Please review the following code: " + params.Arguments["Code"]}},
},
}, nil
},
@ -505,7 +509,9 @@ func handleEmbeddedResource(_ context.Context, _ *ServerSession, params *ReadRes
return nil, fmt.Errorf("no embedded resource named %q", key)
}
return &ReadResourceResult{
Contents: []*ResourceContents{NewTextResourceContents(params.URI, "text/plain", text)},
Contents: []*ResourceContents{
{URI: params.URI, MIMEType: "text/plain", Text: text},
},
}, nil
}

View file

@ -11,6 +11,8 @@ package mcp
// sdiff -l <(curl $prefix/2025-03-26/schema.ts) <(curl $prefix/2025/06-18/schema.ts)
import (
"encoding/json"
"github.com/modelcontextprotocol/go-sdk/jsonschema"
)
@ -63,7 +65,7 @@ type CallToolResult struct {
Meta `json:"_meta,omitempty"`
// A list of content objects that represent the unstructured result of the tool
// call.
Content []*ContentBlock `json:"content"`
Content []Content `json:"content"`
// Whether the tool call ended in an error.
//
// If not set, this is assumed to be false (the call was successful).
@ -83,13 +85,32 @@ type CallToolResult struct {
StructuredContent map[string]any `json:"structuredContent,omitempty"`
}
// UnmarshalJSON handles the unmarshalling of content into the Content
// interface.
func (x *CallToolResult) UnmarshalJSON(data []byte) error {
type res CallToolResult // avoid recursion
var wire struct {
res
Content []*wireContent `json:"content"`
}
if err := json.Unmarshal(data, &wire); err != nil {
return err
}
var err error
if wire.res.Content, err = contentsFromWire(wire.Content, nil); err != nil {
return err
}
*x = CallToolResult(wire.res)
return nil
}
type CallToolResultFor[Out any] struct {
// This property is reserved by the protocol to allow clients and servers to
// attach additional metadata to their responses.
Meta `json:"_meta,omitempty"`
// A list of content objects that represent the unstructured result of the tool
// call.
Content []*ContentBlock `json:"content"`
Content []Content `json:"content"`
// Whether the tool call ended in an error.
//
// If not set, this is assumed to be false (the call was successful).
@ -177,7 +198,7 @@ type CreateMessageResult struct {
// This property is reserved by the protocol to allow clients and servers to
// attach additional metadata to their responses.
Meta `json:"_meta,omitempty"`
Content any `json:"content"`
Content Content `json:"content"`
// The name of the model that generated the message.
Model string `json:"model"`
Role Role `json:"role"`
@ -518,8 +539,27 @@ func (x *PromptListChangedParams) SetProgressToken(t any) { setProgressToken(x,
// This is similar to `SamplingMessage`, but also supports the embedding of
// resources from the MCP server.
type PromptMessage struct {
Content *ContentBlock `json:"content"`
Role Role `json:"role"`
Content Content `json:"content"`
Role Role `json:"role"`
}
// UnmarshalJSON handles the unmarshalling of content into the Content
// interface.
func (m *PromptMessage) UnmarshalJSON(data []byte) error {
type msg PromptMessage // avoid recursion
var wire struct {
msg
Content *wireContent `json:"content"`
}
if err := json.Unmarshal(data, &wire); err != nil {
return err
}
var err error
if wire.msg.Content, err = contentFromWire(wire.Content, nil); err != nil {
return err
}
*m = PromptMessage(wire.msg)
return nil
}
type ReadResourceParams struct {
@ -650,8 +690,27 @@ type ElicitationCapabilities struct{}
// Describes a message issued to or received from an LLM API.
type SamplingMessage struct {
Content any `json:"content"`
Role Role `json:"role"`
Content Content `json:"content"`
Role Role `json:"role"`
}
// UnmarshalJSON handles the unmarshalling of content into the Content
// interface.
func (m *SamplingMessage) UnmarshalJSON(data []byte) error {
type msg SamplingMessage // avoid recursion
var wire struct {
msg
Content *wireContent `json:"content"`
}
if err := json.Unmarshal(data, &wire); err != nil {
return err
}
var err error
if wire.msg.Content, err = contentFromWire(wire.Content, map[string]bool{"text": true, "image": true, "audio": true}); err != nil {
return err
}
*m = SamplingMessage(wire.msg)
return nil
}
type SetLevelParams struct {

View file

@ -412,7 +412,9 @@ func fileResourceHandler(dir string) ResourceHandler {
return nil, err
}
// TODO(jba): figure out mime type. Omit for now: Server.readResource will fill it in.
return &ReadResourceResult{Contents: []*ResourceContents{NewBlobResourceContents(params.URI, "", data)}}, nil
return &ReadResourceResult{Contents: []*ResourceContents{
&ResourceContents{URI: params.URI, Blob: data},
}}, nil
}
}

View file

@ -18,8 +18,8 @@ type SayHiParams struct {
func SayHi(ctx context.Context, cc *mcp.ServerSession, params *mcp.CallToolParamsFor[SayHiParams]) (*mcp.CallToolResultFor[any], error) {
return &mcp.CallToolResultFor[any]{
Content: []*mcp.ContentBlock{
mcp.NewTextContent("Hi " + params.Arguments.Name),
Content: []mcp.Content{
&mcp.TextContent{Text: "Hi " + params.Arguments.Name},
},
}, nil
}
@ -49,7 +49,7 @@ func ExampleServer() {
if err != nil {
log.Fatal(err)
}
fmt.Println(res.Content[0].Text)
fmt.Println(res.Content[0].(*mcp.TextContent).Text)
clientSession.Close()
serverSession.Wait()

View file

@ -20,7 +20,9 @@ type AddParams struct {
func Add(ctx context.Context, cc *mcp.ServerSession, params *mcp.CallToolParamsFor[AddParams]) (*mcp.CallToolResultFor[any], error) {
return &mcp.CallToolResultFor[any]{
Content: []*mcp.ContentBlock{mcp.NewTextContent(fmt.Sprintf("%d", params.Arguments.X+params.Arguments.Y))},
Content: []mcp.Content{
&mcp.TextContent{Text: fmt.Sprintf("%d", params.Arguments.X+params.Arguments.Y)},
},
}, nil
}
@ -48,7 +50,7 @@ func ExampleSSEHandler() {
if err != nil {
log.Fatal(err)
}
fmt.Println(res.Content[0].Text)
fmt.Println(res.Content[0].(*mcp.TextContent).Text)
// Output: 3
}

View file

@ -53,7 +53,9 @@ func TestSSEServer(t *testing.T) {
t.Fatal(err)
}
wantHi := &CallToolResult{
Content: []*ContentBlock{{Type: "text", Text: "hi user"}},
Content: []Content{
&TextContent{Text: "hi user"},
},
}
if diff := cmp.Diff(wantHi, gotHi); diff != "" {
t.Errorf("tools/call 'greet' mismatch (-want +got):\n%s", diff)

View file

@ -82,7 +82,9 @@ func TestStreamableTransports(t *testing.T) {
// 5. Verify that the correct response is received.
want := &CallToolResult{
Content: []*ContentBlock{{Type: "text", Text: "hi streamy"}},
Content: []Content{
&TextContent{Text: "hi streamy"},
},
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("CallTool() returned unexpected content (-want +got):\n%s", diff)

View file

@ -24,7 +24,6 @@ code_review
"jsonrpc": "2.0",
"id": 1,
"result": {
"_meta": {},
"capabilities": {
"completions": {},
"logging": {},
@ -49,7 +48,6 @@ code_review
"jsonrpc": "2.0",
"id": 2,
"result": {
"_meta": {},
"tools": []
}
}
@ -57,7 +55,6 @@ code_review
"jsonrpc": "2.0",
"id": 4,
"result": {
"_meta": {},
"prompts": [
{
"arguments": [

View file

@ -44,7 +44,6 @@ info.txt
"jsonrpc": "2.0",
"id": 1,
"result": {
"_meta": {},
"capabilities": {
"completions": {},
"logging": {},
@ -69,7 +68,6 @@ info.txt
"jsonrpc": "2.0",
"id": 2,
"result": {
"_meta": {},
"resources": [
{
"mimeType": "text/plain",
@ -88,7 +86,6 @@ info.txt
"jsonrpc": "2.0",
"id": 3,
"result": {
"_meta": {},
"contents": [
{
"uri": "embedded:info",
@ -107,7 +104,6 @@ info.txt
"jsonrpc": "2.0",
"id": 3,
"result": {
"_meta": {},
"contents": [
{
"uri": "file:///info.txt",

View file

@ -27,7 +27,6 @@ greet
"jsonrpc": "2.0",
"id": 1,
"result": {
"_meta": {},
"capabilities": {
"completions": {},
"logging": {},
@ -52,7 +51,6 @@ greet
"jsonrpc": "2.0",
"id": 2,
"result": {
"_meta": {},
"tools": [
{
"description": "say hi",
@ -79,7 +77,6 @@ greet
"jsonrpc": "2.0",
"id": 3,
"result": {
"_meta": {},
"resources": []
}
}
@ -87,7 +84,6 @@ greet
"jsonrpc": "2.0",
"id": 4,
"result": {
"_meta": {},
"prompts": []
}
}

View file

@ -17,7 +17,6 @@ response with its latest supported version.
"jsonrpc": "2.0",
"id": 1,
"result": {
"_meta": {},
"capabilities": {
"completions": {},
"logging": {},

View file

@ -17,7 +17,6 @@ support.
"jsonrpc": "2.0",
"id": 1,
"result": {
"_meta": {},
"capabilities": {
"completions": {},
"logging": {},

View file

@ -132,7 +132,7 @@ func newRawHandler(st *ServerTool) rawToolHandler {
// rather than returned as jsonrpc2 server errors.
if err != nil {
return &CallToolResult{
Content: []*ContentBlock{NewTextContent(err.Error())},
Content: []Content{&TextContent{Text: err.Error()}},
IsError: true,
}, nil
}