diff --git a/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java b/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java index 79534c87..c9e4d0e6 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java +++ b/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java @@ -33,6 +33,7 @@ * @author Christian Tzolov * @author Luca Chang * @author Surbhi Bansal + * @author Anurag Pant */ public final class McpSchema { @@ -142,8 +143,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(); @@ -156,6 +158,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<>() { }; @@ -201,7 +218,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, @@ -261,7 +277,13 @@ public record InitializeResult( // @formatter:off @JsonProperty("protocolVersion") String protocolVersion, @JsonProperty("capabilities") ServerCapabilities capabilities, @JsonProperty("serverInfo") Implementation serverInfo, - @JsonProperty("instructions") String instructions) { + @JsonProperty("instructions") String instructions, + @JsonProperty("_meta") Map meta) implements Result { + + public InitializeResult(String protocolVersion, ServerCapabilities capabilities, + Implementation serverInfo, String instructions) { + this(protocolVersion, capabilities, serverInfo, instructions, null); + } } // @formatter:on /** @@ -566,6 +588,7 @@ public interface BaseMetadata { * 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) @@ -576,7 +599,8 @@ public record Resource( // @formatter:off @JsonProperty("description") String description, @JsonProperty("mimeType") String mimeType, @JsonProperty("size") Long size, - @JsonProperty("annotations") Annotations annotations) implements Annotated, ResourceContent {// @formatter:on + @JsonProperty("annotations") Annotations annotations, + @JsonProperty("_meta") Map meta) implements Annotated, ResourceContent {// @formatter:on /** * @deprecated Only exists for backwards-compatibility purposes. Use @@ -584,7 +608,7 @@ public record Resource( // @formatter:off */ @Deprecated public Resource(String uri, String name, String description, String mimeType, Annotations annotations) { - this(uri, name, null, description, mimeType, null, annotations); + this(uri, name, null, description, mimeType, null, annotations, null); } public static Builder builder() { @@ -607,6 +631,8 @@ public static class Builder { private Annotations annotations; + private Map meta; + public Builder uri(String uri) { this.uri = uri; return this; @@ -642,11 +668,16 @@ public Builder annotations(Annotations annotations) { return this; } + 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, title, description, mimeType, size, annotations); + return new Resource(uri, name, title, description, mimeType, size, annotations, meta); } } @@ -654,7 +685,6 @@ public Resource build() { /** * 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. @@ -668,30 +698,50 @@ 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("title") String title, - @JsonProperty("description") String description, - @JsonProperty("mimeType") String mimeType, - @JsonProperty("annotations") Annotations annotations) implements Annotated, BaseMetadata { - } // @formatter:on + @JsonProperty("uriTemplate") String uriTemplate, + @JsonProperty("name") String name, + @JsonProperty("title") String title, + @JsonProperty("description") String description, + @JsonProperty("mimeType") String mimeType, + @JsonProperty("annotations") Annotations annotations, + @JsonProperty("_meta") Map meta) implements Annotated, BaseMetadata { + + public ResourceTemplate(String uri, String name, String title, String description, String mimeType, Annotations annotations) { + this(uri, name, title, 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) { + @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) { + @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) @@ -708,7 +758,12 @@ public ReadResourceRequest(String uri) { @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record ReadResourceResult( // @formatter:off - @JsonProperty("contents") List contents){ + @JsonProperty("contents") List contents, + @JsonProperty("_meta") Map meta) implements Result { + + public ReadResourceResult(List contents) { + this(contents, null); + } } // @formatter:on /** @@ -717,17 +772,27 @@ 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){ + @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){ + @JsonProperty("uri") String uri, + @JsonProperty("_meta") Map meta) implements Request { + + public UnsubscribeRequest(String uri) { + this(uri, null); + } } // @formatter:on /** @@ -750,6 +815,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(); + } /** @@ -759,13 +832,19 @@ 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 { + @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 /** @@ -776,13 +855,19 @@ 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 { + @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 // --------------------------- @@ -795,6 +880,7 @@ public record BlobResourceContents( // @formatter:off * @param title An optional title for the prompt. * @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) @@ -802,8 +888,13 @@ public record Prompt( // @formatter:off @JsonProperty("name") String name, @JsonProperty("title") String title, @JsonProperty("description") String description, - @JsonProperty("arguments") List arguments) implements BaseMetadata { - } // @formatter:on + @JsonProperty("arguments") List arguments, + @JsonProperty("_meta") Map meta) implements BaseMetadata { + + public Prompt(String name, String title, String description, List arguments) { + this(name, title, description, arguments, null); + } + } // @formatter:on /** * Describes an argument that a prompt can accept. @@ -816,11 +907,11 @@ public record Prompt( // @formatter:off @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record PromptArgument( // @formatter:off - @JsonProperty("name") String name, - @JsonProperty("title") String title, - @JsonProperty("description") String description, - @JsonProperty("required") Boolean required) implements BaseMetadata { - }// @formatter:on + @JsonProperty("name") String name, + @JsonProperty("title") String title, + @JsonProperty("description") String description, + @JsonProperty("required") Boolean required) implements BaseMetadata { + }// @formatter:on /** * Describes a message returned as part of a prompt. @@ -834,9 +925,9 @@ public record PromptArgument( // @formatter:off @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record PromptMessage( // @formatter:off - @JsonProperty("role") Role role, - @JsonProperty("content") Content content) { - } // @formatter:on + @JsonProperty("role") Role role, + @JsonProperty("content") Content content) { + } // @formatter:on /** * The server's response to a prompts/list request from the client. @@ -844,44 +935,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 + public GetPromptRequest(String name, Map arguments) { + this(name, arguments, null); + } + }// @formatter:on - /** - * 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 + /** + * 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 @@ -892,35 +996,41 @@ 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) public record JsonSchema( // @formatter:off - @JsonProperty("type") String type, - @JsonProperty("properties") Map properties, - @JsonProperty("required") List required, - @JsonProperty("additionalProperties") Boolean additionalProperties, - @JsonProperty("$defs") Map defs, - @JsonProperty("definitions") Map definitions) { - } // @formatter:on + @JsonProperty("type") String type, + @JsonProperty("properties") Map properties, + @JsonProperty("required") List required, + @JsonProperty("additionalProperties") Boolean additionalProperties, + @JsonProperty("$defs") Map defs, + @JsonProperty("definitions") Map definitions) { + } // @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record ToolAnnotations( // @formatter:off - @JsonProperty("title") String title, - @JsonProperty("readOnlyHint") Boolean readOnlyHint, - @JsonProperty("destructiveHint") Boolean destructiveHint, - @JsonProperty("idempotentHint") Boolean idempotentHint, - @JsonProperty("openWorldHint") Boolean openWorldHint, - @JsonProperty("returnDirect") Boolean returnDirect) { - } // @formatter:on + @JsonProperty("title") String title, + @JsonProperty("readOnlyHint") Boolean readOnlyHint, + @JsonProperty("destructiveHint") Boolean destructiveHint, + @JsonProperty("idempotentHint") Boolean idempotentHint, + @JsonProperty("openWorldHint") Boolean openWorldHint, + @JsonProperty("returnDirect") Boolean returnDirect) { + } // @formatter:on /** * Represents a tool that the server provides. Tools enable servers to expose @@ -940,18 +1050,23 @@ public record ToolAnnotations( // @formatter:off @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record Tool( // @formatter:off - @JsonProperty("name") String name, - @JsonProperty("title") String title, - @JsonProperty("description") String description, - @JsonProperty("inputSchema") JsonSchema inputSchema, - @JsonProperty("annotations") ToolAnnotations annotations) implements BaseMetadata { // @formatter:on + @JsonProperty("name") String name, + @JsonProperty("title") String title, + @JsonProperty("description") String description, + @JsonProperty("inputSchema") JsonSchema inputSchema, + @JsonProperty("annotations") ToolAnnotations annotations, + @JsonProperty("_meta") Map meta) implements BaseMetadata { // @formatter:on public Tool(String name, String description, String schema) { - this(name, null, description, parseSchema(schema), null); + this(name, null, description, parseSchema(schema), null, null); } public Tool(String name, String description, String schema, ToolAnnotations annotations) { - this(name, null, description, parseSchema(schema), annotations); + this(name, null, description, parseSchema(schema), annotations, null); + } + + public Tool(String name, String title, String description, JsonSchema schema, ToolAnnotations annotations) { + this(name, title, description, schema, annotations, null); } public static Builder builder() { @@ -970,6 +1085,8 @@ public static class Builder { private ToolAnnotations annotations; + private Map meta; + public Builder name(String name) { this.name = name; return this; @@ -995,10 +1112,15 @@ public Builder annotations(ToolAnnotations annotations) { return this; } + public Builder meta(Map meta) { + this.meta = meta; + return this; + } + public Tool build() { Assert.hasText(name, "name must not be empty"); - return new Tool(name, title, description, inputSchema, annotations); + return new Tool(name, title, description, inputSchema, annotations, meta); } } @@ -1021,185 +1143,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 { - - public CallToolRequest(String name, String jsonArguments) { - this(name, parseJsonArguments(jsonArguments), null); - } - public CallToolRequest(String name, Map arguments) { - this(name, arguments, 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 static Builder builder() { - return new Builder(); - } + @JsonProperty("name") String name, + @JsonProperty("arguments") Map arguments, + @JsonProperty("_meta") Map meta) implements Request { - public static class Builder { - private String name; - private Map arguments; - private Map meta; - - public Builder name(String name) { - this.name = name; - return this; - } + public CallToolRequest(String name, String jsonArguments) { + this(name, parseJsonArguments(jsonArguments), null); + } + public CallToolRequest(String name, Map arguments) { + this(name, arguments, null); + } - public Builder arguments(Map arguments) { - this.arguments = arguments; - return this; - } + 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 Builder arguments(String jsonArguments) { - this.arguments = parseJsonArguments(jsonArguments); - return this; - } + public static Builder builder() { + return new Builder(); + } - public Builder meta(Map meta) { - this.meta = meta; - return this; - } + public static class Builder { + private String name; + private Map arguments; + private Map meta; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder arguments(Map arguments) { + this.arguments = arguments; + return this; + } + + public Builder arguments(String jsonArguments) { + this.arguments = parseJsonArguments(jsonArguments); + 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 CallToolRequest build() { + Assert.hasText(name, "name must not be empty"); + return new CallToolRequest(name, arguments, meta); + } + } + }// @formatter:on - public Builder progressToken(String progressToken) { - if (this.meta == null) { - this.meta = new HashMap<>(); - } - this.meta.put("progressToken", progressToken); - return this; - } + /** + * 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 { - public CallToolRequest build() { - Assert.hasText(name, "name must not be empty"); - return new CallToolRequest(name, arguments, meta); - } - } - }// @formatter:off + public CallToolResult(List content, Boolean isError) { + this(content, isError, null); + } /** - * The server's response to a tools/call request from the client. + * Creates a new instance of {@link CallToolResult} with a string containing the + * tool result. * - * @param content A list of content items representing the tool's output. Each item can be text, an image, - * or an embedded resource. + * @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. */ - @JsonInclude(JsonInclude.Include.NON_ABSENT) - @JsonIgnoreProperties(ignoreUnknown = true) - public record CallToolResult( // @formatter:off - @JsonProperty("content") List content, - @JsonProperty("isError") Boolean isError) { - - /** - * 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); - } - - /** - * Creates a builder for {@link CallToolResult}. - * @return a new builder instance - */ - public static Builder builder() { - return new Builder(); - } - - /** - * 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; - } - - /** - * 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; - } - - /** - * 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; - } + public CallToolResult(String content, Boolean isError) { + this(List.of(new TextContent(content)), isError, null); + } - /** - * 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)); - } + /** + * Creates a builder for {@link CallToolResult}. + * @return a new builder instance + */ + public static Builder builder() { + return new Builder(); + } - /** - * 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; - } + /** + * 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 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; + } + + /** + * 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; + } + + /** + * 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); + } + } - /** - * Builds a new {@link CallToolResult} instance. - * @return a new CallToolResult instance - */ - public CallToolResult build() { - return new CallToolResult(content, isError); - } - } - - } // @formatter:on + } // @formatter:on // --------------------------- // Sampling Interfaces @@ -1207,53 +1348,53 @@ public CallToolResult build() { @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record ModelPreferences(// @formatter:off - @JsonProperty("hints") List hints, - @JsonProperty("costPriority") Double costPriority, - @JsonProperty("speedPriority") Double speedPriority, - @JsonProperty("intelligencePriority") Double intelligencePriority) { + @JsonProperty("hints") List hints, + @JsonProperty("costPriority") Double costPriority, + @JsonProperty("speedPriority") Double speedPriority, + @JsonProperty("intelligencePriority") Double intelligencePriority) { - public static Builder builder() { - return new Builder(); - } + public static Builder builder() { + return new Builder(); + } - public static class Builder { - private List hints; - private Double costPriority; - private Double speedPriority; - private Double intelligencePriority; + public static class Builder { + private List hints; + private Double costPriority; + private Double speedPriority; + private Double intelligencePriority; - public Builder hints(List hints) { - this.hints = hints; - return this; - } + public Builder hints(List hints) { + this.hints = hints; + return this; + } - public Builder addHint(String name) { - if (this.hints == null) { - this.hints = new ArrayList<>(); - } - this.hints.add(new ModelHint(name)); - return this; - } + public Builder addHint(String name) { + if (this.hints == null) { + this.hints = new ArrayList<>(); + } + this.hints.add(new ModelHint(name)); + return this; + } - public Builder costPriority(Double costPriority) { - this.costPriority = costPriority; - return this; - } + public Builder costPriority(Double costPriority) { + this.costPriority = costPriority; + return this; + } - public Builder speedPriority(Double speedPriority) { - this.speedPriority = speedPriority; - return this; - } + public Builder speedPriority(Double speedPriority) { + this.speedPriority = speedPriority; + return this; + } - public Builder intelligencePriority(Double intelligencePriority) { - this.intelligencePriority = intelligencePriority; - return this; - } + public Builder intelligencePriority(Double intelligencePriority) { + this.intelligencePriority = intelligencePriority; + return this; + } - public ModelPreferences build() { - return new ModelPreferences(hints, costPriority, speedPriority, intelligencePriority); - } - } + public ModelPreferences build() { + return new ModelPreferences(hints, costPriority, speedPriority, intelligencePriority); + } + } } // @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) @@ -1267,114 +1408,114 @@ public static ModelHint of(String name) { @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record SamplingMessage(// @formatter:off - @JsonProperty("role") Role role, - @JsonProperty("content") Content content) { - } // @formatter:on + @JsonProperty("role") Role role, + @JsonProperty("content") Content content) { + } // @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 { - - - // 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 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 Builder modelPreferences(ModelPreferences modelPreferences) { - this.modelPreferences = modelPreferences; - return this; - } + @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 { - public Builder systemPrompt(String systemPrompt) { - this.systemPrompt = systemPrompt; - return this; - } - - public Builder includeContext(ContextInclusionStrategy includeContext) { - this.includeContext = includeContext; - return this; - } - - public Builder temperature(Double temperature) { - this.temperature = temperature; - return this; - } - - public Builder maxTokens(int maxTokens) { - this.maxTokens = maxTokens; - return this; - } - - public Builder stopSequences(List stopSequences) { - this.stopSequences = stopSequences; - return this; - } - - public Builder metadata(Map metadata) { - this.metadata = metadata; - return this; - } + + // 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 Builder meta(Map meta) { - this.meta = meta; - return this; - } + public enum ContextInclusionStrategy { + @JsonProperty("none") NONE, + @JsonProperty("thisServer") THIS_SERVER, + @JsonProperty("allServers") ALL_SERVERS + } - public Builder progressToken(String progressToken) { - if (this.meta == null) { - this.meta = new HashMap<>(); - } - this.meta.put("progressToken", progressToken); - return this; - } + public static Builder builder() { + return new Builder(); + } - public CreateMessageRequest build() { - return new CreateMessageRequest(messages, modelPreferences, systemPrompt, - includeContext, temperature, maxTokens, stopSequences, metadata, meta); - } - } - }// @formatter:on + 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 systemPrompt(String systemPrompt) { + this.systemPrompt = systemPrompt; + return this; + } + + public Builder includeContext(ContextInclusionStrategy includeContext) { + this.includeContext = includeContext; + return this; + } + + public Builder temperature(Double temperature) { + this.temperature = temperature; + return this; + } + + public Builder maxTokens(int maxTokens) { + this.maxTokens = maxTokens; + return this; + } + + public Builder stopSequences(List stopSequences) { + this.stopSequences = stopSequences; + 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 @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) @@ -1382,7 +1523,8 @@ public record CreateMessageResult(// @formatter:off @JsonProperty("role") Role role, @JsonProperty("content") Content content, @JsonProperty("model") String model, - @JsonProperty("stopReason") StopReason stopReason) { + @JsonProperty("stopReason") StopReason stopReason, + @JsonProperty("_meta") Map meta) implements Result { public enum StopReason { @JsonProperty("endTurn") END_TURN("endTurn"), @@ -1405,6 +1547,11 @@ private static StopReason of(String value) { } } + public CreateMessageResult(Role role, Content content, String model, + StopReason stopReason) { + this(role, content, model, stopReason, null); + } + public static Builder builder() { return new Builder(); } @@ -1414,6 +1561,7 @@ public static class Builder { private Content content; private String model; private StopReason stopReason = StopReason.END_TURN; + private Map meta; public Builder role(Role role) { this.role = role; @@ -1440,8 +1588,13 @@ public Builder message(String message) { return this; } + public Builder meta(Map meta) { + this.meta = meta; + return this; + } + public CreateMessageResult build() { - return new CreateMessageResult(role, content, model, stopReason); + return new CreateMessageResult(role, content, model, stopReason, meta); } } }// @formatter:on @@ -1452,92 +1605,105 @@ 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 { - - // backwards compatibility constructor - public ElicitRequest(String message, Map requestedSchema) { - this(message, requestedSchema, null); - } + @JsonProperty("message") String message, + @JsonProperty("requestedSchema") Map requestedSchema, + @JsonProperty("_meta") Map meta) implements Request { - public static Builder builder() { - return new Builder(); - } - - 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 requestedSchema(Map requestedSchema) { - this.requestedSchema = requestedSchema; - return this; - } - - public Builder meta(Map meta) { - this.meta = meta; - return this; - } + // backwards compatibility constructor + public ElicitRequest(String message, Map requestedSchema) { + this(message, requestedSchema, null); + } - public Builder progressToken(String progressToken) { - if (this.meta == null) { - this.meta = new HashMap<>(); - } - this.meta.put("progressToken", progressToken); - return this; - } + public static Builder builder() { + return new Builder(); + } - public ElicitRequest build() { - return new ElicitRequest(message, requestedSchema, meta); - } - } - }// @formatter:on + 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 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 @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 @@ -1545,8 +1711,9 @@ 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); @@ -1578,26 +1745,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 @@ -1608,64 +1787,76 @@ 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 static class Builder { - private LoggingLevel level = LoggingLevel.INFO; - private String logger = "server"; - private String data; - - public Builder level(LoggingLevel level) { - this.level = level; - return this; - } - - public Builder logger(String logger) { - this.logger = logger; - return this; - } + public LoggingMessageNotification(LoggingLevel level, String logger, String data) { + this(level, logger, data, null); + } - public Builder data(String data) { - this.data = data; - return this; - } + public static Builder builder() { + return new Builder(); + } - public LoggingMessageNotification build() { - return new LoggingMessageNotification(level, logger, data); - } - } - }// @formatter:on + public static class Builder { + private LoggingLevel level = LoggingLevel.INFO; + private String logger = "server"; + private String data; + private Map meta; + + public Builder level(LoggingLevel level) { + this.level = level; + return this; + } + + public Builder logger(String logger) { + this.logger = logger; + return this; + } + + 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), - @JsonProperty("info") INFO(1), - @JsonProperty("notice") NOTICE(2), - @JsonProperty("warning") WARNING(3), - @JsonProperty("error") ERROR(4), - @JsonProperty("critical") CRITICAL(5), - @JsonProperty("alert") ALERT(6), - @JsonProperty("emergency") EMERGENCY(7); - - private final int level; - - LoggingLevel(int level) { - this.level = level; - } + @JsonProperty("debug") DEBUG(0), + @JsonProperty("info") INFO(1), + @JsonProperty("notice") NOTICE(2), + @JsonProperty("warning") WARNING(3), + @JsonProperty("error") ERROR(4), + @JsonProperty("critical") CRITICAL(5), + @JsonProperty("alert") ALERT(6), + @JsonProperty("emergency") EMERGENCY(7); + + private final int level; + + LoggingLevel(int level) { + this.level = level; + } - public int level() { - return level; - } + public int level() { + return level; + } - } // @formatter:on + } // @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) @@ -1686,62 +1877,67 @@ public sealed interface CompleteReference permits PromptReference, ResourceRefer @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record PromptReference(// @formatter:off - @JsonProperty("type") String type, - @JsonProperty("name") String name, - @JsonProperty("title") String title ) implements McpSchema.CompleteReference, BaseMetadata { + @JsonProperty("type") String type, + @JsonProperty("name") String name, + @JsonProperty("title") String title ) implements McpSchema.CompleteReference, BaseMetadata { - public PromptReference(String name) { - this("ref/prompt", name, null); - } + public PromptReference(String name) { + this("ref/prompt", name, null); + } - @Override - public String identifier() { - return name(); - } - }// @formatter:on + @Override + public String identifier() { + return name(); + } + }// @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record ResourceReference(// @formatter:off - @JsonProperty("type") String type, - @JsonProperty("uri") String uri) implements McpSchema.CompleteReference { + @JsonProperty("type") String type, + @JsonProperty("uri") String uri) implements McpSchema.CompleteReference { - public ResourceReference(String uri) { - this("ref/resource", uri); - } + public ResourceReference(String uri) { + this("ref/resource", uri); + } - @Override - public String identifier() { - return uri(); - } - }// @formatter:on + @Override + public String identifier() { + return uri(); + } + }// @formatter:on @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 CompleteResult(CompleteCompletion completion) { + this(completion, null); + } - public record CompleteCompletion( - @JsonProperty("values") List values, - @JsonProperty("total") Integer total, - @JsonProperty("hasMore") Boolean hasMore) { - }// @formatter:on + public record CompleteCompletion( + @JsonProperty("values") List values, + @JsonProperty("total") Integer total, + @JsonProperty("hasMore") Boolean hasMore) { + }// @formatter:on } // --------------------------- @@ -1755,6 +1951,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"; @@ -1779,11 +1977,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); } /** @@ -1791,7 +1994,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); } /** @@ -1814,16 +2017,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); } /** @@ -1846,16 +2055,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 @@ -1863,7 +2082,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); } /** @@ -1899,17 +2118,24 @@ public Double priority() { * 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 link. */ @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record ResourceLink( // @formatter:off - @JsonProperty("name") String name, - @JsonProperty("title") String title, - @JsonProperty("uri") String uri, - @JsonProperty("description") String description, - @JsonProperty("mimeType") String mimeType, - @JsonProperty("size") Long size, - @JsonProperty("annotations") Annotations annotations) implements Annotated, Content, ResourceContent { // @formatter:on + @JsonProperty("name") String name, + @JsonProperty("title") String title, + @JsonProperty("uri") String uri, + @JsonProperty("description") String description, + @JsonProperty("mimeType") String mimeType, + @JsonProperty("size") Long size, + @JsonProperty("annotations") Annotations annotations, + @JsonProperty("_meta") Map meta) implements Annotated, Content, ResourceContent { // @formatter:on + + public ResourceLink(String name, String title, String uri, String description, String mimeType, Long size, + Annotations annotations) { + this(name, title, uri, description, mimeType, size, annotations, null); + } public static Builder builder() { return new Builder(); @@ -1931,6 +2157,8 @@ public static class Builder { private Long size; + private Map meta; + public Builder name(String name) { this.name = name; return this; @@ -1966,11 +2194,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, title, uri, description, mimeType, size, annotations); + return new ResourceLink(name, title, uri, description, mimeType, size, annotations, meta); } } @@ -1988,13 +2221,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 @@ -2006,16 +2245,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("nextCursor") String nextCursor, + @JsonProperty("_meta") Map meta) implements Result { public ListRootsResult(List roots) { this(roots, 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 786be329..f52526e4 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java @@ -24,6 +24,7 @@ /** * @author Christian Tzolov + * @author Anurag Pant */ public class McpSchemaTests { @@ -46,11 +47,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 +80,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 +104,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 +170,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 +178,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 +201,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,12 +210,14 @@ 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 void testResourceLink() throws Exception { McpSchema.ResourceLink resourceLink = new McpSchema.ResourceLink("main.rs", "Main file", - "file:///project/src/main.rs", "Primary application entry point", "text/x-rust", null, null); + "file:///project/src/main.rs", "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) @@ -216,14 +225,14 @@ void testResourceLink() throws Exception { .isObject() .isEqualTo( json(""" - {"type":"resource_link","name":"main.rs","title":"Main file","uri":"file:///project/src/main.rs","description":"Primary application entry point","mimeType":"text/x-rust"}""")); + {"type":"resource_link","name":"main.rs","title":"Main file","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"); @@ -231,6 +240,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 @@ -307,8 +317,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 +328,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 +385,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 +394,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 +430,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", - "Test Template", "A test resource template", "text/plain", annotations); + "Test Template", "A test resource template", "text/plain", annotations, meta); String value = mapper.writeValueAsString(template); assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) @@ -427,7 +441,7 @@ void testResourceTemplate() throws Exception { .isObject() .isEqualTo( json(""" - {"uriTemplate":"resource://{param}/test","name":"Test Template","title":"Test Template","description":"A test resource template","mimeType":"text/plain","annotations":{"audience":["user"],"priority":0.5}}""")); + {"uriTemplate":"resource://{param}/test","name":"Test Template","title":"Test Template","description":"A test resource template","mimeType":"text/plain","annotations":{"audience":["user"],"priority":0.5},"_meta":{"metaKey":"metaValue"}}""")); } @Test @@ -438,8 +452,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 +463,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 +488,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 +537,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 +546,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 @@ -541,7 +559,7 @@ void testPrompt() throws Exception { false); McpSchema.Prompt prompt = new McpSchema.Prompt("test-prompt", "Test Prompt", "A test prompt", - Arrays.asList(arg1, arg2)); + Arrays.asList(arg1, arg2), Map.of("metaKey", "metaValue")); String value = mapper.writeValueAsString(prompt); assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) @@ -549,7 +567,7 @@ void testPrompt() throws Exception { .isObject() .isEqualTo( json(""" - {"name":"test-prompt","title":"Test Prompt","description":"A test prompt","arguments":[{"name":"arg1","title":"First argument","description":"First argument","required":true},{"name":"arg2","title":"Second argument","description":"Second argument","required":false}]}""")); + {"name":"test-prompt","title":"Test Prompt","description":"A test prompt","arguments":[{"name":"arg1","title":"First argument","description":"First argument","required":true},{"name":"arg2","title":"Second argument","description":"Second argument","required":false}],"_meta":{"metaKey":"metaValue"}}""")); } @Test @@ -805,6 +823,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", "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 = """ @@ -1285,14 +1330,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 @@ -1319,7 +1364,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) @@ -1327,19 +1372,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