From 0cdaf48b5de0e85bb845f8aa900d76f87bc4156f Mon Sep 17 00:00:00 2001 From: Anurag Pant Date: Tue, 1 Jul 2025 11:27:07 -0700 Subject: [PATCH 1/2] feat: Add _meta support to MCPSchema --- .../modelcontextprotocol/spec/McpSchema.java | 1413 ++++++++++------- .../spec/McpSchemaTests.java | 103 +- 2 files changed, 895 insertions(+), 621 deletions(-) diff --git a/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java b/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java index 9be585ce..2ac88e57 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java +++ b/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java @@ -20,6 +20,14 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo.As; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; + +import io.modelcontextprotocol.spec.McpSchema.CompleteRequest; +import io.modelcontextprotocol.spec.McpSchema.CompleteResult; +import io.modelcontextprotocol.spec.McpSchema.ListRootsResult; +import io.modelcontextprotocol.spec.McpSchema.LoggingMessageNotification; +import io.modelcontextprotocol.spec.McpSchema.ResourceLink; +import io.modelcontextprotocol.spec.McpSchema.ResourcesUpdatedNotification; +import io.modelcontextprotocol.spec.McpSchema.TextContent; import io.modelcontextprotocol.util.Assert; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,6 +40,7 @@ * * @author Christian Tzolov * @author Luca Chang + * @author Anurag Pant */ public final class McpSchema { @@ -141,8 +150,9 @@ public static final class ErrorCodes { } - public sealed interface Request permits InitializeRequest, CallToolRequest, CreateMessageRequest, ElicitRequest, - CompleteRequest, GetPromptRequest, PaginatedRequest, ReadResourceRequest { + public sealed interface Request + permits InitializeRequest, CallToolRequest, CreateMessageRequest, ElicitRequest, CompleteRequest, + GetPromptRequest, ReadResourceRequest, SubscribeRequest, UnsubscribeRequest, PaginatedRequest { Map meta(); @@ -155,6 +165,21 @@ default String progressToken() { } + public sealed interface Result permits InitializeResult, ListResourcesResult, ListResourceTemplatesResult, + ReadResourceResult, ListPromptsResult, GetPromptResult, ListToolsResult, CallToolResult, + CreateMessageResult, ElicitResult, CompleteResult, ListRootsResult { + + Map meta(); + + } + + public sealed interface Notification + permits ProgressNotification, LoggingMessageNotification, ResourcesUpdatedNotification { + + Map meta(); + + } + private static final TypeReference> MAP_TYPE_REF = new TypeReference<>() { }; @@ -200,7 +225,6 @@ public sealed interface JSONRPCMessage permits JSONRPCRequest, JSONRPCNotificati @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) - // TODO: batching support // @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) public record JSONRPCRequest( // @formatter:off @JsonProperty("jsonrpc") String jsonrpc, @@ -244,24 +268,31 @@ public record JSONRPCError( @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record InitializeRequest( // @formatter:off - @JsonProperty("protocolVersion") String protocolVersion, - @JsonProperty("capabilities") ClientCapabilities capabilities, - @JsonProperty("clientInfo") Implementation clientInfo, - @JsonProperty("_meta") Map meta) implements Request { - - public InitializeRequest(String protocolVersion, ClientCapabilities capabilities, Implementation clientInfo) { - this(protocolVersion, capabilities, clientInfo, null); - } - } // @formatter:on + @JsonProperty("protocolVersion") String protocolVersion, + @JsonProperty("capabilities") ClientCapabilities capabilities, + @JsonProperty("clientInfo") Implementation clientInfo, + @JsonProperty("_meta") Map meta) implements Request { + + public InitializeRequest(String protocolVersion, ClientCapabilities capabilities, + Implementation clientInfo) { + this(protocolVersion, capabilities, clientInfo, null); + } + } // @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record InitializeResult( // @formatter:off - @JsonProperty("protocolVersion") String protocolVersion, - @JsonProperty("capabilities") ServerCapabilities capabilities, - @JsonProperty("serverInfo") Implementation serverInfo, - @JsonProperty("instructions") String instructions) { - } // @formatter:on + @JsonProperty("protocolVersion") String protocolVersion, + @JsonProperty("capabilities") ServerCapabilities capabilities, + @JsonProperty("serverInfo") Implementation serverInfo, + @JsonProperty("instructions") String instructions, + @JsonProperty("_meta") Mapmeta) implements Result { + + public InitializeResult(String protocolVersion, ServerCapabilities capabilities, + Implementation serverInfo, String instructions) { + this(protocolVersion, capabilities, serverInfo, instructions, null); + } + } // @formatter:on /** * Clients can implement additional features to enrich connected MCP servers with @@ -530,80 +561,87 @@ public interface ResourceContent { * sizes and estimate context window usage. * @param annotations Optional annotations for the client. The client can use * annotations to inform how objects are used or displayed. + * @param meta Additional metadata related to this resource. */ @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record Resource( // @formatter:off - @JsonProperty("uri") String uri, - @JsonProperty("name") String name, - @JsonProperty("description") String description, - @JsonProperty("mimeType") String mimeType, - @JsonProperty("size") Long size, - @JsonProperty("annotations") Annotations annotations) implements Annotated, ResourceContent { + @JsonProperty("uri") String uri, + @JsonProperty("name") String name, + @JsonProperty("description") String description, + @JsonProperty("mimeType") String mimeType, + @JsonProperty("size") Long size, + @JsonProperty("annotations") Annotations annotations, + @JsonProperty("_meta") Map meta) implements Annotated { - /** - * @deprecated Only exists for backwards-compatibility purposes. Use - * {@link Resource#builder()} instead. - */ - @Deprecated - public Resource(String uri, String name, String description, String mimeType, Annotations annotations) { - this(uri, name, description, mimeType, null, annotations); - } + /** + * @deprecated Only exists for backwards-compatibility purposes. Use + * {@link Resource#builder()} instead. + */ + @Deprecated + public Resource(String uri, String name, String description, String mimeType, Annotations annotations) { + this(uri, name, description, mimeType, null, annotations, null); + } - public static Builder builder() { - return new Builder(); - } + public static Builder builder() { + return new Builder(); + } - public static class Builder { - private String uri; - private String name; - private String description; - private String mimeType; - private Long size; - private Annotations annotations; - - public Builder uri(String uri) { - this.uri = uri; - return this; - } + public static class Builder { + private String uri; + private String name; + private String description; + private String mimeType; + private Long size; + private Annotations annotations; + private Map meta; - public Builder name(String name) { - this.name = name; - return this; - } + public Builder uri(String uri) { + this.uri = uri; + return this; + } - public Builder description(String description) { - this.description = description; - return this; - } + public Builder name(String name) { + this.name = name; + return this; + } - public Builder mimeType(String mimeType) { - this.mimeType = mimeType; - return this; - } + public Builder description(String description) { + this.description = description; + return this; + } - public Builder size(Long size) { - this.size = size; - return this; - } + public Builder mimeType(String mimeType) { + this.mimeType = mimeType; + return this; + } - public Builder annotations(Annotations annotations) { - this.annotations = annotations; - return this; - } + public Builder size(Long size) { + this.size = size; + return this; + } - public Resource build() { - Assert.hasText(uri, "uri must not be empty"); - Assert.hasText(name, "name must not be empty"); + public Builder annotations(Annotations annotations) { + this.annotations = annotations; + return this; + } - return new Resource(uri, name, description, mimeType, size, annotations); - } - } - } // @formatter:on + public Builder meta(Map meta) { + this.meta = meta; + return this; + } + + public Resource build() { + Assert.hasText(uri, "uri must not be empty"); + Assert.hasText(name, "name must not be empty"); + + return new Resource(uri, name, description, mimeType, size, annotations, meta); + } + } + } // @formatter:on /** * Resource templates allow servers to expose parameterized resources using URI - * templates. * * @param uriTemplate A URI template that can be used to generate URIs for this * resource. @@ -616,47 +654,72 @@ public Resource build() { * @param annotations Optional annotations for the client. The client can use * annotations to inform how objects are used or displayed. * @see RFC 6570 + * @param meta Additional metadata related to this resource template. + * "https://modelcontextprotocol.io/specification/2025-06-18/basic/index#meta">Specification + * for notes on _meta usage + * */ @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record ResourceTemplate( // @formatter:off - @JsonProperty("uriTemplate") String uriTemplate, - @JsonProperty("name") String name, - @JsonProperty("description") String description, - @JsonProperty("mimeType") String mimeType, - @JsonProperty("annotations") Annotations annotations) implements Annotated { - } // @formatter:on + @JsonProperty("uriTemplate") String uriTemplate, + @JsonProperty("name") String name, + @JsonProperty("description") String description, + @JsonProperty("mimeType") String mimeType, + @JsonProperty("annotations") Annotations annotations, + @JsonProperty("_meta") Map meta) implements Annotated { + + public ResourceTemplate(String uri, String name, String description, String mimeType, Annotations annotations) { + this(uri, name, description, mimeType, annotations, null); + } + } // @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record ListResourcesResult( // @formatter:off - @JsonProperty("resources") List resources, - @JsonProperty("nextCursor") String nextCursor) { - } // @formatter:on + @JsonProperty("resources") List resources, + @JsonProperty("nextCursor") String nextCursor, + @JsonProperty("_meta") Map meta) implements Result { + + public ListResourcesResult(List resources, String nextCursor) { + this(resources, nextCursor, null); + } + } // @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record ListResourceTemplatesResult( // @formatter:off - @JsonProperty("resourceTemplates") List resourceTemplates, - @JsonProperty("nextCursor") String nextCursor) { - } // @formatter:on + @JsonProperty("resourceTemplates") List resourceTemplates, + @JsonProperty("nextCursor") String nextCursor, + @JsonProperty("_meta") Map meta) implements Result { + + public ListResourceTemplatesResult(List resourceTemplates, + String nextCursor) { + this(resourceTemplates, nextCursor, null); + } + } // @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record ReadResourceRequest( // @formatter:off - @JsonProperty("uri") String uri, - @JsonProperty("_meta") Map meta) implements Request { + @JsonProperty("uri") String uri, + @JsonProperty("_meta") Map meta) implements Request { - public ReadResourceRequest(String uri) { - this(uri, null); - } - } // @formatter:on + public ReadResourceRequest(String uri) { + this(uri, null); + } + } // @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record ReadResourceResult( // @formatter:off - @JsonProperty("contents") List contents){ - } // @formatter:on + @JsonProperty("contents") List contents, + @JsonProperty("_meta") Map meta) implements Result { + + public ReadResourceResult(List contents) { + this(contents, null); + } + } // @formatter:on /** * Sent from the client to request resources/updated notifications from the server @@ -664,18 +727,29 @@ public record ReadResourceResult( // @formatter:off * * @param uri the URI of the resource to subscribe to. The URI can use any protocol; * it is up to the server how to interpret it. + * @param meta Additional metadata related to this prompt. */ @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record SubscribeRequest( // @formatter:off - @JsonProperty("uri") String uri){ - } // @formatter:on + @JsonProperty("uri") String uri, + @JsonProperty("_meta") Map meta) implements Request { + + public SubscribeRequest(String uri) { + this(uri, null); + } + } // @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record UnsubscribeRequest( // @formatter:off - @JsonProperty("uri") String uri){ - } // @formatter:on + @JsonProperty("uri") String uri, + @JsonProperty("_meta") Map meta) implements Request { + + public UnsubscribeRequest(String uri) { + this(uri, null); + } + } // @formatter:on /** * The contents of a specific resource or sub-resource. @@ -697,6 +771,14 @@ public sealed interface ResourceContents permits TextResourceContents, BlobResou */ String mimeType(); + /** + * @see Specification + * for notes on _meta usage + * @return additional metadata related to this resource. + */ + Map meta(); + } /** @@ -706,14 +788,20 @@ public sealed interface ResourceContents permits TextResourceContents, BlobResou * @param mimeType the MIME type of this resource. * @param text the text of the resource. This must only be set if the resource can * actually be represented as text (not binary data). + * @param meta Additional metadata related to this resource content. */ @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record TextResourceContents( // @formatter:off - @JsonProperty("uri") String uri, - @JsonProperty("mimeType") String mimeType, - @JsonProperty("text") String text) implements ResourceContents { - } // @formatter:on + @JsonProperty("uri") String uri, + @JsonProperty("mimeType") String mimeType, + @JsonProperty("text") String text, + @JsonProperty("_meta") Map meta) implements ResourceContents { + + public TextResourceContents(String uri, String mimeType, String text) { + this(uri, mimeType, text, null); + } + } // @formatter:on /** * Binary contents of a resource. @@ -723,14 +811,20 @@ public record TextResourceContents( // @formatter:off * @param blob a base64-encoded string representing the binary data of the resource. * This must only be set if the resource can actually be represented as binary data * (not text). + * @param meta Additional metadata related to this resource content. */ @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record BlobResourceContents( // @formatter:off - @JsonProperty("uri") String uri, - @JsonProperty("mimeType") String mimeType, - @JsonProperty("blob") String blob) implements ResourceContents { - } // @formatter:on + @JsonProperty("uri") String uri, + @JsonProperty("mimeType") String mimeType, + @JsonProperty("blob") String blob, + @JsonProperty("_meta") Map meta) implements ResourceContents { + + public BlobResourceContents(String uri, String mimeType, String blob) { + this(uri, mimeType, blob, null); + } + } // @formatter:on // --------------------------- // Prompt Interfaces @@ -741,14 +835,20 @@ public record BlobResourceContents( // @formatter:off * @param name The name of the prompt or prompt template. * @param description An optional description of what this prompt provides. * @param arguments A list of arguments to use for templating the prompt. + * @param meta Additional metadata related to this prompt. */ @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record Prompt( // @formatter:off - @JsonProperty("name") String name, - @JsonProperty("description") String description, - @JsonProperty("arguments") List arguments) { - } // @formatter:on + @JsonProperty("name") String name, + @JsonProperty("description") String description, + @JsonProperty("arguments") List arguments, + @JsonProperty("_meta") Map meta) { + + public Prompt(String name, String description, List arguments) { + this(name, description, arguments, null); + } + } // @formatter:on /** * Describes an argument that a prompt can accept. @@ -787,44 +887,57 @@ public record PromptMessage( // @formatter:off * @param prompts A list of prompts that the server provides. * @param nextCursor An optional cursor for pagination. If present, indicates there * are more prompts available. + * @param meta Additional metadata related to this result. */ @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record ListPromptsResult( // @formatter:off - @JsonProperty("prompts") List prompts, - @JsonProperty("nextCursor") String nextCursor) { - }// @formatter:on + @JsonProperty("prompts") List prompts, + @JsonProperty("nextCursor") String nextCursor, + @JsonProperty("_meta") Map meta) implements Result { + + public ListPromptsResult(List prompts, String nextCursor) { + this(prompts, nextCursor, null); + } + }// @formatter:on /** * Used by the client to get a prompt provided by the server. * * @param name The name of the prompt or prompt template. * @param arguments Arguments to use for templating the prompt. + * @param meta Additional metadata related to this request. */ @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record GetPromptRequest(// @formatter:off - @JsonProperty("name") String name, - @JsonProperty("arguments") Map arguments, - @JsonProperty("_meta") Map meta) implements Request { + @JsonProperty("name") String name, + @JsonProperty("arguments") Map arguments, + @JsonProperty("_meta") Map meta) implements Request { - public GetPromptRequest(String name, Map arguments) { - this(name, arguments, null); - } - }// @formatter:off - - /** - * The server's response to a prompts/get request from the client. - * - * @param description An optional description for the prompt. - * @param messages A list of messages to display as part of the prompt. - */ - @JsonInclude(JsonInclude.Include.NON_ABSENT) - @JsonIgnoreProperties(ignoreUnknown = true) - public record GetPromptResult( // @formatter:off - @JsonProperty("description") String description, - @JsonProperty("messages") List messages) { - } // @formatter:on + public GetPromptRequest(String name, Map arguments) { + this(name, arguments, null); + } + }// @formatter:off + + /** + * The server's response to a prompts/get request from the client. + * + * @param description An optional description for the prompt. + * @param messages A list of messages to display as part of the prompt. + * @param meta Additional metadata related to this result. + */ + @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) + public record GetPromptResult( // @formatter:off + @JsonProperty("description") String description, + @JsonProperty("messages") List messages, + @JsonProperty("_meta") Map meta) implements Result { + + public GetPromptResult(String description, List messages) { + this(description, messages, null); + } + } // @formatter:on // --------------------------- // Tool Interfaces @@ -835,13 +948,19 @@ public record GetPromptResult( // @formatter:off * @param tools A list of tools that the server provides. * @param nextCursor An optional cursor for pagination. If present, indicates there * are more tools available. + * @param meta Additional metadata related to this result. */ @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record ListToolsResult( // @formatter:off - @JsonProperty("tools") List tools, - @JsonProperty("nextCursor") String nextCursor) { - }// @formatter:on + @JsonProperty("tools") List tools, + @JsonProperty("nextCursor") String nextCursor, + @JsonProperty("_meta") Map meta) implements Result { + + public ListToolsResult(List tools, String nextCursor) { + this(tools, nextCursor, null); + } + }// @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) @@ -852,7 +971,7 @@ public record JsonSchema( // @formatter:off @JsonProperty("additionalProperties") Boolean additionalProperties, @JsonProperty("$defs") Map defs, @JsonProperty("definitions") Map definitions) { - } // @formatter:on + }// @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) @@ -863,7 +982,7 @@ public record ToolAnnotations( // @formatter:off @JsonProperty("idempotentHint") Boolean idempotentHint, @JsonProperty("openWorldHint") Boolean openWorldHint, @JsonProperty("returnDirect") Boolean returnDirect) { - } // @formatter:on + }// @formatter:on /** * Represents a tool that the server provides. Tools enable servers to expose @@ -882,20 +1001,24 @@ public record ToolAnnotations( // @formatter:off @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record Tool( // @formatter:off - @JsonProperty("name") String name, - @JsonProperty("description") String description, - @JsonProperty("inputSchema") JsonSchema inputSchema, - @JsonProperty("annotations") ToolAnnotations annotations) { - - public Tool(String name, String description, String schema) { - this(name, description, parseSchema(schema), null); - } + @JsonProperty("name") String name, + @JsonProperty("description") String description, + @JsonProperty("inputSchema") JsonSchema inputSchema, + @JsonProperty("annotations") ToolAnnotations annotations, + @JsonProperty("_meta") Map meta) { + + public Tool(String name, String description, JsonSchema schema, ToolAnnotations annotations) { + this(name, description, schema, annotations, null); + } - public Tool(String name, String description, String schema, ToolAnnotations annotations) { - this(name, description, parseSchema(schema), annotations); - } + public Tool(String name, String description, String schema) { + this(name, description, parseSchema(schema), null, null); + } - } // @formatter:on + public Tool(String name, String description, String schema, ToolAnnotations annotations) { + this(name, description, parseSchema(schema), annotations, null); + } + } // @formatter:on private static JsonSchema parseSchema(String schema) { try { @@ -913,185 +1036,204 @@ private static JsonSchema parseSchema(String schema) { * tools/list. * @param arguments Arguments to pass to the tool. These must conform to the tool's * input schema. + * @param meta Additional metadata related to this request. */ @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record CallToolRequest(// @formatter:off - @JsonProperty("name") String name, - @JsonProperty("arguments") Map arguments, - @JsonProperty("_meta") Map meta) implements Request { + @JsonProperty("name") String name, + @JsonProperty("arguments") Map arguments, + @JsonProperty("_meta") Map meta) implements Request { - public CallToolRequest(String name, String jsonArguments) { - this(name, parseJsonArguments(jsonArguments), null); - } - public CallToolRequest(String name, Map arguments) { - this(name, arguments, null); - } + public CallToolRequest(String name, String jsonArguments) { + this(name, parseJsonArguments(jsonArguments), null); + } - private static Map parseJsonArguments(String jsonArguments) { - try { - return OBJECT_MAPPER.readValue(jsonArguments, MAP_TYPE_REF); - } - catch (IOException e) { - throw new IllegalArgumentException("Invalid arguments: " + jsonArguments, e); - } - } + public CallToolRequest(String name, Map arguments) { + this(name, arguments, null); + } - public static Builder builder() { - return new Builder(); - } + private static Map parseJsonArguments(String jsonArguments) { + try { + return OBJECT_MAPPER.readValue(jsonArguments, MAP_TYPE_REF); + } + catch (IOException e) { + throw new IllegalArgumentException("Invalid arguments: " + jsonArguments, e); + } + } - public static class Builder { - private String name; - private Map arguments; - private Map meta; + public static Builder builder() { + return new Builder(); + } - public Builder name(String name) { - this.name = name; - return this; - } + public static class Builder { + private String name; + private Map arguments; + private Map meta; - public Builder arguments(Map arguments) { - this.arguments = arguments; - return this; - } + public Builder name(String name) { + this.name = name; + return this; + } - public Builder arguments(String jsonArguments) { - this.arguments = parseJsonArguments(jsonArguments); - return this; - } + public Builder arguments(Map arguments) { + this.arguments = arguments; + return this; + } - public Builder meta(Map meta) { - this.meta = meta; - return this; - } + public Builder arguments(String jsonArguments) { + this.arguments = parseJsonArguments(jsonArguments); + return this; + } - public Builder progressToken(String progressToken) { - if (this.meta == null) { - this.meta = new HashMap<>(); - } - this.meta.put("progressToken", progressToken); - return this; - } + public Builder meta(Map meta) { + this.meta = meta; + return this; + } - public CallToolRequest build() { - Assert.hasText(name, "name must not be empty"); - return new CallToolRequest(name, arguments, meta); - } - } - }// @formatter:off - - /** - * The server's response to a tools/call request from the client. - * - * @param content A list of content items representing the tool's output. Each item can be text, an image, - * or an embedded resource. - * @param isError If true, indicates that the tool execution failed and the content contains error information. - * If false or absent, indicates successful execution. - */ - @JsonInclude(JsonInclude.Include.NON_ABSENT) - @JsonIgnoreProperties(ignoreUnknown = true) - public record CallToolResult( // @formatter:off - @JsonProperty("content") List content, - @JsonProperty("isError") Boolean isError) { + public Builder progressToken(String progressToken) { + if (this.meta == null) { + this.meta = new HashMap<>(); + } + this.meta.put("progressToken", progressToken); + return this; + } - /** - * Creates a new instance of {@link CallToolResult} with a string containing the - * tool result. - * - * @param content The content of the tool result. This will be mapped to a one-sized list - * with a {@link TextContent} element. - * @param isError If true, indicates that the tool execution failed and the content contains error information. - * If false or absent, indicates successful execution. - */ - public CallToolResult(String content, Boolean isError) { - this(List.of(new TextContent(content)), isError); - } + public CallToolRequest build() { + Assert.hasText(name, "name must not be empty"); + return new CallToolRequest(name, arguments, meta); + } + } + }// @formatter:off - /** - * Creates a builder for {@link CallToolResult}. - * @return a new builder instance - */ - public static Builder builder() { - return new Builder(); - } + /** + * The server's response to a tools/call request from the client. + * + * @param content A list of content items representing the tool's output. Each item can be text, an image, + * or an embedded resource. + * @param isError If true, indicates that the tool execution failed and the content contains error information. + * If false or absent, indicates successful execution. + * @param meta Additional metadata related to this result. + */ + @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) + public record CallToolResult( // @formatter:off + @JsonProperty("content") List content, + @JsonProperty("isError") Boolean isError, + @JsonProperty("_meta") Map meta) implements Result { - /** - * Builder for {@link CallToolResult}. - */ - public static class Builder { - private List content = new ArrayList<>(); - private Boolean isError; - - /** - * Sets the content list for the tool result. - * @param content the content list - * @return this builder - */ - public Builder content(List content) { - Assert.notNull(content, "content must not be null"); - this.content = content; - return this; - } + /** + * Creates a new instance of {@link CallToolResult} with a string containing the + * tool result. + * + * @param content The content of the tool result. This will be mapped to a one-sized list + * with a {@link TextContent} element. + * @param isError If true, indicates that the tool execution failed and the content contains error information. + * If false or absent, indicates successful execution. + * @param meta Additional metadata related to this result. + */ + public CallToolResult(String content, Boolean isError) { + this(List.of(new TextContent(content)), isError, null); + } - /** - * Sets the text content for the tool result. - * @param textContent the text content - * @return this builder - */ - public Builder textContent(List textContent) { - Assert.notNull(textContent, "textContent must not be null"); - textContent.stream() - .map(TextContent::new) - .forEach(this.content::add); - return this; - } + public CallToolResult(List content, Boolean isError) { + this(content, isError, null); + } - /** - * Adds a content item to the tool result. - * @param contentItem the content item to add - * @return this builder - */ - public Builder addContent(Content contentItem) { - Assert.notNull(contentItem, "contentItem must not be null"); - if (this.content == null) { - this.content = new ArrayList<>(); - } - this.content.add(contentItem); - return this; - } + /** + * Creates a builder for {@link CallToolResult}. + * @return a new builder instance + */ + public static Builder builder() { + return new Builder(); + } - /** - * Adds a text content item to the tool result. - * @param text the text content - * @return this builder - */ - public Builder addTextContent(String text) { - Assert.notNull(text, "text must not be null"); - return addContent(new TextContent(text)); - } + /** + * Builder for {@link CallToolResult}. + */ + public static class Builder { + private List content = new ArrayList<>(); + private Boolean isError; + private Map meta; + + /** + * Sets the content list for the tool result. + * @param content the content list + * @return this builder + */ + public Builder content(List content) { + Assert.notNull(content, "content must not be null"); + this.content = content; + return this; + } - /** - * Sets whether the tool execution resulted in an error. - * @param isError true if the tool execution failed, false otherwise - * @return this builder - */ - public Builder isError(Boolean isError) { - Assert.notNull(isError, "isError must not be null"); - this.isError = isError; - return this; - } + /** + * Sets the text content for the tool result. + * @param textContent the text content + * @return this builder + */ + public Builder textContent(List textContent) { + Assert.notNull(textContent, "textContent must not be null"); + textContent.stream() + .map(TextContent::new) + .forEach(this.content::add); + return this; + } - /** - * Builds a new {@link CallToolResult} instance. - * @return a new CallToolResult instance - */ - public CallToolResult build() { - return new CallToolResult(content, isError); - } - } + /** + * Adds a content item to the tool result. + * @param contentItem the content item to add + * @return this builder + */ + public Builder addContent(Content contentItem) { + Assert.notNull(contentItem, "contentItem must not be null"); + if (this.content == null) { + this.content = new ArrayList<>(); + } + this.content.add(contentItem); + return this; + } - } // @formatter:on + /** + * Adds a text content item to the tool result. + * @param text the text content + * @return this builder + */ + public Builder addTextContent(String text) { + Assert.notNull(text, "text must not be null"); + return addContent(new TextContent(text)); + } + + /** + * Sets whether the tool execution resulted in an error. + * @param isError true if the tool execution failed, false otherwise + * @return this builder + */ + public Builder isError(Boolean isError) { + Assert.notNull(isError, "isError must not be null"); + this.isError = isError; + return this; + } + + /** + * Sets the metadata for the tool result. + * @param meta metadata + * @return this builder + */ + public Builder meta(Map meta) { + this.meta = meta; + return this; + } + + /** + * Builds a new {@link CallToolResult} instance. + * @return a new CallToolResult instance + */ + public CallToolResult build() { + return new CallToolResult(content, isError, meta); + } + } + }// @formatter:on // --------------------------- // Sampling Interfaces @@ -1146,7 +1288,7 @@ public ModelPreferences build() { return new ModelPreferences(hints, costPriority, speedPriority, intelligencePriority); } } -} // @formatter:on +}// @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) @@ -1161,182 +1303,194 @@ public static ModelHint of(String name) { public record SamplingMessage(// @formatter:off @JsonProperty("role") Role role, @JsonProperty("content") Content content) { - } // @formatter:on + }// @formatter:on // Sampling and Message Creation @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record CreateMessageRequest(// @formatter:off - @JsonProperty("messages") List messages, - @JsonProperty("modelPreferences") ModelPreferences modelPreferences, - @JsonProperty("systemPrompt") String systemPrompt, - @JsonProperty("includeContext") ContextInclusionStrategy includeContext, - @JsonProperty("temperature") Double temperature, - @JsonProperty("maxTokens") int maxTokens, - @JsonProperty("stopSequences") List stopSequences, - @JsonProperty("metadata") Map metadata, - @JsonProperty("_meta") Map meta) implements Request { + @JsonProperty("messages") List messages, + @JsonProperty("modelPreferences") ModelPreferences modelPreferences, + @JsonProperty("systemPrompt") String systemPrompt, + @JsonProperty("includeContext") ContextInclusionStrategy includeContext, + @JsonProperty("temperature") Double temperature, + @JsonProperty("maxTokens") int maxTokens, + @JsonProperty("stopSequences") List stopSequences, + @JsonProperty("metadata") Map metadata, + @JsonProperty("_meta") Map meta) implements Request { - // backwards compatibility constructor - public CreateMessageRequest(List messages, ModelPreferences modelPreferences, - String systemPrompt, ContextInclusionStrategy includeContext, - Double temperature, int maxTokens, List stopSequences, - Map metadata) { - this(messages, modelPreferences, systemPrompt, includeContext, temperature, maxTokens, - stopSequences, metadata, null); - } + // backwards compatibility constructor + public CreateMessageRequest(List messages, ModelPreferences modelPreferences, + String systemPrompt, ContextInclusionStrategy includeContext, + Double temperature, int maxTokens, List stopSequences, + Map metadata) { + this(messages, modelPreferences, systemPrompt, includeContext, temperature, maxTokens, + stopSequences, metadata, null); + } - public enum ContextInclusionStrategy { - @JsonProperty("none") NONE, - @JsonProperty("thisServer") THIS_SERVER, - @JsonProperty("allServers") ALL_SERVERS - } + public enum ContextInclusionStrategy { + @JsonProperty("none") NONE, + @JsonProperty("thisServer") THIS_SERVER, + @JsonProperty("allServers") ALL_SERVERS + } - public static Builder builder() { - return new Builder(); - } + public static Builder builder() { + return new Builder(); + } - public static class Builder { - private List messages; - private ModelPreferences modelPreferences; - private String systemPrompt; - private ContextInclusionStrategy includeContext; - private Double temperature; - private int maxTokens; - private List stopSequences; - private Map metadata; - private Map meta; - - public Builder messages(List messages) { - this.messages = messages; - return this; - } + public static class Builder { + private List messages; + private ModelPreferences modelPreferences; + private String systemPrompt; + private ContextInclusionStrategy includeContext; + private Double temperature; + private int maxTokens; + private List stopSequences; + private Map metadata; + private Map meta; + + public Builder messages(List messages) { + this.messages = messages; + return this; + } - public Builder modelPreferences(ModelPreferences modelPreferences) { - this.modelPreferences = modelPreferences; - return this; - } + public Builder modelPreferences(ModelPreferences modelPreferences) { + this.modelPreferences = modelPreferences; + return this; + } - public Builder systemPrompt(String systemPrompt) { - this.systemPrompt = systemPrompt; - return this; - } + public Builder systemPrompt(String systemPrompt) { + this.systemPrompt = systemPrompt; + return this; + } - public Builder includeContext(ContextInclusionStrategy includeContext) { - this.includeContext = includeContext; - return this; - } + public Builder includeContext(ContextInclusionStrategy includeContext) { + this.includeContext = includeContext; + return this; + } - public Builder temperature(Double temperature) { - this.temperature = temperature; - return this; - } + public Builder temperature(Double temperature) { + this.temperature = temperature; + return this; + } - public Builder maxTokens(int maxTokens) { - this.maxTokens = maxTokens; - return this; - } + public Builder maxTokens(int maxTokens) { + this.maxTokens = maxTokens; + return this; + } - public Builder stopSequences(List stopSequences) { - this.stopSequences = stopSequences; - return this; - } + public Builder stopSequences(List stopSequences) { + this.stopSequences = stopSequences; + return this; + } - public Builder metadata(Map metadata) { - this.metadata = metadata; - return this; - } + public Builder metadata(Map metadata) { + this.metadata = metadata; + return this; + } - public Builder meta(Map meta) { - this.meta = meta; - return this; - } - - public Builder progressToken(String progressToken) { - if (this.meta == null) { - this.meta = new HashMap<>(); - } - this.meta.put("progressToken", progressToken); - return this; - } - - public CreateMessageRequest build() { - return new CreateMessageRequest(messages, modelPreferences, systemPrompt, - includeContext, temperature, maxTokens, stopSequences, metadata, meta); - } - } - }// @formatter:on + public Builder meta(Map meta) { + this.meta = meta; + return this; + } + + public Builder progressToken(String progressToken) { + if (this.meta == null) { + this.meta = new HashMap<>(); + } + this.meta.put("progressToken", progressToken); + return this; + } + + public CreateMessageRequest build() { + return new CreateMessageRequest(messages, modelPreferences, systemPrompt, + includeContext, temperature, maxTokens, stopSequences, metadata, meta); + } + } + }// @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record CreateMessageResult(// @formatter:off - @JsonProperty("role") Role role, - @JsonProperty("content") Content content, - @JsonProperty("model") String model, - @JsonProperty("stopReason") StopReason stopReason) { - - public enum StopReason { - @JsonProperty("endTurn") END_TURN("endTurn"), - @JsonProperty("stopSequence") STOP_SEQUENCE("stopSequence"), - @JsonProperty("maxTokens") MAX_TOKENS("maxTokens"), - @JsonProperty("unknown") UNKNOWN("unknown"); - - private final String value; + @JsonProperty("role") Role role, + @JsonProperty("content") Content content, + @JsonProperty("model") String model, + @JsonProperty("stopReason") StopReason stopReason, + @JsonProperty("_meta") Map meta) implements Result { + + public enum StopReason { + @JsonProperty("endTurn") END_TURN("endTurn"), + @JsonProperty("stopSequence") STOP_SEQUENCE("stopSequence"), + @JsonProperty("maxTokens") MAX_TOKENS("maxTokens"), + @JsonProperty("unknown") UNKNOWN("unknown"); + + private final String value; + + StopReason(String value) { + this.value = value; + } - StopReason(String value) { - this.value = value; - } + @JsonCreator + private static StopReason of(String value) { + return Arrays.stream(StopReason.values()) + .filter(stopReason -> stopReason.value.equals(value)) + .findFirst() + .orElse(StopReason.UNKNOWN); + } + } - @JsonCreator - private static StopReason of(String value) { - return Arrays.stream(StopReason.values()) - .filter(stopReason -> stopReason.value.equals(value)) - .findFirst() - .orElse(StopReason.UNKNOWN); - } - } + public CreateMessageResult(Role role, Content content, String model, + StopReason stopReason) { + this(role, content, model, stopReason, null); + } - public static Builder builder() { - return new Builder(); - } + public static Builder builder() { + return new Builder(); + } - public static class Builder { - private Role role = Role.ASSISTANT; - private Content content; - private String model; - private StopReason stopReason = StopReason.END_TURN; + public static class Builder { + private Role role = Role.ASSISTANT; + private Content content; + private String model; + private StopReason stopReason = StopReason.END_TURN; + private Map meta; + + public Builder role(Role role) { + this.role = role; + return this; + } - public Builder role(Role role) { - this.role = role; - return this; - } + public Builder content(Content content) { + this.content = content; + return this; + } - public Builder content(Content content) { - this.content = content; - return this; - } + public Builder model(String model) { + this.model = model; + return this; + } - public Builder model(String model) { - this.model = model; - return this; - } + public Builder stopReason(StopReason stopReason) { + this.stopReason = stopReason; + return this; + } - public Builder stopReason(StopReason stopReason) { - this.stopReason = stopReason; - return this; - } + public Builder message(String message) { + this.content = new TextContent(message); + return this; + } - public Builder message(String message) { - this.content = new TextContent(message); - return this; - } + public Builder meta(Map meta) { + this.meta = meta; + return this; + } - public CreateMessageResult build() { - return new CreateMessageResult(role, content, model, stopReason); - } - } - }// @formatter:on + public CreateMessageResult build() { + return new CreateMessageResult(role, content, model, stopReason, meta); + } + } + }// @formatter:on // Elicitation /** @@ -1344,92 +1498,104 @@ public CreateMessageResult build() { * * @param message The body of the elicitation message. * @param requestedSchema The elicitation response schema that must be satisfied. + * @param meta Additional metadata related to this request. */ @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record ElicitRequest(// @formatter:off - @JsonProperty("message") String message, - @JsonProperty("requestedSchema") Map requestedSchema, - @JsonProperty("_meta") Map meta) implements Request { + @JsonProperty("message") String message, + @JsonProperty("requestedSchema") Map requestedSchema, + @JsonProperty("_meta") Map meta) implements Request { - // backwards compatibility constructor - public ElicitRequest(String message, Map requestedSchema) { - this(message, requestedSchema, null); - } + // backwards compatibility constructor + public ElicitRequest(String message, Map requestedSchema) { + this(message, requestedSchema, null); + } - public static Builder builder() { - return new Builder(); - } + public static Builder builder() { + return new Builder(); + } - public static class Builder { - private String message; - private Map requestedSchema; - private Map meta; + public static class Builder { + private String message; + private Map requestedSchema; + private Map meta; - public Builder message(String message) { - this.message = message; - return this; - } + public Builder message(String message) { + this.message = message; + return this; + } - public Builder requestedSchema(Map requestedSchema) { - this.requestedSchema = requestedSchema; - return this; - } + public Builder requestedSchema(Map requestedSchema) { + this.requestedSchema = requestedSchema; + return this; + } - public Builder meta(Map meta) { - this.meta = meta; - return this; - } - - public Builder progressToken(String progressToken) { - if (this.meta == null) { - this.meta = new HashMap<>(); - } - this.meta.put("progressToken", progressToken); - return this; - } - - public ElicitRequest build() { - return new ElicitRequest(message, requestedSchema, meta); - } - } - }// @formatter:on + public Builder meta(Map meta) { + this.meta = meta; + return this; + } + + public Builder progressToken(String progressToken) { + if (this.meta == null) { + this.meta = new HashMap<>(); + } + this.meta.put("progressToken", progressToken); + return this; + } + + public ElicitRequest build() { + return new ElicitRequest(message, requestedSchema, meta); + } + } + }// @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record ElicitResult(// @formatter:off - @JsonProperty("action") Action action, - @JsonProperty("content") Map content) { + @JsonProperty("action") Action action, + @JsonProperty("content") Map content, + @JsonProperty("_meta") Map meta) implements Result { + + public enum Action { + @JsonProperty("accept") ACCEPT, + @JsonProperty("decline") DECLINE, + @JsonProperty("cancel") CANCEL + } - public enum Action { - @JsonProperty("accept") ACCEPT, - @JsonProperty("decline") DECLINE, - @JsonProperty("cancel") CANCEL - } + public ElicitResult(Action action, Map content) { + this(action, content, null); + } - public static Builder builder() { - return new Builder(); - } + public static Builder builder() { + return new Builder(); + } - public static class Builder { - private Action action; - private Map content; + public static class Builder { + private Action action; + private Map content; + private Map meta; - public Builder message(Action action) { - this.action = action; - return this; - } + public Builder message(Action action) { + this.action = action; + return this; + } - public Builder content(Map content) { - this.content = content; - return this; - } + public Builder content(Map content) { + this.content = content; + return this; + } - public ElicitResult build() { - return new ElicitResult(action, content); - } - } - }// @formatter:on + public Builder meta(Map meta) { + this.meta = meta; + return this; + } + + public ElicitResult build() { + return new ElicitResult(action, content, meta); + } + } + }// @formatter:on // --------------------------- // Pagination Interfaces @@ -1437,8 +1603,8 @@ public ElicitResult build() { @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record PaginatedRequest(// @formatter:off - @JsonProperty("cursor") String cursor, - @JsonProperty("_meta") Map meta) implements Request { // @formatter:on + @JsonProperty("cursor") String cursor, + @JsonProperty("_meta") Map meta) implements Request { // @formatter:on public PaginatedRequest(String cursor) { this(cursor, null); @@ -1470,26 +1636,38 @@ public record PaginatedResult(@JsonProperty("nextCursor") String nextCursor) { * @param progress A value indicating the current progress. * @param total An optional total amount of work to be done, if known. * @param message An optional message providing additional context about the progress. + * @param meta Additional metadata related to this notification. */ @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record ProgressNotification(// @formatter:off - @JsonProperty("progressToken") String progressToken, - @JsonProperty("progress") Double progress, - @JsonProperty("total") Double total, - @JsonProperty("message") String message) { - }// @formatter:on + @JsonProperty("progressToken") String progressToken, + @JsonProperty("progress") double progress, + @JsonProperty("total") Double total, + @JsonProperty("message") String message, + @JsonProperty("_meta") Map meta) implements Notification { + + public ProgressNotification(String progressToken, double progress, Double total, String message) { + this(progressToken, progress, total, message, null); + } + }// @formatter:on /** * The Model Context Protocol (MCP) provides a standardized way for servers to send * resources update message to clients. * * @param uri The updated resource uri. + * @param meta Additional metadata related to this notification. */ @JsonIgnoreProperties(ignoreUnknown = true) public record ResourcesUpdatedNotification(// @formatter:off - @JsonProperty("uri") String uri) { - }// @formatter:on + @JsonProperty("uri") String uri, + @JsonProperty("_meta") Map meta) implements Notification { + + public ResourcesUpdatedNotification(String uri) { + this(uri, null); + } + }// @formatter:on /** * The Model Context Protocol (MCP) provides a standardized way for servers to send @@ -1500,42 +1678,54 @@ public record ResourcesUpdatedNotification(// @formatter:off * @param level The severity levels. The minimum log level is set by the client. * @param logger The logger that generated the message. * @param data JSON-serializable logging data. + * @param meta Additional metadata related to this notification. */ @JsonIgnoreProperties(ignoreUnknown = true) public record LoggingMessageNotification(// @formatter:off - @JsonProperty("level") LoggingLevel level, - @JsonProperty("logger") String logger, - @JsonProperty("data") String data) { + @JsonProperty("level") LoggingLevel level, + @JsonProperty("logger") String logger, + @JsonProperty("data") String data, + @JsonProperty("_meta") Map meta) implements Notification { - public static Builder builder() { - return new Builder(); - } + public LoggingMessageNotification(LoggingLevel level, String logger, String data) { + this(level, logger, data, null); + } - public static class Builder { - private LoggingLevel level = LoggingLevel.INFO; - private String logger = "server"; - private String data; + public static Builder builder() { + return new Builder(); + } - public Builder level(LoggingLevel level) { - this.level = level; - return this; - } + public static class Builder { + private LoggingLevel level = LoggingLevel.INFO; + private String logger = "server"; + private String data; + private Map meta; - public Builder logger(String logger) { - this.logger = logger; - return this; - } + public Builder level(LoggingLevel level) { + this.level = level; + return this; + } - public Builder data(String data) { - this.data = data; - return this; - } + public Builder logger(String logger) { + this.logger = logger; + return this; + } - public LoggingMessageNotification build() { - return new LoggingMessageNotification(level, logger, data); - } - } - }// @formatter:on + public Builder data(String data) { + this.data = data; + return this; + } + + public Builder meta(Map meta) { + this.meta = meta; + return this; + } + + public LoggingMessageNotification build() { + return new LoggingMessageNotification(level, logger, data, meta); + } + } + }// @formatter:on public enum LoggingLevel {// @formatter:off @JsonProperty("debug") DEBUG(0), @@ -1610,29 +1800,34 @@ public String identifier() { @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record CompleteRequest(// @formatter:off - @JsonProperty("ref") McpSchema.CompleteReference ref, - @JsonProperty("argument") CompleteArgument argument, - @JsonProperty("_meta") Map meta) implements Request { + @JsonProperty("ref") McpSchema.CompleteReference ref, + @JsonProperty("argument") CompleteArgument argument, + @JsonProperty("_meta") Map meta) implements Request { - public CompleteRequest(McpSchema.CompleteReference ref, CompleteArgument argument) { - this(ref, argument, null); - } + public CompleteRequest(McpSchema.CompleteReference ref, CompleteArgument argument) { + this(ref, argument, null); + } - public record CompleteArgument( - @JsonProperty("name") String name, - @JsonProperty("value") String value) { - }// @formatter:on + public record CompleteArgument( + @JsonProperty("name") String name, + @JsonProperty("value") String value) { + }// @formatter:on } @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) - public record CompleteResult(@JsonProperty("completion") CompleteCompletion completion) { // @formatter:off + public record CompleteResult(@JsonProperty("completion") CompleteCompletion completion, + @JsonProperty("_meta") Map meta) implements Result { // @formatter:off - public record CompleteCompletion( - @JsonProperty("values") List values, - @JsonProperty("total") Integer total, - @JsonProperty("hasMore") Boolean hasMore) { - }// @formatter:on + public CompleteResult(CompleteCompletion completion) { + this(completion, null); + } + + public record CompleteCompletion( + @JsonProperty("values") List values, + @JsonProperty("total") Integer total, + @JsonProperty("hasMore") Boolean hasMore) { + }// @formatter:on } // --------------------------- @@ -1670,11 +1865,16 @@ else if (this instanceof ResourceLink) { @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record TextContent( // @formatter:off - @JsonProperty("annotations") Annotations annotations, - @JsonProperty("text") String text) implements Annotated, Content { // @formatter:on + @JsonProperty("annotations") Annotations annotations, + @JsonProperty("text") String text, + @JsonProperty("_meta") Map meta) implements Annotated, Content { // @formatter:on + + public TextContent(Annotations annotations, String content) { + this(annotations, content, null); + } public TextContent(String content) { - this(null, content); + this(null, content, null); } /** @@ -1682,7 +1882,7 @@ public TextContent(String content) { * {@link TextContent#TextContent(Annotations, String)} instead. */ public TextContent(List audience, Double priority, String content) { - this(audience != null || priority != null ? new Annotations(audience, priority) : null, content); + this(audience != null || priority != null ? new Annotations(audience, priority) : null, content, null); } /** @@ -1705,16 +1905,22 @@ public Double priority() { @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record ImageContent( // @formatter:off - @JsonProperty("annotations") Annotations annotations, - @JsonProperty("data") String data, - @JsonProperty("mimeType") String mimeType) implements Annotated, Content { // @formatter:on + @JsonProperty("annotations") Annotations annotations, + @JsonProperty("data") String data, + @JsonProperty("mimeType") String mimeType, + @JsonProperty("_meta") Map meta) implements Annotated, Content { // @formatter:on + + public ImageContent(Annotations annotations, String data, String mimeType) { + this(annotations, data, mimeType, null); + } /** * @deprecated Only exists for backwards-compatibility purposes. Use * {@link ImageContent#ImageContent(Annotations, String, String)} instead. */ public ImageContent(List audience, Double priority, String data, String mimeType) { - this(audience != null || priority != null ? new Annotations(audience, priority) : null, data, mimeType); + this(audience != null || priority != null ? new Annotations(audience, priority) : null, data, mimeType, + null); } /** @@ -1737,16 +1943,26 @@ public Double priority() { @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record AudioContent( // @formatter:off - @JsonProperty("annotations") Annotations annotations, - @JsonProperty("data") String data, - @JsonProperty("mimeType") String mimeType) implements Annotated, Content { // @formatter:on + @JsonProperty("annotations") Annotations annotations, + @JsonProperty("data") String data, + @JsonProperty("mimeType") String mimeType, + @JsonProperty("_meta") Map meta) implements Annotated, Content { // @formatter:on + + public AudioContent(Annotations annotations, String data, String mimeType) { + this(annotations, data, mimeType, null); + } } @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record EmbeddedResource( // @formatter:off - @JsonProperty("annotations") Annotations annotations, - @JsonProperty("resource") ResourceContents resource) implements Annotated, Content { // @formatter:on + @JsonProperty("annotations") Annotations annotations, + @JsonProperty("resource") ResourceContents resource, + @JsonProperty("_meta") Map meta) implements Annotated, Content { // @formatter:on + + public EmbeddedResource(Annotations annotations, ResourceContents resource) { + this(annotations, resource, null); + } /** * @deprecated Only exists for backwards-compatibility purposes. Use @@ -1754,7 +1970,7 @@ public record EmbeddedResource( // @formatter:off * instead. */ public EmbeddedResource(List audience, Double priority, ResourceContents resource) { - this(audience != null || priority != null ? new Annotations(audience, priority) : null, resource); + this(audience != null || priority != null ? new Annotations(audience, priority) : null, resource, null); } /** @@ -1870,13 +2086,19 @@ public ResourceLink build() { * @param name 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. + * @param meta Additional metadata related to this notification. */ @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record Root( // @formatter:off - @JsonProperty("uri") String uri, - @JsonProperty("name") String name) { - } // @formatter:on + @JsonProperty("uri") String uri, + @JsonProperty("name") String name, + @JsonProperty("_meta") Map meta) { + + public Root(String uri, String name) { + this(uri, name, null); + } + } // @formatter:on /** * The client's response to a roots/list request from the server. This result contains @@ -1888,16 +2110,23 @@ public record Root( // @formatter:off * @param nextCursor An optional cursor for pagination. If present, indicates there * are more roots available. The client can use this cursor to request the next page * of results by sending a roots/list request with the cursor parameter set to this + * @param meta Additional metadata related toAdditional metadata related to this + * result. */ @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record ListRootsResult( // @formatter:off - @JsonProperty("roots") List roots, - @JsonProperty("nextCursor") String nextCursor) { + @JsonProperty("roots") List roots, + @JsonProperty("nextCursor") String nextCursor, + @JsonProperty("_meta") Map meta) implements Result { - public ListRootsResult(List roots) { - this(roots, null); - } - } // @formatter:on + public ListRootsResult(List roots) { + this(roots, null, null); + } + + public ListRootsResult(List roots, String nextCursor) { + this(roots, nextCursor, null); + } + } // @formatter:on } diff --git a/mcp/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java b/mcp/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java index ea063e4e..1c78ffe8 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java @@ -3,27 +3,27 @@ */ package io.modelcontextprotocol.spec; -import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; -import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.junit.jupiter.api.Test; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.exc.InvalidTypeIdException; import io.modelcontextprotocol.spec.McpSchema.TextResourceContents; +import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; +import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json; import net.javacrumbs.jsonunit.core.Option; /** * @author Christian Tzolov + * @author Anurag Pant */ public class McpSchemaTests { @@ -46,11 +46,12 @@ void testTextContent() throws Exception { @Test void testTextContentDeserialization() throws Exception { McpSchema.TextContent textContent = mapper.readValue(""" - {"type":"text","text":"XXX"}""", McpSchema.TextContent.class); + {"type":"text","text":"XXX","_meta":{"metaKey":"metaValue"}}""", McpSchema.TextContent.class); assertThat(textContent).isNotNull(); assertThat(textContent.type()).isEqualTo("text"); assertThat(textContent.text()).isEqualTo("XXX"); + assertThat(textContent.meta()).containsKey("metaKey"); } @Test @@ -78,11 +79,13 @@ void testImageContent() throws Exception { @Test void testImageContentDeserialization() throws Exception { McpSchema.ImageContent imageContent = mapper.readValue(""" - {"type":"image","data":"base64encodeddata","mimeType":"image/png"}""", McpSchema.ImageContent.class); + {"type":"image","data":"base64encodeddata","mimeType":"image/png","_meta":{"metaKey":"metaValue"}}""", + McpSchema.ImageContent.class); assertThat(imageContent).isNotNull(); assertThat(imageContent.type()).isEqualTo("image"); assertThat(imageContent.data()).isEqualTo("base64encodeddata"); assertThat(imageContent.mimeType()).isEqualTo("image/png"); + assertThat(imageContent.meta()).containsKey("metaKey"); } @Test @@ -100,11 +103,13 @@ void testAudioContent() throws Exception { @Test void testAudioContentDeserialization() throws Exception { McpSchema.AudioContent audioContent = mapper.readValue(""" - {"type":"audio","data":"base64encodeddata","mimeType":"audio/wav"}""", McpSchema.AudioContent.class); + {"type":"audio","data":"base64encodeddata","mimeType":"audio/wav","_meta":{"metaKey":"metaValue"}}""", + McpSchema.AudioContent.class); assertThat(audioContent).isNotNull(); assertThat(audioContent.type()).isEqualTo("audio"); assertThat(audioContent.data()).isEqualTo("base64encodeddata"); assertThat(audioContent.mimeType()).isEqualTo("audio/wav"); + assertThat(audioContent.meta()).containsKey("metaKey"); } @Test @@ -164,7 +169,7 @@ void testEmbeddedResource() throws Exception { void testEmbeddedResourceDeserialization() throws Exception { McpSchema.EmbeddedResource embeddedResource = mapper.readValue( """ - {"type":"resource","resource":{"uri":"resource://test","mimeType":"text/plain","text":"Sample resource content"}}""", + {"type":"resource","resource":{"uri":"resource://test","mimeType":"text/plain","text":"Sample resource content"},"_meta":{"metaKey":"metaValue"}}""", McpSchema.EmbeddedResource.class); assertThat(embeddedResource).isNotNull(); assertThat(embeddedResource.type()).isEqualTo("resource"); @@ -172,6 +177,7 @@ void testEmbeddedResourceDeserialization() throws Exception { assertThat(embeddedResource.resource().uri()).isEqualTo("resource://test"); assertThat(embeddedResource.resource().mimeType()).isEqualTo("text/plain"); assertThat(((TextResourceContents) embeddedResource.resource()).text()).isEqualTo("Sample resource content"); + assertThat(embeddedResource.meta()).containsKey("metaKey"); } @Test @@ -194,7 +200,7 @@ void testEmbeddedResourceWithBlobContents() throws Exception { void testEmbeddedResourceWithBlobContentsDeserialization() throws Exception { McpSchema.EmbeddedResource embeddedResource = mapper.readValue( """ - {"type":"resource","resource":{"uri":"resource://test","mimeType":"application/octet-stream","blob":"base64encodedblob"}}""", + {"type":"resource","resource":{"uri":"resource://test","mimeType":"application/octet-stream","blob":"base64encodedblob","_meta":{"metaKey":"metaValue"}}}""", McpSchema.EmbeddedResource.class); assertThat(embeddedResource).isNotNull(); assertThat(embeddedResource.type()).isEqualTo("resource"); @@ -203,6 +209,7 @@ void testEmbeddedResourceWithBlobContentsDeserialization() throws Exception { assertThat(embeddedResource.resource().mimeType()).isEqualTo("application/octet-stream"); assertThat(((McpSchema.BlobResourceContents) embeddedResource.resource()).blob()) .isEqualTo("base64encodedblob"); + assertThat(((McpSchema.BlobResourceContents) embeddedResource.resource()).meta()).containsKey("metaKey"); } @Test @@ -307,8 +314,10 @@ void testInitializeRequest() throws Exception { .build(); McpSchema.Implementation clientInfo = new McpSchema.Implementation("test-client", "1.0.0"); + Map meta = Map.of("metaKey", "metaValue"); - McpSchema.InitializeRequest request = new McpSchema.InitializeRequest("2024-11-05", capabilities, clientInfo); + McpSchema.InitializeRequest request = new McpSchema.InitializeRequest("2024-11-05", capabilities, clientInfo, + meta); String value = mapper.writeValueAsString(request); assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) @@ -316,7 +325,7 @@ void testInitializeRequest() throws Exception { .isObject() .isEqualTo( json(""" - {"protocolVersion":"2024-11-05","capabilities":{"roots":{"listChanged":true},"sampling":{}},"clientInfo":{"name":"test-client","version":"1.0.0"}}""")); + {"protocolVersion":"2024-11-05","capabilities":{"roots":{"listChanged":true},"sampling":{}},"clientInfo":{"name":"test-client","version":"1.0.0"},"_meta":{"metaKey":"metaValue"}}""")); } @Test @@ -373,6 +382,7 @@ void testResourceBuilder() throws Exception { .mimeType("text/plain") .size(256L) .annotations(annotations) + .meta(Map.of("metaKey", "metaValue")) .build(); String value = mapper.writeValueAsString(resource); @@ -381,7 +391,7 @@ void testResourceBuilder() throws Exception { .isObject() .isEqualTo( json(""" - {"uri":"resource://test","name":"Test Resource","description":"A test resource","mimeType":"text/plain","size":256,"annotations":{"audience":["user","assistant"],"priority":0.8}}""")); + {"uri":"resource://test","name":"Test Resource","description":"A test resource","mimeType":"text/plain","size":256,"annotations":{"audience":["user","assistant"],"priority":0.8},"_meta":{"metaKey":"metaValue"}}""")); } @Test @@ -417,9 +427,10 @@ void testResourceBuilderNameRequired() { @Test void testResourceTemplate() throws Exception { McpSchema.Annotations annotations = new McpSchema.Annotations(Arrays.asList(McpSchema.Role.USER), 0.5); + Map meta = Map.of("metaKey", "metaValue"); McpSchema.ResourceTemplate template = new McpSchema.ResourceTemplate("resource://{param}/test", "Test Template", - "A test resource template", "text/plain", annotations); + "A test resource template", "text/plain", annotations, meta); String value = mapper.writeValueAsString(template); assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) @@ -427,7 +438,7 @@ void testResourceTemplate() throws Exception { .isObject() .isEqualTo( json(""" - {"uriTemplate":"resource://{param}/test","name":"Test Template","description":"A test resource template","mimeType":"text/plain","annotations":{"audience":["user"],"priority":0.5}}""")); + {"uriTemplate":"resource://{param}/test","name":"Test Template","description":"A test resource template","mimeType":"text/plain","annotations":{"audience":["user"],"priority":0.5},"_meta":{"metaKey":"metaValue"}}""")); } @Test @@ -438,8 +449,10 @@ void testListResourcesResult() throws Exception { McpSchema.Resource resource2 = new McpSchema.Resource("resource://test2", "Test Resource 2", "Second test resource", "application/json", null); + Map meta = Map.of("metaKey", "metaValue"); + McpSchema.ListResourcesResult result = new McpSchema.ListResourcesResult(Arrays.asList(resource1, resource2), - "next-cursor"); + "next-cursor", meta); String value = mapper.writeValueAsString(result); assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) @@ -447,7 +460,7 @@ void testListResourcesResult() throws Exception { .isObject() .isEqualTo( json(""" - {"resources":[{"uri":"resource://test1","name":"Test Resource 1","description":"First test resource","mimeType":"text/plain"},{"uri":"resource://test2","name":"Test Resource 2","description":"Second test resource","mimeType":"application/json"}],"nextCursor":"next-cursor"}""")); + {"resources":[{"uri":"resource://test1","name":"Test Resource 1","description":"First test resource","mimeType":"text/plain"},{"uri":"resource://test2","name":"Test Resource 2","description":"Second test resource","mimeType":"application/json"}],"nextCursor":"next-cursor","_meta":{"metaKey":"metaValue"}}""")); } @Test @@ -472,14 +485,15 @@ void testListResourceTemplatesResult() throws Exception { @Test void testReadResourceRequest() throws Exception { - McpSchema.ReadResourceRequest request = new McpSchema.ReadResourceRequest("resource://test"); + McpSchema.ReadResourceRequest request = new McpSchema.ReadResourceRequest("resource://test", + Map.of("metaKey", "metaValue")); String value = mapper.writeValueAsString(request); assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) .when(Option.IGNORING_EXTRA_ARRAY_ITEMS) .isObject() .isEqualTo(json(""" - {"uri":"resource://test"}""")); + {"uri":"resource://test","_meta":{"metaKey":"metaValue"}}""")); } @Test @@ -520,7 +534,8 @@ void testReadResourceResult() throws Exception { McpSchema.BlobResourceContents contents2 = new McpSchema.BlobResourceContents("resource://test2", "application/octet-stream", "base64encodedblob"); - McpSchema.ReadResourceResult result = new McpSchema.ReadResourceResult(Arrays.asList(contents1, contents2)); + McpSchema.ReadResourceResult result = new McpSchema.ReadResourceResult(Arrays.asList(contents1, contents2), + Map.of("metaKey", "metaValue")); String value = mapper.writeValueAsString(result); assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) @@ -528,7 +543,7 @@ void testReadResourceResult() throws Exception { .isObject() .isEqualTo( json(""" - {"contents":[{"uri":"resource://test1","mimeType":"text/plain","text":"Sample text content"},{"uri":"resource://test2","mimeType":"application/octet-stream","blob":"base64encodedblob"}]}""")); + {"contents":[{"uri":"resource://test1","mimeType":"text/plain","text":"Sample text content"},{"uri":"resource://test2","mimeType":"application/octet-stream","blob":"base64encodedblob"}],"_meta":{"metaKey":"metaValue"}}""")); } // Prompt Tests @@ -539,7 +554,8 @@ void testPrompt() throws Exception { McpSchema.PromptArgument arg2 = new McpSchema.PromptArgument("arg2", "Second argument", false); - McpSchema.Prompt prompt = new McpSchema.Prompt("test-prompt", "A test prompt", Arrays.asList(arg1, arg2)); + McpSchema.Prompt prompt = new McpSchema.Prompt("test-prompt", "A test prompt", Arrays.asList(arg1, arg2), + Map.of("metaKey", "metaValue")); String value = mapper.writeValueAsString(prompt); assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) @@ -547,7 +563,7 @@ void testPrompt() throws Exception { .isObject() .isEqualTo( json(""" - {"name":"test-prompt","description":"A test prompt","arguments":[{"name":"arg1","description":"First argument","required":true},{"name":"arg2","description":"Second argument","required":false}]}""")); + {"name":"test-prompt","description":"A test prompt","arguments":[{"name":"arg1","description":"First argument","required":true},{"name":"arg2","description":"Second argument","required":false}],"_meta":{"metaKey":"metaValue"}}""")); } @Test @@ -801,6 +817,33 @@ void testToolWithComplexSchema() throws Exception { assertThat(deserializedTool.inputSchema().defs()).containsKey("Address"); } + @Test + void testToolWithMeta() throws Exception { + String schemaJson = """ + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "number" + } + }, + "required": ["name"] + } + """; + + McpSchema.JsonSchema schema = mapper.readValue(schemaJson, McpSchema.JsonSchema.class); + Map meta = Map.of("metaKey", "metaValue"); + + McpSchema.Tool tool = new McpSchema.Tool("addressTool", "Handles addresses", schema, null, meta); + + // Verify that meta value was preserved + assertThat(tool.meta()).isNotNull(); + assertThat(tool.meta()).containsKey("metaKey"); + } + @Test void testToolWithAnnotations() throws Exception { String schemaJson = """ @@ -1281,14 +1324,14 @@ void testCompleteRequestWithMeta() throws Exception { @Test void testRoot() throws Exception { - McpSchema.Root root = new McpSchema.Root("file:///path/to/root", "Test Root"); + McpSchema.Root root = new McpSchema.Root("file:///path/to/root", "Test Root", Map.of("metaKey", "metaValue")); String value = mapper.writeValueAsString(root); assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) .when(Option.IGNORING_EXTRA_ARRAY_ITEMS) .isObject() .isEqualTo(json(""" - {"uri":"file:///path/to/root","name":"Test Root"}""")); + {"uri":"file:///path/to/root","name":"Test Root","_meta":{"metaKey":"metaValue"}}""")); } @Test @@ -1315,7 +1358,7 @@ void testListRootsResult() throws Exception { @Test void testProgressNotificationWithMessage() throws Exception { McpSchema.ProgressNotification notification = new McpSchema.ProgressNotification("progress-token-123", 0.5, 1.0, - "Processing file 1 of 2"); + "Processing file 1 of 2", Map.of("key", "value")); String value = mapper.writeValueAsString(notification); assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) @@ -1323,19 +1366,21 @@ void testProgressNotificationWithMessage() throws Exception { .isObject() .isEqualTo( json(""" - {"progressToken":"progress-token-123","progress":0.5,"total":1.0,"message":"Processing file 1 of 2"}""")); + {"progressToken":"progress-token-123","progress":0.5,"total":1.0,"message":"Processing file 1 of 2"},"_meta":{"key":"value"}""")); } @Test void testProgressNotificationDeserialization() throws Exception { - McpSchema.ProgressNotification notification = mapper.readValue(""" - {"progressToken":"token-456","progress":0.75,"total":1.0,"message":"Almost done"}""", + McpSchema.ProgressNotification notification = mapper.readValue( + """ + {"progressToken":"token-456","progress":0.75,"total":1.0,"message":"Almost done","_meta":{"key":"value"}}""", McpSchema.ProgressNotification.class); assertThat(notification.progressToken()).isEqualTo("token-456"); assertThat(notification.progress()).isEqualTo(0.75); assertThat(notification.total()).isEqualTo(1.0); assertThat(notification.message()).isEqualTo("Almost done"); + assertThat(notification.meta()).containsEntry("key", "value"); } @Test From 257cb1bff28aa75ba5f5148ac1731ea3ec131531 Mon Sep 17 00:00:00 2001 From: Anurag Pant Date: Tue, 1 Jul 2025 14:42:10 -0700 Subject: [PATCH 2/2] Add _meta to ResourceLink --- .../modelcontextprotocol/spec/McpSchema.java | 19 +++++++++++++++++-- .../spec/McpSchemaTests.java | 9 +++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java b/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java index 2ac88e57..40117ffb 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java +++ b/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java @@ -1841,6 +1841,8 @@ public record CompleteCompletion( @JsonSubTypes.Type(value = ResourceLink.class, name = "resource_link") }) public sealed interface Content permits TextContent, ImageContent, AudioContent, EmbeddedResource, ResourceLink { + Map meta(); + default String type() { if (this instanceof TextContent) { return "text"; @@ -2014,7 +2016,13 @@ public record ResourceLink( // @formatter:off @JsonProperty("description") String description, @JsonProperty("mimeType") String mimeType, @JsonProperty("size") Long size, - @JsonProperty("annotations") Annotations annotations) implements Annotated, Content, ResourceContent { // @formatter:on + @JsonProperty("annotations") Annotations annotations, + @JsonProperty("_meta") Map meta) implements Annotated, Content, ResourceContent { // @formatter:on + + public ResourceLink(String name, String uri, String description, String mimeType, Long size, + Annotations annotations) { + this(name, uri, description, mimeType, size, annotations, null); + } public static Builder builder() { return new Builder(); @@ -2034,6 +2042,8 @@ public static class Builder { private Long size; + private Map meta; + public Builder name(String name) { this.name = name; return this; @@ -2064,11 +2074,16 @@ public Builder size(Long size) { return this; } + public Builder meta(Map meta) { + this.meta = meta; + return this; + } + public ResourceLink build() { Assert.hasText(uri, "uri must not be empty"); Assert.hasText(name, "name must not be empty"); - return new ResourceLink(name, uri, description, mimeType, size, annotations); + return new ResourceLink(name, uri, description, mimeType, size, annotations, meta); } } diff --git a/mcp/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java b/mcp/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java index 1c78ffe8..f11b2ef6 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java @@ -215,7 +215,7 @@ void testEmbeddedResourceWithBlobContentsDeserialization() throws Exception { @Test void testResourceLink() throws Exception { McpSchema.ResourceLink resourceLink = new McpSchema.ResourceLink("main.rs", "file:///project/src/main.rs", - "Primary application entry point", "text/x-rust", null, null); + "Primary application entry point", "text/x-rust", null, null, Map.of("metaKey", "metaValue")); String value = mapper.writeValueAsString(resourceLink); assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) @@ -223,14 +223,14 @@ void testResourceLink() throws Exception { .isObject() .isEqualTo( json(""" - {"type":"resource_link","name":"main.rs","uri":"file:///project/src/main.rs","description":"Primary application entry point","mimeType":"text/x-rust"}""")); + {"type":"resource_link","name":"main.rs","uri":"file:///project/src/main.rs","description":"Primary application entry point","mimeType":"text/x-rust","_meta":{"metaKey":"metaValue"}}""")); } @Test void testResourceLinkDeserialization() throws Exception { McpSchema.ResourceLink resourceLink = mapper.readValue( """ - {"type":"resource_link","name":"main.rs","uri":"file:///project/src/main.rs","description":"Primary application entry point","mimeType":"text/x-rust"}""", + {"type":"resource_link","name":"main.rs","uri":"file:///project/src/main.rs","description":"Primary application entry point","mimeType":"text/x-rust","_meta":{"metaKey":"metaValue"}}""", McpSchema.ResourceLink.class); assertThat(resourceLink).isNotNull(); assertThat(resourceLink.type()).isEqualTo("resource_link"); @@ -238,6 +238,7 @@ void testResourceLinkDeserialization() throws Exception { assertThat(resourceLink.uri()).isEqualTo("file:///project/src/main.rs"); assertThat(resourceLink.description()).isEqualTo("Primary application entry point"); assertThat(resourceLink.mimeType()).isEqualTo("text/x-rust"); + assertThat(resourceLink.meta()).containsEntry("metaKey", "metaValue"); } // JSON-RPC Message Types Tests @@ -1366,7 +1367,7 @@ void testProgressNotificationWithMessage() throws Exception { .isObject() .isEqualTo( json(""" - {"progressToken":"progress-token-123","progress":0.5,"total":1.0,"message":"Processing file 1 of 2"},"_meta":{"key":"value"}""")); + {"progressToken":"progress-token-123","progress":0.5,"total":1.0,"message":"Processing file 1 of 2","_meta":{"key":"value"}}""")); } @Test