From dac5be2c50871b08ff002b177951dce77d57a835 Mon Sep 17 00:00:00 2001 From: Rob Findley Date: Tue, 13 May 2025 21:51:26 +0000 Subject: [PATCH] internal/mcp: simplify content Now that the protocol package is merged into the mcp package, clean up the higher level 'Content' union types, in favor of types constructors. Change-Id: I37bedb0ff0a296eef799ba65c37ca7f94ee1dbbe Reviewed-on: https://go-review.googlesource.com/c/tools/+/672415 Reviewed-by: Jonathan Amsterdam TryBot-Bypass: Jonathan Amsterdam --- design/design.md | 30 ++++--- examples/hello/main.go | 4 +- examples/sse/main.go | 2 +- internal/util/util.go | 9 -- mcp/cmd_test.go | 2 +- mcp/content.go | 174 ++++++++++--------------------------- mcp/content_test.go | 67 ++++++-------- mcp/generate.go | 4 +- mcp/mcp_test.go | 12 +-- mcp/protocol.go | 8 +- mcp/server_example_test.go | 2 +- mcp/sse_example_test.go | 2 +- mcp/sse_test.go | 2 +- mcp/tool.go | 5 +- mcp/util.go | 5 -- 15 files changed, 114 insertions(+), 214 deletions(-) diff --git a/design/design.md b/design/design.md index 8046cee..3791f0f 100644 --- a/design/design.md +++ b/design/design.md @@ -123,6 +123,7 @@ type Stream interface { Close() error } ``` + Methods accept a Go `Context` and return an `error`, as is idiomatic for APIs that do I/O. @@ -350,17 +351,6 @@ type Content struct { Data []byte `json:"data,omitempty"` Resource *Resource `json:"resource,omitempty"` } - -// Resource is the wire format for embedded resources. -// -// 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 []byte `json:"blob"` -} ``` **Differences from mcp-go**: these types are largely similar, but our type @@ -445,6 +435,7 @@ content, err := session.CallTool(ctx, &CallToolParams{ ... return session.Close() ``` + A server that can handle that client call would look like this: ```go @@ -465,8 +456,6 @@ session until the client disconnects: func (*Server) Run(context.Context, Transport) ``` - - **Differences from mcp-go**: the Server APIs are very similar to mcp-go, though the association between servers and transports is different. In mcp-go, a single server is bound to what we would call an `SSEHTTPHandler`, @@ -493,9 +482,11 @@ documentation. As we saw above, the `ClientSession` method for the specification's `CallTool` RPC takes a context and a params pointer as arguments, and returns a result pointer and error: + ```go func (*ClientSession) CallTool(context.Context, *CallToolParams) (*CallToolResult, error) ``` + Our SDK has a method for every RPC in the spec, and their signatures all share this form. To avoid boilerplate, we don't repeat this signature for RPCs defined in the spec; readers may assume it when we mention a "spec method." @@ -867,12 +858,14 @@ handler to a Go function using reflection to derive its arguments. We provide To add a resource or resource template to a server, users call the `AddResource` and `AddResourceTemplate` methods, passing the resource or template and a function for reading it: + ```go type ReadResourceHandler func(context.Context, *ServerSession, *Resource, *ReadResourceParams) (*ReadResourceResult, error) func (*Server) AddResource(*Resource, ReadResourceHandler) func (*Server) AddResourceTemplate(*ResourceTemplate, ReadResourceHandler) ``` + The `Resource` is passed to the reader function even though it is redundant (the function could have closed over it) so a single handler can support multiple resources. If the incoming resource matches a template, a `Resource` argument is constructed @@ -880,15 +873,18 @@ from the fields in the `ResourceTemplate`. The `ServerSession` argument is there so the reader can observe the client's roots. To read files from the local filesystem, we recommend using `FileReadResourceHandler` to construct a handler: + ```go // FileReadResourceHandler returns a ReadResourceHandler that reads paths using dir as a root directory. // It protects against path traversal attacks. // It will not read any file that is not in the root set of the client requesting the resource. func (*Server) FileReadResourceHandler(dir string) ReadResourceHandler ``` + It guards against [path traversal attacks](https://go.dev/blog/osroot) and observes the client's roots. Here is an example: + ```go // Safely read "/public/puppies.txt". s.AddResource( @@ -897,14 +893,17 @@ s.AddResource( ``` There are also server methods to remove resources and resource templates. + ```go func (*Server) RemoveResources(uris ...string) func (*Server) RemoveResourceTemplates(names ...string) ``` + Resource templates don't have unique identifiers, so removing a name will remove all resource templates with that name. Servers support all of the resource-related spec methods: + - `ListResources` and `ListResourceTemplates` for listings. - `ReadResource` to get the contents of a resource. - `Subscribe` and `Unsubscribe` to manage subscriptions on resources. @@ -916,6 +915,7 @@ then returns the result of calling the associated reader function. #### Subscriptions ClientSessions can manage change notifications on particular resources: + ```go func (*ClientSession) Subscribe(context.Context, *SubscribeParams) error func (*ClientSession) Unsubscribe(context.Context, *UnsubscribeParams) error @@ -929,6 +929,7 @@ user doesn't have to. If a server author wants to support resource subscriptions, they must provide handlers to be called when clients subscribe and unsubscribe. It is an error to provide only one of these handlers. + ```go type ServerOptions struct { ... @@ -940,9 +941,11 @@ type ServerOptions struct { ``` User code should call `ResourceUpdated` when a subscribed resource changes. + ```go func (*Server) ResourceUpdated(context.Context, *ResourceUpdatedNotification) error ``` + The server routes these notifications to the server sessions that subscribed to the resource. ### ListChanged notifications @@ -1007,6 +1010,7 @@ follows: level, a handler would call `session.Log(ctx, mcp.LevelNotice, "message")`. A client that wishes to receive log messages must provide a handler: + ```go type ClientOptions struct { ... diff --git a/examples/hello/main.go b/examples/hello/main.go index 20087b4..7e3c52d 100644 --- a/examples/hello/main.go +++ b/examples/hello/main.go @@ -22,7 +22,7 @@ type HiParams struct { func SayHi(ctx context.Context, cc *mcp.ServerConnection, params *HiParams) ([]mcp.Content, error) { return []mcp.Content{ - mcp.TextContent{Text: "Hi " + params.Name}, + mcp.NewTextContent("Hi " + params.Name), }, nil } @@ -30,7 +30,7 @@ func PromptHi(ctx context.Context, cc *mcp.ServerConnection, params *HiParams) ( return &mcp.GetPromptResult{ Description: "Code review prompt", Messages: []mcp.PromptMessage{ - {Role: "user", Content: mcp.TextContent{Text: "Say hi to " + params.Name}.ToWire()}, + {Role: "user", Content: mcp.NewTextContent("Say hi to " + params.Name)}, }, }, nil } diff --git a/examples/sse/main.go b/examples/sse/main.go index 1dcdd6e..8ddea9e 100644 --- a/examples/sse/main.go +++ b/examples/sse/main.go @@ -21,7 +21,7 @@ type SayHiParams struct { func SayHi(ctx context.Context, cc *mcp.ServerConnection, params *SayHiParams) ([]mcp.Content, error) { return []mcp.Content{ - mcp.TextContent{Text: "Hi " + params.Name}, + mcp.NewTextContent("Hi " + params.Name), }, nil } diff --git a/internal/util/util.go b/internal/util/util.go index 0cd3eac..03a5dbb 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -10,15 +10,6 @@ import ( "slices" ) -// Apply returns a new slice resulting from applying f to each element of x. -func Apply[S ~[]E, E, F any](x S, f func(E) F) []F { - y := make([]F, len(x)) - for i, e := range x { - y[i] = f(e) - } - return y -} - // Helpers below are copied from gopls' moremaps package. // sorted returns an iterator over the entries of m in key order. diff --git a/mcp/cmd_test.go b/mcp/cmd_test.go index 9d17749..f380267 100644 --- a/mcp/cmd_test.go +++ b/mcp/cmd_test.go @@ -57,7 +57,7 @@ func TestCmdTransport(t *testing.T) { log.Fatal(err) } want := &mcp.CallToolResult{ - Content: []mcp.WireContent{{Type: "text", Text: "Hi user"}}, + Content: []mcp.Content{{Type: "text", Text: "Hi user"}}, } if diff := cmp.Diff(want, got); diff != "" { t.Errorf("greet returned unexpected content (-want +got):\n%s", diff) diff --git a/mcp/content.go b/mcp/content.go index 7d783e6..84ac60e 100644 --- a/mcp/content.go +++ b/mcp/content.go @@ -11,37 +11,25 @@ import ( // The []byte fields below are marked omitzero, not omitempty: // we want to marshal an empty byte slice. +// TODO(jba): figure out how to fix this for 1.23. -// WireContent is the wire format for content. +// Content 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"` +type Content struct { + 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"` } -// 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 +func (c *Content) UnmarshalJSON(data []byte) error { + type wireContent Content // for naive unmarshaling var c2 wireContent if err := json.Unmarshal(data, &c2); err != nil { return err @@ -51,77 +39,44 @@ func (c *WireContent) UnmarshalJSON(data []byte) error { default: return fmt.Errorf("unrecognized content type %s", c.Type) } - *c = WireContent(c2) + *c = Content(c2) return nil } -// Content is the union of supported content types: [TextContent], -// [ImageContent], [AudioContent], and [ResourceContent]. +func NewTextContent(text string) Content { + return Content{Type: "text", Text: text} +} + +func NewImageContent(data []byte, mimeType string) Content { + return Content{Type: "image", Data: data, MIMEType: mimeType} +} + +func NewAudioContent(data []byte, mimeType string) Content { + return Content{Type: "audio", Data: data, MIMEType: mimeType} +} + +func NewResourceContent(resource ResourceContents) Content { + return Content{Type: "resource", Resource: &resource} +} + +// 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. // -// 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 +// The URI field describes the resource location. +type ResourceContents struct { + URI string `json:"uri,"` + MIMEType string `json:"mimeType,omitempty"` + Text string `json:"text,omitempty"` + Blob []byte `json:"blob,omitzero"` } -// 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, +func NewTextResourceContents(uri, mimeType, text string) ResourceContents { + return ResourceContents{ + URI: uri, + MIMEType: mimeType, + Text: text, // Blob is nil, indicating this is a TextResourceContents. } } @@ -133,43 +88,10 @@ type BlobResourceContents struct { 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)) +func NewBlobResourceContents(uri, mimeType string, blob []byte) ResourceContents { + return ResourceContents{ + URI: uri, + MIMEType: mimeType, + Blob: blob, } } diff --git a/mcp/content_test.go b/mcp/content_test.go index 152bc27..512c7d3 100644 --- a/mcp/content_test.go +++ b/mcp/content_test.go @@ -5,6 +5,7 @@ package mcp_test import ( + "encoding/json" "testing" "github.com/google/go-cmp/cmp" @@ -14,57 +15,45 @@ import ( func TestContent(t *testing.T) { tests := []struct { in mcp.Content - want mcp.WireContent + want string // json serialization }{ - {mcp.TextContent{Text: "hello"}, mcp.WireContent{Type: "text", Text: "hello"}}, + {mcp.NewTextContent("hello"), `{"type":"text","text":"hello"}`}, { - mcp.ImageContent{Data: []byte("a1b2c3"), MIMEType: "image/png"}, - mcp.WireContent{Type: "image", Data: []byte("a1b2c3"), MIMEType: "image/png"}, + mcp.NewImageContent([]byte("a1b2c3"), "image/png"), + `{"type":"image","mimeType":"image/png","data":"YTFiMmMz"}`, }, { - mcp.AudioContent{Data: []byte("a1b2c3"), MIMEType: "audio/wav"}, - mcp.WireContent{Type: "audio", Data: []byte("a1b2c3"), MIMEType: "audio/wav"}, + mcp.NewAudioContent([]byte("a1b2c3"), "audio/wav"), + `{"type":"audio","mimeType":"audio/wav","data":"YTFiMmMz"}`, }, { - mcp.ResourceContent{ - Resource: mcp.TextResourceContents{ - URI: "file://foo", - MIMEType: "text", - Text: "abc", - }, - }, - mcp.WireContent{ - Type: "resource", - Resource: &mcp.WireResource{ - URI: "file://foo", - MIMEType: "text", - Text: "abc", - }, - }, + mcp.NewResourceContent( + mcp.NewTextResourceContents("file://foo", "text", "abc"), + ), + `{"type":"resource","resource":{"uri":"file://foo","mimeType":"text","text":"abc"}}`, }, { - mcp.ResourceContent{ - Resource: mcp.BlobResourceContents{ - URI: "file://foo", - MIMEType: "text", - Blob: []byte("a1b2c3"), - }, - }, - mcp.WireContent{ - Type: "resource", - Resource: &mcp.WireResource{ - URI: "file://foo", - MIMEType: "text", - Blob: []byte("a1b2c3"), - }, - }, + mcp.NewResourceContent( + mcp.NewBlobResourceContents("file://foo", "image/png", []byte("a1b2c3")), + ), + `{"type":"resource","resource":{"uri":"file://foo","mimeType":"image/png","blob":"YTFiMmMz"}}`, }, } for _, test := range tests { - got := test.in.ToWire() - if diff := cmp.Diff(test.want, got); diff != "" { - t.Errorf("ToWire mismatch (-want +got):\n%s", diff) + got, err := json.Marshal(test.in) + if err != nil { + t.Fatal(err) + } + 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.Content + if err := json.Unmarshal(got, &out); err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(test.in, out); diff != "" { + t.Errorf("json.Unmarshal(%q) mismatch (-want +got):\n%s", string(got), diff) } } } diff --git a/mcp/generate.go b/mcp/generate.go index 36b2bf6..7724ec2 100644 --- a/mcp/generate.go +++ b/mcp/generate.go @@ -107,7 +107,7 @@ var declarations = config{ Fields: config{"Params": {Name: "ReadResourceParams"}}, }, "ReadResourceResult": { - Fields: config{"Contents": {Substitute: "*WireResource"}}, + Fields: config{"Contents": {Substitute: "*ResourceContents"}}, }, "Resource": {}, "Role": {}, @@ -288,7 +288,7 @@ func writeType(w io.Writer, config *typeConfig, def *jsonschema.Schema, named ma if slices.ContainsFunc(def.AnyOf, func(s *jsonschema.Schema) bool { return s.Ref == "#/definitions/TextContent" }) { - fmt.Fprintf(w, "WireContent") + fmt.Fprintf(w, "Content") } else { // E.g. union types. fmt.Fprintf(w, "json.RawMessage") diff --git a/mcp/mcp_test.go b/mcp/mcp_test.go index 05d1a89..18eb797 100644 --- a/mcp/mcp_test.go +++ b/mcp/mcp_test.go @@ -28,7 +28,7 @@ func sayHi(ctx context.Context, cc *ServerConnection, v hiParams) ([]Content, er if err := cc.Ping(ctx); err != nil { return nil, fmt.Errorf("ping failed: %v", err) } - return []Content{TextContent{Text: "hi " + v.Name}}, nil + return []Content{NewTextContent("hi " + v.Name)}, nil } func TestEndToEnd(t *testing.T) { @@ -53,7 +53,7 @@ func TestEndToEnd(t *testing.T) { return &GetPromptResult{ Description: "Code review prompt", Messages: []PromptMessage{ - {Role: "user", Content: TextContent{Text: "Please review the following code: " + params.Code}.ToWire()}, + {Role: "user", Content: NewTextContent("Please review the following code: " + params.Code)}, }, }, nil }), @@ -116,7 +116,7 @@ func TestEndToEnd(t *testing.T) { wantReview := &GetPromptResult{ Description: "Code review prompt", Messages: []PromptMessage{{ - Content: TextContent{Text: "Please review the following code: 1+1"}.ToWire(), + Content: NewTextContent("Please review the following code: 1+1"), Role: "user", }}, } @@ -165,7 +165,7 @@ func TestEndToEnd(t *testing.T) { t.Fatal(err) } wantHi := &CallToolResult{ - Content: []WireContent{{Type: "text", Text: "hi user"}}, + Content: []Content{{Type: "text", Text: "hi user"}}, } if diff := cmp.Diff(wantHi, gotHi); diff != "" { t.Errorf("tools/call 'greet' mismatch (-want +got):\n%s", diff) @@ -179,7 +179,7 @@ func TestEndToEnd(t *testing.T) { } wantFail := &CallToolResult{ IsError: true, - Content: []WireContent{{Type: "text", Text: failure.Error()}}, + Content: []Content{{Type: "text", Text: failure.Error()}}, } if diff := cmp.Diff(wantFail, gotFail); diff != "" { t.Errorf("tools/call 'fail' mismatch (-want +got):\n%s", diff) @@ -201,7 +201,7 @@ func TestEndToEnd(t *testing.T) { readHandler := func(_ context.Context, r Resource, _ *ReadResourceParams) (*ReadResourceResult, error) { if r.URI == "file:///file1.txt" { return &ReadResourceResult{ - Contents: &WireResource{ + Contents: &ResourceContents{ Text: "file contents", }, }, nil diff --git a/mcp/protocol.go b/mcp/protocol.go index b885d2b..e942ecd 100644 --- a/mcp/protocol.go +++ b/mcp/protocol.go @@ -47,7 +47,7 @@ type CallToolResult struct { // This result property is reserved by the protocol to allow clients and servers // to attach additional metadata to their responses. Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Content []WireContent `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). @@ -195,8 +195,8 @@ type PromptArgument struct { // This is similar to `SamplingMessage`, but also supports the embedding of // resources from the MCP server. type PromptMessage struct { - Content WireContent `json:"content"` - Role Role `json:"role"` + Content Content `json:"content"` + Role Role `json:"role"` } type ReadResourceParams struct { @@ -210,7 +210,7 @@ type ReadResourceResult struct { // This result property is reserved by the protocol to allow clients and servers // to attach additional metadata to their responses. Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Contents *WireResource `json:"contents"` + Contents *ResourceContents `json:"contents"` } // A known resource that the server is capable of reading. diff --git a/mcp/server_example_test.go b/mcp/server_example_test.go index 614cc41..b94a07d 100644 --- a/mcp/server_example_test.go +++ b/mcp/server_example_test.go @@ -18,7 +18,7 @@ type SayHiParams struct { func SayHi(ctx context.Context, cc *mcp.ServerConnection, params *SayHiParams) ([]mcp.Content, error) { return []mcp.Content{ - mcp.TextContent{Text: "Hi " + params.Name}, + mcp.NewTextContent("Hi " + params.Name), }, nil } diff --git a/mcp/sse_example_test.go b/mcp/sse_example_test.go index 11a680f..9b4e0fe 100644 --- a/mcp/sse_example_test.go +++ b/mcp/sse_example_test.go @@ -20,7 +20,7 @@ type AddParams struct { func Add(ctx context.Context, cc *mcp.ServerConnection, params *AddParams) ([]mcp.Content, error) { return []mcp.Content{ - mcp.TextContent{Text: fmt.Sprintf("%d", params.X+params.Y)}, + mcp.NewTextContent(fmt.Sprintf("%d", params.X+params.Y)), }, nil } diff --git a/mcp/sse_test.go b/mcp/sse_test.go index e4533e6..ce8e514 100644 --- a/mcp/sse_test.go +++ b/mcp/sse_test.go @@ -48,7 +48,7 @@ func TestSSEServer(t *testing.T) { t.Fatal(err) } wantHi := &CallToolResult{ - Content: []WireContent{{Type: "text", Text: "hi user"}}, + Content: []Content{{Type: "text", Text: "hi user"}}, } if diff := cmp.Diff(wantHi, gotHi); diff != "" { t.Errorf("tools/call 'greet' mismatch (-want +got):\n%s", diff) diff --git a/mcp/tool.go b/mcp/tool.go index 78a0df4..86d0b0a 100644 --- a/mcp/tool.go +++ b/mcp/tool.go @@ -9,7 +9,6 @@ import ( "encoding/json" "slices" - "github.com/modelcontextprotocol/go-sdk/internal/util" "github.com/modelcontextprotocol/go-sdk/jsonschema" ) @@ -56,12 +55,12 @@ func NewTool[TReq any](name, description string, handler func(context.Context, * // rather than returned as jsonrpc2 server errors. if err != nil { return &CallToolResult{ - Content: []WireContent{TextContent{Text: err.Error()}.ToWire()}, + Content: []Content{NewTextContent(err.Error())}, IsError: true, }, nil } res := &CallToolResult{ - Content: util.Apply(content, Content.ToWire), + Content: content, } return res, nil } diff --git a/mcp/util.go b/mcp/util.go index d929cf1..102c088 100644 --- a/mcp/util.go +++ b/mcp/util.go @@ -14,11 +14,6 @@ func assert(cond bool, msg string) { } } -func is[T any](v any) bool { - _, ok := v.(T) - return ok -} - // Copied from crypto/rand. // TODO: once 1.24 is assured, just use crypto/rand. const base32alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"