diff --git a/README.md b/README.md index 6be05729..49dd6bd9 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ type HiParams struct { func SayHi(ctx context.Context, cc *mcp.ServerSession, params *mcp.CallToolParamsFor[HiParams]) (*mcp.CallToolResultFor[any], error) { return &mcp.CallToolResultFor[any]{ - Content: []*mcp.Content{mcp.NewTextContent("Hi " + params.Name)}, + Content: []*mcp.ContentBlock{mcp.NewTextContent("Hi " + params.Name)}, }, nil } diff --git a/examples/hello/main.go b/examples/hello/main.go index 2e8c2d91..5f343ff9 100644 --- a/examples/hello/main.go +++ b/examples/hello/main.go @@ -24,7 +24,7 @@ type HiArgs struct { func SayHi(ctx context.Context, ss *mcp.ServerSession, params *mcp.CallToolParamsFor[HiArgs]) (*mcp.CallToolResultFor[struct{}], error) { return &mcp.CallToolResultFor[struct{}]{ - Content: []*mcp.Content{ + Content: []*mcp.ContentBlock{ mcp.NewTextContent("Hi " + params.Name), }, }, nil diff --git a/examples/sse/main.go b/examples/sse/main.go index 010d545f..f88605a0 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.ServerSession, params *mcp.CallToolParamsFor[SayHiParams]) (*mcp.CallToolResultFor[any], error) { return &mcp.CallToolResultFor[any]{ - Content: []*mcp.Content{ + Content: []*mcp.ContentBlock{ mcp.NewTextContent("Hi " + params.Name), }, }, nil diff --git a/internal/readme/server/server.go b/internal/readme/server/server.go index 7d5b6eac..8a93ea2b 100644 --- a/internal/readme/server/server.go +++ b/internal/readme/server/server.go @@ -18,7 +18,7 @@ type HiParams struct { func SayHi(ctx context.Context, cc *mcp.ServerSession, params *mcp.CallToolParamsFor[HiParams]) (*mcp.CallToolResultFor[any], error) { return &mcp.CallToolResultFor[any]{ - Content: []*mcp.Content{mcp.NewTextContent("Hi " + params.Name)}, + Content: []*mcp.ContentBlock{mcp.NewTextContent("Hi " + params.Name)}, }, nil } diff --git a/mcp/cmd_test.go b/mcp/cmd_test.go index 69f67a50..1d34050e 100644 --- a/mcp/cmd_test.go +++ b/mcp/cmd_test.go @@ -70,7 +70,7 @@ func TestCmdTransport(t *testing.T) { log.Fatal(err) } want := &mcp.CallToolResult{ - Content: []*mcp.Content{{Type: "text", Text: "Hi user"}}, + Content: []*mcp.ContentBlock{{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 d8eb87d2..587f999d 100644 --- a/mcp/content.go +++ b/mcp/content.go @@ -10,58 +10,67 @@ import ( "fmt" ) -// Content is the wire format for content. -// It represents the protocol types TextContent, ImageContent, AudioContent -// and EmbeddedResource. -// Use [NewTextContent], [NewImageContent], [NewAudioContent] or [NewResourceContent] -// to create one. +// A ContentBlock is one of a TextContent, ImageContent, AudioContent +// ResourceLink, or EmbeddedResource. +// Use [NewTextContent], [NewImageContent], [NewAudioContent], [NewResourceLink] +// or [NewResourceContents] to create one. // -// The Type field must be one of "text", "image", "audio" or "resource". The -// constructors above populate this field appropriately. -// Although at most one of Text, Data, and Resource should be non-zero, consumers of Content -// use the Type field to determine which value to use; values in the other fields are ignored. -type Content 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"` - Annotations *Annotations `json:"annotations,omitempty"` +// 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"` } -func (c *Content) UnmarshalJSON(data []byte) error { - type wireContent Content // for naive unmarshaling +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": + case "text", "image", "audio", "resource", "resource_link": default: return fmt.Errorf("unrecognized content type %s", c.Type) } - *c = Content(c2) + *c = ContentBlock(c2) return nil } -// NewTextContent creates a [Content] with text. -func NewTextContent(text string) *Content { - return &Content{Type: "text", Text: text} +// NewTextContent creates a [ContentBlock] with text. +func NewTextContent(text string) *ContentBlock { + return &ContentBlock{Type: "text", Text: text} } -// NewImageContent creates a [Content] with image data. -func NewImageContent(data []byte, mimeType string) *Content { - return &Content{Type: "image", Data: data, MIMEType: mimeType} +// NewImageContent creates a [ContentBlock] with image data. +func NewImageContent(data []byte, mimeType string) *ContentBlock { + return &ContentBlock{Type: "image", Data: data, MIMEType: mimeType} } -// NewAudioContent creates a [Content] with audio data. -func NewAudioContent(data []byte, mimeType string) *Content { - return &Content{Type: "audio", Data: data, MIMEType: mimeType} +// NewAudioContent creates a [ContentBlock] with audio data. +func NewAudioContent(data []byte, mimeType string) *ContentBlock { + return &ContentBlock{Type: "audio", Data: data, MIMEType: mimeType} } -// NewResourceContent creates a [Content] with an embedded resource. -func NewResourceContent(resource *ResourceContents) *Content { - return &Content{Type: "resource", Resource: resource} +// NewResourceLink creates a [ContentBlock] with a [Resource]. +func NewResourceLink(r *Resource) *ContentBlock { + return &ContentBlock{Type: "resource_link", ResourceLink: r} +} + +// NewResourceContents creates a [ContentBlock] with an embedded resource (a [ResourceContents]). +func NewResourceContents(rc *ResourceContents) *ContentBlock { + return &ContentBlock{Type: "resource", Resource: rc} } // ResourceContents represents the union of the spec's {Text,Blob}ResourceContents types. @@ -71,10 +80,11 @@ func NewResourceContent(resource *ResourceContents) *Content { // A ResourceContents is either a TextResourceContents or a BlobResourceContents. // Use [NewTextResourceContents] or [NextBlobResourceContents] to create one. type ResourceContents struct { - 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 + 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 } func (r ResourceContents) MarshalJSON() ([]byte, error) { diff --git a/mcp/content_test.go b/mcp/content_test.go index 24cb53c5..08974c3c 100644 --- a/mcp/content_test.go +++ b/mcp/content_test.go @@ -14,7 +14,7 @@ import ( func TestContent(t *testing.T) { tests := []struct { - in *mcp.Content + in *mcp.ContentBlock want string // json serialization }{ {mcp.NewTextContent("hello"), `{"type":"text","text":"hello"}`}, @@ -27,13 +27,13 @@ func TestContent(t *testing.T) { `{"type":"audio","mimeType":"audio/wav","data":"YTFiMmMz"}`, }, { - mcp.NewResourceContent( + mcp.NewResourceContents( mcp.NewTextResourceContents("file://foo", "text", "abc"), ), `{"type":"resource","resource":{"uri":"file://foo","mimeType":"text","text":"abc"}}`, }, { - mcp.NewResourceContent( + mcp.NewResourceContents( mcp.NewBlobResourceContents("file://foo", "image/png", []byte("a1b2c3")), ), `{"type":"resource","resource":{"uri":"file://foo","mimeType":"image/png","blob":"YTFiMmMz"}}`, @@ -48,7 +48,7 @@ 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.Content + var out *mcp.ContentBlock if err := json.Unmarshal(got, &out); err != nil { t.Fatal(err) } diff --git a/mcp/example_progress_test.go b/mcp/example_progress_test.go index 56a57729..902b2347 100644 --- a/mcp/example_progress_test.go +++ b/mcp/example_progress_test.go @@ -23,7 +23,9 @@ func Example_progressMiddleware() { func addProgressToken[S mcp.Session](h mcp.MethodHandler[S]) mcp.MethodHandler[S] { return func(ctx context.Context, s S, method string, params mcp.Params) (result mcp.Result, err error) { - params.GetMeta().ProgressToken = nextProgressToken.Add(1) + if rp, ok := params.(mcp.RequestParams); ok { + rp.SetProgressToken(nextProgressToken.Add(1)) + } return h(ctx, s, method, params) } } diff --git a/mcp/features_test.go b/mcp/features_test.go index cdff4fb9..8a965a67 100644 --- a/mcp/features_test.go +++ b/mcp/features_test.go @@ -20,7 +20,7 @@ type SayHiParams struct { func SayHi(ctx context.Context, cc *ServerSession, params *CallToolParamsFor[SayHiParams]) (*CallToolResultFor[any], error) { return &CallToolResultFor[any]{ - Content: []*Content{ + Content: []*ContentBlock{ NewTextContent("Hi " + params.Name), }, }, nil diff --git a/mcp/mcp_test.go b/mcp/mcp_test.go index f39e68af..dd8de8d2 100644 --- a/mcp/mcp_test.go +++ b/mcp/mcp_test.go @@ -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: []*Content{NewTextContent("hi " + params.Arguments.Name)}}, nil + return &CallToolResultFor[any]{Content: []*ContentBlock{NewTextContent("hi " + params.Arguments.Name)}}, nil } func TestEndToEnd(t *testing.T) { @@ -195,7 +195,7 @@ func TestEndToEnd(t *testing.T) { t.Fatal(err) } wantHi := &CallToolResult{ - Content: []*Content{{Type: "text", Text: "hi user"}}, + Content: []*ContentBlock{{Type: "text", Text: "hi user"}}, } if diff := cmp.Diff(wantHi, gotHi); diff != "" { t.Errorf("tools/call 'greet' mismatch (-want +got):\n%s", diff) @@ -212,7 +212,7 @@ func TestEndToEnd(t *testing.T) { } wantFail := &CallToolResult{ IsError: true, - Content: []*Content{{Type: "text", Text: errTestFailure.Error()}}, + Content: []*ContentBlock{{Type: "text", Text: errTestFailure.Error()}}, } if diff := cmp.Diff(wantFail, gotFail); diff != "" { t.Errorf("tools/call 'fail' mismatch (-want +got):\n%s", diff) diff --git a/mcp/protocol.go b/mcp/protocol.go index 2746a18f..7168c938 100644 --- a/mcp/protocol.go +++ b/mcp/protocol.go @@ -4,6 +4,12 @@ package mcp +// Protocol types for version 2025-06-18. +// To see the schema changes from the previous version, run: +// +// prefix=https://raw.githubusercontent.com/modelcontextprotocol/modelcontextprotocol/refs/heads/main/schema +// sdiff -l <(curl $prefix/2025-03-26/schema.ts) <(curl $prefix/2025/06-18/schema.ts) + import ( "github.com/modelcontextprotocol/go-sdk/jsonschema" ) @@ -16,6 +22,13 @@ type Annotations struct { // It can include multiple entries to indicate content useful for multiple // audiences (e.g., `["user", "assistant"]`). Audience []Role `json:"audience,omitempty"` + // The moment the resource was last modified, as an ISO 8601 formatted string. + // + // Should be an ISO 8601 formatted string (e.g., "2025-01-12T15:00:58Z"). + // + // Examples: last activity timestamp in an open file, timestamp when the + // resource was attached, etc. + LastModified string `json:"lastModified,omitempty"` // Describes how important this data is for operating the server. // // A value of 1 means "most important," and indicates that the data is @@ -27,63 +40,78 @@ type Annotations struct { type CallToolParams struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` Arguments any `json:"arguments,omitempty"` Name string `json:"name"` } -func (x *CallToolParams) GetMeta() *Meta { return &x.Meta } +func (x *CallToolParams) GetProgressToken() any { return getProgressToken(x) } +func (x *CallToolParams) SetProgressToken(t any) { setProgressToken(x, t) } type CallToolParamsFor[In any] struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` Arguments In `json:"arguments,omitempty"` Name string `json:"name"` } -func (x *CallToolParamsFor[In]) GetMeta() *Meta { return &x.Meta } - // The server's response to a tool call. -// -// Any errors that originate from the tool SHOULD be reported inside the result -// object, with `isError` set to true, _not_ as an MCP protocol-level error -// response. Otherwise, the LLM would not be able to see that an error occurred -// and self-correct. -// -// However, any errors in _finding_ the tool, an error indicating that the -// server does not support tool calls, or any other exceptional conditions, -// should be reported as an MCP error response. type CallToolResult struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` - Content []*Content `json:"content"` + Meta `json:"_meta,omitempty"` + // A list of content objects that represent the unstructured result of the tool + // call. + Content []*ContentBlock `json:"content"` // Whether the tool call ended in an error. // // If not set, this is assumed to be false (the call was successful). + // + // Any errors that originate from the tool SHOULD be reported inside the result + // object, with `isError` set to true, _not_ as an MCP protocol-level error + // response. Otherwise, the LLM would not be able to see that an error occurred + // and self-correct. + // + // However, any errors in _finding_ the tool, an error indicating that the + // server does not support tool calls, or any other exceptional conditions, + // should be reported as an MCP error response. IsError bool `json:"isError,omitempty"` + // An optional JSON object that represents the structured result of the tool + // call. + // TODO(jba,rfindley): should this be any? + StructuredContent map[string]any `json:"structuredContent,omitempty"` } -func (x *CallToolResult) GetMeta() *Meta { return &x.Meta } - 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 Meta `json:"_meta,omitempty"` - Content []*Content `json:"content"` + Meta `json:"_meta,omitempty"` + // A list of content objects that represent the unstructured result of the tool + // call. + Content []*ContentBlock `json:"content"` // Whether the tool call ended in an error. // // If not set, this is assumed to be false (the call was successful). + // + // Any errors that originate from the tool SHOULD be reported inside the result + // object, with `isError` set to true, not as an MCP protocol-level error + // response. Otherwise, the LLM would not be able to see that an error occurred + // and self-correct. + // + // However, any errors in finding the tool, an error indicating that the + // server does not support tool calls, or any other exceptional conditions, + // should be reported as an MCP error response. IsError bool `json:"isError,omitempty"` + // An optional JSON object that represents the structured result of the tool + // call. + StructuredContent Out `json:"structuredContent,omitempty"` } -func (x *CallToolResultFor[Out]) GetMeta() *Meta { return &x.Meta } - type CancelledParams struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` // An optional string describing the reason for the cancellation. This MAY be // logged or presented to the user. Reason string `json:"reason,omitempty"` @@ -94,7 +122,8 @@ type CancelledParams struct { RequestID any `json:"requestId"` } -func (x *CancelledParams) GetMeta() *Meta { return &x.Meta } +func (x *CancelledParams) GetProgressToken() any { return getProgressToken(x) } +func (x *CancelledParams) SetProgressToken(t any) { setProgressToken(x, t) } // Capabilities a client may support. Known capabilities are defined here, in // this schema, but this is not a closed set: any client can define its own, @@ -109,12 +138,14 @@ type ClientCapabilities struct { } `json:"roots,omitempty"` // Present if the client supports sampling from an LLM. Sampling *SamplingCapabilities `json:"sampling,omitempty"` + // Present if the client supports elicitation from the server. + Elicitation *ElicitationCapabilities `json:"elicitation,omitempty"` } type CreateMessageParams struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` // A request to include context from one or more MCP servers (including the // caller), to be attached to the prompt. The client MAY ignore this request. IncludeContext string `json:"includeContext,omitempty"` @@ -135,7 +166,8 @@ type CreateMessageParams struct { Temperature float64 `json:"temperature,omitempty"` } -func (x *CreateMessageParams) GetMeta() *Meta { return &x.Meta } +func (x *CreateMessageParams) GetProgressToken() any { return getProgressToken(x) } +func (x *CreateMessageParams) SetProgressToken(t any) { setProgressToken(x, t) } // The client's response to a sampling/create_message request from the server. // The client should inform the user before returning the sampled message, to @@ -144,8 +176,8 @@ func (x *CreateMessageParams) GetMeta() *Meta { return &x.Meta } type CreateMessageResult struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` - Content *Content `json:"content"` + Meta `json:"_meta,omitempty"` + Content any `json:"content"` // The name of the model that generated the message. Model string `json:"model"` Role Role `json:"role"` @@ -153,36 +185,33 @@ type CreateMessageResult struct { StopReason string `json:"stopReason,omitempty"` } -func (x *CreateMessageResult) GetMeta() *Meta { return &x.Meta } - type GetPromptParams struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` // Arguments to use for templating the prompt. Arguments map[string]string `json:"arguments,omitempty"` // The name of the prompt or prompt template. Name string `json:"name"` } -func (x *GetPromptParams) GetMeta() *Meta { return &x.Meta } +func (x *GetPromptParams) GetProgressToken() any { return getProgressToken(x) } +func (x *GetPromptParams) SetProgressToken(t any) { setProgressToken(x, t) } // The server's response to a prompts/get request from the client. type GetPromptResult struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` // An optional description for the prompt. Description string `json:"description,omitempty"` Messages []*PromptMessage `json:"messages"` } -func (x *GetPromptResult) GetMeta() *Meta { return &x.Meta } - type InitializeParams struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` Capabilities *ClientCapabilities `json:"capabilities"` ClientInfo *implementation `json:"clientInfo"` // The latest version of the Model Context Protocol that the client supports. @@ -190,14 +219,15 @@ type InitializeParams struct { ProtocolVersion string `json:"protocolVersion"` } -func (x *InitializeParams) GetMeta() *Meta { return &x.Meta } +func (x *InitializeParams) GetProgressToken() any { return getProgressToken(x) } +func (x *InitializeParams) SetProgressToken(t any) { setProgressToken(x, t) } // After receiving an initialize request from the client, the server sends this // response. type InitializeResult struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` Capabilities *serverCapabilities `json:"capabilities"` // Instructions describing how to use the server and its features. // @@ -212,101 +242,101 @@ type InitializeResult struct { ServerInfo *implementation `json:"serverInfo"` } -func (x *InitializeResult) GetMeta() *Meta { return &x.Meta } - type InitializedParams struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` } -func (x *InitializedParams) GetMeta() *Meta { return &x.Meta } +func (x *InitializedParams) GetProgressToken() any { return getProgressToken(x) } +func (x *InitializedParams) SetProgressToken(t any) { setProgressToken(x, t) } type ListPromptsParams struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` // An opaque token representing the current pagination position. If provided, // the server should return results starting after this cursor. Cursor string `json:"cursor,omitempty"` } -func (x *ListPromptsParams) GetMeta() *Meta { return &x.Meta } -func (x *ListPromptsParams) cursorPtr() *string { return &x.Cursor } +func (x *ListPromptsParams) GetProgressToken() any { return getProgressToken(x) } +func (x *ListPromptsParams) SetProgressToken(t any) { setProgressToken(x, t) } +func (x *ListPromptsParams) cursorPtr() *string { return &x.Cursor } // The server's response to a prompts/list request from the client. type ListPromptsResult struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` // An opaque token representing the pagination position after the last returned // result. If present, there may be more results available. NextCursor string `json:"nextCursor,omitempty"` Prompts []*Prompt `json:"prompts"` } -func (x *ListPromptsResult) GetMeta() *Meta { return &x.Meta } func (x *ListPromptsResult) nextCursorPtr() *string { return &x.NextCursor } type ListResourceTemplatesParams struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` // An opaque token representing the current pagination position. If provided, // the server should return results starting after this cursor. Cursor string `json:"cursor,omitempty"` } -func (x *ListResourceTemplatesParams) GetMeta() *Meta { return &x.Meta } -func (x *ListResourceTemplatesParams) cursorPtr() *string { return &x.Cursor } +func (x *ListResourceTemplatesParams) GetProgressToken() any { return getProgressToken(x) } +func (x *ListResourceTemplatesParams) SetProgressToken(t any) { setProgressToken(x, t) } +func (x *ListResourceTemplatesParams) cursorPtr() *string { return &x.Cursor } // The server's response to a resources/templates/list request from the client. type ListResourceTemplatesResult struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` // An opaque token representing the pagination position after the last returned // result. If present, there may be more results available. NextCursor string `json:"nextCursor,omitempty"` ResourceTemplates []*ResourceTemplate `json:"resourceTemplates"` } -func (x *ListResourceTemplatesResult) GetMeta() *Meta { return &x.Meta } func (x *ListResourceTemplatesResult) nextCursorPtr() *string { return &x.NextCursor } type ListResourcesParams struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` // An opaque token representing the current pagination position. If provided, // the server should return results starting after this cursor. Cursor string `json:"cursor,omitempty"` } -func (x *ListResourcesParams) GetMeta() *Meta { return &x.Meta } -func (x *ListResourcesParams) cursorPtr() *string { return &x.Cursor } +func (x *ListResourcesParams) GetProgressToken() any { return getProgressToken(x) } +func (x *ListResourcesParams) SetProgressToken(t any) { setProgressToken(x, t) } +func (x *ListResourcesParams) cursorPtr() *string { return &x.Cursor } // The server's response to a resources/list request from the client. type ListResourcesResult struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` // An opaque token representing the pagination position after the last returned // result. If present, there may be more results available. NextCursor string `json:"nextCursor,omitempty"` Resources []*Resource `json:"resources"` } -func (x *ListResourcesResult) GetMeta() *Meta { return &x.Meta } func (x *ListResourcesResult) nextCursorPtr() *string { return &x.NextCursor } type ListRootsParams struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` } -func (x *ListRootsParams) GetMeta() *Meta { return &x.Meta } +func (x *ListRootsParams) GetProgressToken() any { return getProgressToken(x) } +func (x *ListRootsParams) SetProgressToken(t any) { setProgressToken(x, t) } // The client's response to a roots/list request from the server. This result // contains an array of Root objects, each representing a root directory or file @@ -314,36 +344,34 @@ func (x *ListRootsParams) GetMeta() *Meta { return &x.Meta } type ListRootsResult struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` Roots []*Root `json:"roots"` } -func (x *ListRootsResult) GetMeta() *Meta { return &x.Meta } - type ListToolsParams struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` // An opaque token representing the current pagination position. If provided, // the server should return results starting after this cursor. Cursor string `json:"cursor,omitempty"` } -func (x *ListToolsParams) GetMeta() *Meta { return &x.Meta } -func (x *ListToolsParams) cursorPtr() *string { return &x.Cursor } +func (x *ListToolsParams) GetProgressToken() any { return getProgressToken(x) } +func (x *ListToolsParams) SetProgressToken(t any) { setProgressToken(x, t) } +func (x *ListToolsParams) cursorPtr() *string { return &x.Cursor } // The server's response to a tools/list request from the client. type ListToolsResult struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` // An opaque token representing the pagination position after the last returned // result. If present, there may be more results available. NextCursor string `json:"nextCursor,omitempty"` Tools []*Tool `json:"tools"` } -func (x *ListToolsResult) GetMeta() *Meta { return &x.Meta } func (x *ListToolsResult) nextCursorPtr() *string { return &x.NextCursor } // The severity of a log message. @@ -355,7 +383,7 @@ type LoggingLevel string type LoggingMessageParams struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` // The data to be logged, such as a string message or an object. Any JSON // serializable type is allowed here. Data any `json:"data"` @@ -365,7 +393,8 @@ type LoggingMessageParams struct { Logger string `json:"logger,omitempty"` } -func (x *LoggingMessageParams) GetMeta() *Meta { return &x.Meta } +func (x *LoggingMessageParams) GetProgressToken() any { return getProgressToken(x) } +func (x *LoggingMessageParams) SetProgressToken(t any) { setProgressToken(x, t) } // Hints to use for model selection. // @@ -422,15 +451,16 @@ type ModelPreferences struct { type PingParams struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` } -func (x *PingParams) GetMeta() *Meta { return &x.Meta } +func (x *PingParams) GetProgressToken() any { return getProgressToken(x) } +func (x *PingParams) SetProgressToken(t any) { setProgressToken(x, t) } type ProgressNotificationParams struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` // An optional message describing the current progress. Message string `json:"message,omitempty"` // The progress thus far. This should increase every time progress is made, even @@ -443,24 +473,33 @@ type ProgressNotificationParams struct { Total float64 `json:"total,omitempty"` } -func (x *ProgressNotificationParams) GetMeta() *Meta { return &x.Meta } - // A prompt or prompt template that the server offers. type Prompt struct { + // See [specification/2025-06-18/basic/index#general-fields] for notes on _meta + // usage. + Meta `json:"_meta,omitempty"` // A list of arguments to use for templating the prompt. Arguments []*PromptArgument `json:"arguments,omitempty"` // An optional description of what this prompt provides Description string `json:"description,omitempty"` - // The name of the prompt or prompt template. + // Intended for programmatic or logical use, but used as a display name in past + // specs or fallback (if title isn't present). Name string `json:"name"` + // Intended for UI and end-user contexts — optimized to be human-readable and + // easily understood, even by those unfamiliar with domain-specific terminology. + Title string `json:"title,omitempty"` } // Describes an argument that a prompt can accept. type PromptArgument struct { + // Intended for programmatic or logical use, but used as a display name in past + // specs or fallback (if title isn't present). + Name string `json:"name"` + // Intended for UI and end-user contexts — optimized to be human-readable and + // easily understood, even by those unfamiliar with domain-specific terminology. + Title string `json:"title,omitempty"` // A human-readable description of the argument. Description string `json:"description,omitempty"` - // The name of the argument. - Name string `json:"name"` // Whether this argument must be provided. Required bool `json:"required,omitempty"` } @@ -468,43 +507,46 @@ type PromptArgument struct { type PromptListChangedParams struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` } -func (x *PromptListChangedParams) GetMeta() *Meta { return &x.Meta } +func (x *PromptListChangedParams) GetProgressToken() any { return getProgressToken(x) } +func (x *PromptListChangedParams) SetProgressToken(t any) { setProgressToken(x, t) } // Describes a message returned as part of a prompt. // // This is similar to `SamplingMessage`, but also supports the embedding of // resources from the MCP server. type PromptMessage struct { - Content *Content `json:"content"` - Role Role `json:"role"` + Content *ContentBlock `json:"content"` + Role Role `json:"role"` } type ReadResourceParams struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` // The URI of the resource to read. The URI can use any protocol; it is up to // the server how to interpret it. URI string `json:"uri"` } -func (x *ReadResourceParams) GetMeta() *Meta { return &x.Meta } +func (x *ReadResourceParams) GetProgressToken() any { return getProgressToken(x) } +func (x *ReadResourceParams) SetProgressToken(t any) { setProgressToken(x, t) } // The server's response to a resources/read request from the client. type ReadResourceResult struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` Contents []*ResourceContents `json:"contents"` } -func (x *ReadResourceResult) GetMeta() *Meta { return &x.Meta } - // A known resource that the server is capable of reading. type Resource struct { + // See [specification/2025-06-18/basic/index#general-fields] for notes on _meta + // usage. + Meta `json:"_meta,omitempty"` // Optional annotations for the client. Annotations *Annotations `json:"annotations,omitempty"` // A description of what this resource represents. @@ -514,9 +556,8 @@ type Resource struct { Description string `json:"description,omitempty"` // The MIME type of this resource, if known. MIMEType string `json:"mimeType,omitempty"` - // A human-readable name for this resource. - // - // This can be used by clients to populate UI elements. + // Intended for programmatic or logical use, but used as a display name in past + // specs or fallback (if title isn't present). Name string `json:"name"` // The size of the raw resource content, in bytes (i.e., before base64 encoding // or any tokenization), if known. @@ -524,6 +565,13 @@ type Resource struct { // This can be used by Hosts to display file sizes and estimate context window // usage. Size int64 `json:"size,omitempty"` + // Intended for UI and end-user contexts — optimized to be human-readable and + // easily understood, even by those unfamiliar with domain-specific terminology. + // + // If not provided, the name should be used for display (except for Tool, where + // `annotations.title` should be given precedence over using `name`, if + // present). + Title string `json:"title,omitempty"` // The URI of this resource. URI string `json:"uri"` } @@ -531,13 +579,17 @@ type Resource struct { type ResourceListChangedParams struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` } -func (x *ResourceListChangedParams) GetMeta() *Meta { return &x.Meta } +func (x *ResourceListChangedParams) GetProgressToken() any { return getProgressToken(x) } +func (x *ResourceListChangedParams) SetProgressToken(t any) { setProgressToken(x, t) } // A template description for resources available on the server. type ResourceTemplate struct { + // See [specification/2025-06-18/basic/index#general-fields] for notes on _meta + // usage. + Meta `json:"_meta,omitempty"` // Optional annotations for the client. Annotations *Annotations `json:"annotations,omitempty"` // A description of what this template is for. @@ -548,10 +600,16 @@ type ResourceTemplate struct { // The MIME type for all resources that match this template. This should only be // included if all resources matching this template have the same type. MIMEType string `json:"mimeType,omitempty"` - // A human-readable name for the type of resource this template refers to. - // - // This can be used by clients to populate UI elements. + // Intended for programmatic or logical use, but used as a display name in past + // specs or fallback (if title isn't present). Name string `json:"name"` + // Intended for UI and end-user contexts — optimized to be human-readable and + // easily understood, even by those unfamiliar with domain-specific terminology. + // + // If not provided, the name should be used for display (except for Tool, where + // `annotations.title` should be given precedence over using `name`, if + // present). + Title string `json:"title,omitempty"` // A URI template (according to RFC 6570) that can be used to construct resource // URIs. URITemplate string `json:"uriTemplate"` @@ -562,6 +620,9 @@ type Role string // Represents a root directory or file that the server can operate on. type Root struct { + // See [specification/2025-06-18/basic/index#general-fields] for notes on _meta + // usage. + Meta `json:"_meta,omitempty"` // An optional name for the root. This can be used to provide a human-readable // identifier for the root, which may be useful for display purposes or for // referencing the root in other parts of the application. @@ -575,35 +636,45 @@ type Root struct { type RootsListChangedParams struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` } -func (x *RootsListChangedParams) GetMeta() *Meta { return &x.Meta } +func (x *RootsListChangedParams) GetProgressToken() any { return getProgressToken(x) } +func (x *RootsListChangedParams) SetProgressToken(t any) { setProgressToken(x, t) } -// Present if the client supports sampling from an LLM. +// SamplingCapabilities describes the capabilities for sampling. type SamplingCapabilities struct{} +// ElicitationCapabilities describes the capabilities for elicitation. +type ElicitationCapabilities struct{} + // Describes a message issued to or received from an LLM API. type SamplingMessage struct { - Content *Content `json:"content"` - Role Role `json:"role"` + Content any `json:"content"` + Role Role `json:"role"` } type SetLevelParams struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` // The level of logging that the client wants to receive from the server. The // server should send all logs at this level and higher (i.e., more severe) to // the client as notifications/message. Level LoggingLevel `json:"level"` } -func (x *SetLevelParams) GetMeta() *Meta { return &x.Meta } +func (x *SetLevelParams) GetProgressToken() any { return getProgressToken(x) } +func (x *SetLevelParams) SetProgressToken(t any) { setProgressToken(x, t) } // Definition for a tool the client can call. type Tool struct { + // See [specification/2025-06-18/basic/index#general-fields] for notes on _meta + // usage. + Meta `json:"_meta,omitempty"` // Optional additional tool information. + // + // Display name precedence order is: title, annotations.title, then name. Annotations *ToolAnnotations `json:"annotations,omitempty"` // A human-readable description of the tool. // @@ -612,8 +683,17 @@ type Tool struct { Description string `json:"description,omitempty"` // A JSON Schema object defining the expected parameters for the tool. InputSchema *jsonschema.Schema `json:"inputSchema"` - // The name of the tool. + // Intended for programmatic or logical use, but used as a display name in past + // specs or fallback (if title isn't present). Name string `json:"name"` + // An optional JSON Schema object defining the structure of the tool's output + // returned in the structuredContent field of a CallToolResult. + OutputSchema *jsonschema.Schema `json:"outputSchema,omitempty"` + // Intended for UI and end-user contexts — optimized to be human-readable and + // easily understood, even by those unfamiliar with domain-specific terminology. + // If not provided, Annotations.Title should be used for display if present, + // otherwise Name. + Title string `json:"title,omitempty"` } // Additional properties describing a Tool to clients. @@ -656,14 +736,25 @@ type ToolAnnotations struct { type ToolListChangedParams struct { // This property is reserved by the protocol to allow clients and servers to // attach additional metadata to their responses. - Meta Meta `json:"_meta,omitempty"` + Meta `json:"_meta,omitempty"` } -func (x *ToolListChangedParams) GetMeta() *Meta { return &x.Meta } +func (x *ToolListChangedParams) GetProgressToken() any { return getProgressToken(x) } +func (x *ToolListChangedParams) SetProgressToken(t any) { setProgressToken(x, t) } + +// TODO(jba): add CompleteRequest and related types. -// Describes the name and version of an MCP implementation. +// TODO(jba): add ElicitRequest and related types. + +// Describes the name and version of an MCP implementation, with an optional +// title for UI representation. type implementation struct { - Name string `json:"name"` + // Intended for programmatic or logical use, but used as a display name in past + // specs or fallback (if title isn't present). + Name string `json:"name"` + // Intended for UI and end-user contexts — optimized to be human-readable and + // easily understood, even by those unfamiliar with domain-specific terminology. + Title string `json:"title,omitempty"` Version string `json:"version"` } @@ -713,6 +804,7 @@ const ( notificationCancelled = "notifications/cancelled" methodComplete = "completion/complete" methodCreateMessage = "sampling/createMessage" + methodElicit = "elicitation/create" methodGetPrompt = "prompts/get" methodInitialize = "initialize" notificationInitialized = "notifications/initialized" diff --git a/mcp/protocol_test.go b/mcp/protocol_test.go new file mode 100644 index 00000000..d68fb738 --- /dev/null +++ b/mcp/protocol_test.go @@ -0,0 +1,69 @@ +// 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" + "maps" + "testing" +) + +func TestParamsMeta(t *testing.T) { + // Verify some properties of the Meta field of Params structs. + // We use CallToolParams for the test, but the Meta setup of all params types + // is identical so they should all behave the same. + + toJSON := func(x any) string { + data, err := json.Marshal(x) + if err != nil { + t.Fatal(err) + } + return string(data) + } + + meta := map[string]any{"m": 1} + + // You can set the embedded Meta field to a literal map. + p := &CallToolParams{ + Meta: meta, + Name: "name", + } + + // The Meta field marshals properly when it's present. + if g, w := toJSON(p), `{"_meta":{"m":1},"name":"name"}`; g != w { + t.Errorf("got %s, want %s", g, w) + } + // ... and when it's absent. + p2 := &CallToolParams{Name: "n"} + if g, w := toJSON(p2), `{"name":"n"}`; g != w { + t.Errorf("got %s, want %s", g, w) + } + + // The GetMeta and SetMeta functions work as expected. + if g := p.GetMeta(); !maps.Equal(g, meta) { + t.Errorf("got %+v, want %+v", g, meta) + } + + meta2 := map[string]any{"x": 2} + p.SetMeta(meta2) + if g := p.GetMeta(); !maps.Equal(g, meta2) { + t.Errorf("got %+v, want %+v", g, meta2) + } + + // The GetProgressToken and SetProgressToken methods work as expected. + if g := p.GetProgressToken(); g != nil { + t.Errorf("got %v, want nil", g) + } + + p.SetProgressToken("t") + if g := p.GetProgressToken(); g != "t" { + t.Errorf("got %v, want `t`", g) + } + + // You can set a progress token to an int, int32 or int64. + p.SetProgressToken(int(1)) + p.SetProgressToken(int32(1)) + p.SetProgressToken(int64(1)) +} diff --git a/mcp/server.go b/mcp/server.go index 87007ebd..f71fe304 100644 --- a/mcp/server.go +++ b/mcp/server.go @@ -146,7 +146,6 @@ func (s *Server) addToolsErr(tools ...*ServerTool) error { st.inputResolved = r } - // TODO: uncomment when output schemas drop. // if st.Tool.OutputSchema != nil { // st.outputResolved, err := st.Tool.OutputSchema.Resolve(&jsonschema.ResolveOptions{ValidateDefaults: true}) // if err != nil { diff --git a/mcp/server_example_test.go b/mcp/server_example_test.go index 152eb5b1..b4af296c 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.ServerSession, params *mcp.CallToolParamsFor[SayHiParams]) (*mcp.CallToolResultFor[any], error) { return &mcp.CallToolResultFor[any]{ - Content: []*mcp.Content{ + Content: []*mcp.ContentBlock{ mcp.NewTextContent("Hi " + params.Arguments.Name), }, }, nil diff --git a/mcp/shared.go b/mcp/shared.go index 942e0d68..07ea5dff 100644 --- a/mcp/shared.go +++ b/mcp/shared.go @@ -242,51 +242,62 @@ func notifySessions[S Session](sessions []S, method string, params Params) { } } -type Meta struct { - Data map[string]any `json:",omitempty"` - // For params, the progress token can be nil, a string or an integer. - // It should be nil for results. - ProgressToken any `json:"progressToken,omitempty"` -} +type Meta map[string]any -type metaSansMethods Meta // avoid infinite recursion during marshaling +func (m Meta) GetMeta() map[string]any { return m } +func (m *Meta) SetMeta(x map[string]any) { *m = x } -func (m Meta) MarshalJSON() ([]byte, error) { - if p := m.ProgressToken; p != nil { - if k := reflect.ValueOf(p).Kind(); k != reflect.Int && k != reflect.String { - return nil, fmt.Errorf("bad type %T for Meta.ProgressToken: must be int or string", p) - } - } - // If ProgressToken is nil, accept Data["progressToken"]. We can't call marshalStructWithMap - // in that case because it will complain about duplicate fields. (We'd have to - // make it much smarter to avoid that; not worth it.) - if m.ProgressToken == nil && len(m.Data) > 0 { - return json.Marshal(m.Data) - } - return marshalStructWithMap((*metaSansMethods)(&m), "Data") +const progressTokenKey = "progressToken" + +func getProgressToken(p Params) any { + return p.GetMeta()[progressTokenKey] } -func (m *Meta) UnmarshalJSON(data []byte) error { - return unmarshalStructWithMap(data, (*metaSansMethods)(m), "Data") +func setProgressToken(p Params, pt any) { + switch pt.(type) { + // Support int32 and int64 for atomic.IntNN. + case int, int32, int64, string: + default: + panic(fmt.Sprintf("progress token %v is of type %[1]T, not int or string", pt)) + } + m := p.GetMeta() + if m == nil { + m = map[string]any{} + } + m[progressTokenKey] = pt } // Params is a parameter (input) type for an MCP call or notification. type Params interface { - // Returns a pointer to the params's Meta field. - GetMeta() *Meta + GetMeta() map[string]any + SetMeta(map[string]any) +} + +// RequestParams is a parameter (input) type for an MCP request. +type RequestParams interface { + Params + + // GetProgressToken returns the progress token from the params' Meta field, or nil + // if there is none. + GetProgressToken() any + + // SetProgressToken sets the given progress token into the params' Meta field. + // It panics if its argument is not an int or a string. + SetProgressToken(any) } // Result is a result of an MCP call. type Result interface { - // Returns a pointer to the result's Meta field. - GetMeta() *Meta + GetMeta() map[string]any + SetMeta(map[string]any) } // emptyResult is returned by methods that have no result, like ping. // Those methods cannot return nil, because jsonrpc2 cannot handle nils. type emptyResult struct{} -func (*emptyResult) GetMeta() *Meta { panic("should never be called") } +func (*emptyResult) GetMeta() map[string]any { panic("should never be called") } +func (*emptyResult) SetMeta(map[string]any) { panic("should never be called") } type listParams interface { // Returns a pointer to the param's Cursor field. diff --git a/mcp/shared_test.go b/mcp/shared_test.go index a2ed9b6f..5a1d5d02 100644 --- a/mcp/shared_test.go +++ b/mcp/shared_test.go @@ -9,67 +9,8 @@ import ( "encoding/json" "strings" "testing" - - "github.com/google/go-cmp/cmp" ) -func TestMetaMarshal(t *testing.T) { - // Verify that Meta values round-trip. - for _, meta := range []Meta{ - {Data: nil, ProgressToken: nil}, - {Data: nil, ProgressToken: "p"}, - {Data: map[string]any{"d": true}, ProgressToken: nil}, - {Data: map[string]any{"d": true}, ProgressToken: "p"}, - } { - got := roundTrip(t, meta) - if !cmp.Equal(got, meta) { - t.Errorf("\ngot %#v\nwant %#v", got, meta) - } - } - - // Check errors. - for _, tt := range []struct { - meta Meta - want string - }{ - { - Meta{Data: map[string]any{"progressToken": "p"}, ProgressToken: 1}, - "duplicate", - }, - { - Meta{ProgressToken: true}, - "bad type", - }, - } { - _, err := json.Marshal(tt.meta) - if err == nil || !strings.Contains(err.Error(), tt.want) { - t.Errorf("%+v: got %v, want error containing %q", tt.meta, err, tt.want) - } - } - - // Accept progressToken in map if the field is nil. - // It will unmarshal by populating ProgressToken. - meta := Meta{Data: map[string]any{"progressToken": "p"}} - got := roundTrip(t, meta) - want := Meta{ProgressToken: "p"} - if !cmp.Equal(got, want) { - t.Errorf("got %+v, want %+v", got, want) - } -} - -func roundTrip[T any](t *testing.T, v T) T { - t.Helper() - bytes, err := json.Marshal(v) - if err != nil { - t.Fatal(err) - } - var res T - if err := json.Unmarshal(bytes, &res); err != nil { - t.Fatal(err) - } - return res -} - // TODO(jba): this shouldn't be in this file, but tool_test.go doesn't have access to unexported symbols. func TestNewServerToolValidate(t *testing.T) { // Check that the tool returned from NewServerTool properly validates its input schema. diff --git a/mcp/sse_example_test.go b/mcp/sse_example_test.go index e7a8eb59..d0a9f98d 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.ServerSession, params *mcp.CallToolParamsFor[AddParams]) (*mcp.CallToolResultFor[any], error) { return &mcp.CallToolResultFor[any]{ - Content: []*mcp.Content{mcp.NewTextContent(fmt.Sprintf("%d", params.Arguments.X+params.Arguments.Y))}, + Content: []*mcp.ContentBlock{mcp.NewTextContent(fmt.Sprintf("%d", params.Arguments.X+params.Arguments.Y))}, }, nil } diff --git a/mcp/sse_test.go b/mcp/sse_test.go index 9a1acd53..8c3dfebc 100644 --- a/mcp/sse_test.go +++ b/mcp/sse_test.go @@ -53,7 +53,7 @@ func TestSSEServer(t *testing.T) { t.Fatal(err) } wantHi := &CallToolResult{ - Content: []*Content{{Type: "text", Text: "hi user"}}, + Content: []*ContentBlock{{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/streamable_test.go b/mcp/streamable_test.go index 26ca9289..26aa064b 100644 --- a/mcp/streamable_test.go +++ b/mcp/streamable_test.go @@ -82,7 +82,7 @@ func TestStreamableTransports(t *testing.T) { // 5. Verify that the correct response is received. want := &CallToolResult{ - Content: []*Content{{Type: "text", Text: "hi streamy"}}, + Content: []*ContentBlock{{Type: "text", Text: "hi streamy"}}, } if diff := cmp.Diff(want, got); diff != "" { t.Errorf("CallTool() returned unexpected content (-want +got):\n%s", diff) @@ -583,4 +583,3 @@ func TestEventID(t *testing.T) { }) } } - diff --git a/mcp/tool.go b/mcp/tool.go index a8b55572..1f6da53e 100644 --- a/mcp/tool.go +++ b/mcp/tool.go @@ -132,7 +132,7 @@ func newRawHandler(st *ServerTool) rawToolHandler { // rather than returned as jsonrpc2 server errors. if err != nil { return &CallToolResult{ - Content: []*Content{NewTextContent(err.Error())}, + Content: []*ContentBlock{NewTextContent(err.Error())}, IsError: true, }, nil }