diff --git a/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java b/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java index cd73c0fc..9be585ce 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java +++ b/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java @@ -142,7 +142,16 @@ public static final class ErrorCodes { } public sealed interface Request permits InitializeRequest, CallToolRequest, CreateMessageRequest, ElicitRequest, - CompleteRequest, GetPromptRequest { + CompleteRequest, GetPromptRequest, PaginatedRequest, ReadResourceRequest { + + Map meta(); + + default String progressToken() { + if (meta() != null && meta().containsKey("progressToken")) { + return meta().get("progressToken").toString(); + } + return null; + } } @@ -194,40 +203,40 @@ public sealed interface JSONRPCMessage permits JSONRPCRequest, JSONRPCNotificati // TODO: batching support // @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) public record JSONRPCRequest( // @formatter:off - @JsonProperty("jsonrpc") String jsonrpc, - @JsonProperty("method") String method, - @JsonProperty("id") Object id, - @JsonProperty("params") Object params) implements JSONRPCMessage { - } // @formatter:on + @JsonProperty("jsonrpc") String jsonrpc, + @JsonProperty("method") String method, + @JsonProperty("id") Object id, + @JsonProperty("params") Object params) implements JSONRPCMessage { + } // @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) // TODO: batching support // @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) public record JSONRPCNotification( // @formatter:off - @JsonProperty("jsonrpc") String jsonrpc, - @JsonProperty("method") String method, - @JsonProperty("params") Object params) implements JSONRPCMessage { - } // @formatter:on + @JsonProperty("jsonrpc") String jsonrpc, + @JsonProperty("method") String method, + @JsonProperty("params") Object params) implements JSONRPCMessage { + } // @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) // TODO: batching support // @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) public record JSONRPCResponse( // @formatter:off - @JsonProperty("jsonrpc") String jsonrpc, - @JsonProperty("id") Object id, - @JsonProperty("result") Object result, - @JsonProperty("error") JSONRPCError error) implements JSONRPCMessage { - - @JsonInclude(JsonInclude.Include.NON_ABSENT) - @JsonIgnoreProperties(ignoreUnknown = true) - public record JSONRPCError( - @JsonProperty("code") int code, - @JsonProperty("message") String message, - @JsonProperty("data") Object data) { - } - }// @formatter:on + @JsonProperty("jsonrpc") String jsonrpc, + @JsonProperty("id") Object id, + @JsonProperty("result") Object result, + @JsonProperty("error") JSONRPCError error) implements JSONRPCMessage { + + @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) + public record JSONRPCError( + @JsonProperty("code") int code, + @JsonProperty("message") String message, + @JsonProperty("data") Object data) { + } + }// @formatter:on // --------------------------- // Initialization @@ -235,19 +244,24 @@ 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) implements Request { - } // @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) { + } // @formatter:on /** * Clients can implement additional features to enrich connected MCP servers with @@ -268,184 +282,184 @@ public record InitializeResult( // @formatter:off @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record ClientCapabilities( // @formatter:off - @JsonProperty("experimental") Map experimental, - @JsonProperty("roots") RootCapabilities roots, - @JsonProperty("sampling") Sampling sampling, - @JsonProperty("elicitation") Elicitation elicitation) { - - /** - * Roots define the boundaries of where servers can operate within the filesystem, - * allowing them to understand which directories and files they have access to. - * Servers can request the list of roots from supporting clients and - * receive notifications when that list changes. - * - * @param listChanged Whether the client would send notification about roots - * has changed since the last time the server checked. - */ - @JsonInclude(JsonInclude.Include.NON_ABSENT) - @JsonIgnoreProperties(ignoreUnknown = true) - public record RootCapabilities( - @JsonProperty("listChanged") Boolean listChanged) { - } - - /** - * Provides a standardized way for servers to request LLM - * sampling ("completions" or "generations") from language - * models via clients. This flow allows clients to maintain - * control over model access, selection, and permissions - * while enabling servers to leverage AI capabilities—with - * no server API keys necessary. Servers can request text or - * image-based interactions and optionally include context - * from MCP servers in their prompts. - */ - @JsonInclude(JsonInclude.Include.NON_ABSENT) - public record Sampling() { - } - - /** - * Provides a standardized way for servers to request additional - * information from users through the client during interactions. - * This flow allows clients to maintain control over user - * interactions and data sharing while enabling servers to gather - * necessary information dynamically. Servers can request structured - * data from users with optional JSON schemas to validate responses. - */ - @JsonInclude(JsonInclude.Include.NON_ABSENT) - public record Elicitation() { - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private Map experimental; - private RootCapabilities roots; - private Sampling sampling; - private Elicitation elicitation; - - public Builder experimental(Map experimental) { - this.experimental = experimental; - return this; - } - - public Builder roots(Boolean listChanged) { - this.roots = new RootCapabilities(listChanged); - return this; - } - - public Builder sampling() { - this.sampling = new Sampling(); - return this; - } - - public Builder elicitation() { - this.elicitation = new Elicitation(); - return this; - } - - public ClientCapabilities build() { - return new ClientCapabilities(experimental, roots, sampling, elicitation); - } - } - }// @formatter:on + @JsonProperty("experimental") Map experimental, + @JsonProperty("roots") RootCapabilities roots, + @JsonProperty("sampling") Sampling sampling, + @JsonProperty("elicitation") Elicitation elicitation) { + + /** + * Roots define the boundaries of where servers can operate within the filesystem, + * allowing them to understand which directories and files they have access to. + * Servers can request the list of roots from supporting clients and + * receive notifications when that list changes. + * + * @param listChanged Whether the client would send notification about roots + * has changed since the last time the server checked. + */ + @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) + public record RootCapabilities( + @JsonProperty("listChanged") Boolean listChanged) { + } + + /** + * Provides a standardized way for servers to request LLM + * sampling ("completions" or "generations") from language + * models via clients. This flow allows clients to maintain + * control over model access, selection, and permissions + * while enabling servers to leverage AI capabilities—with + * no server API keys necessary. Servers can request text or + * image-based interactions and optionally include context + * from MCP servers in their prompts. + */ + @JsonInclude(JsonInclude.Include.NON_ABSENT) + public record Sampling() { + } + + /** + * Provides a standardized way for servers to request additional + * information from users through the client during interactions. + * This flow allows clients to maintain control over user + * interactions and data sharing while enabling servers to gather + * necessary information dynamically. Servers can request structured + * data from users with optional JSON schemas to validate responses. + */ + @JsonInclude(JsonInclude.Include.NON_ABSENT) + public record Elicitation() { + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Map experimental; + private RootCapabilities roots; + private Sampling sampling; + private Elicitation elicitation; + + public Builder experimental(Map experimental) { + this.experimental = experimental; + return this; + } + + public Builder roots(Boolean listChanged) { + this.roots = new RootCapabilities(listChanged); + return this; + } + + public Builder sampling() { + this.sampling = new Sampling(); + return this; + } + + public Builder elicitation() { + this.elicitation = new Elicitation(); + return this; + } + + public ClientCapabilities build() { + return new ClientCapabilities(experimental, roots, sampling, elicitation); + } + } + }// @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record ServerCapabilities( // @formatter:off - @JsonProperty("completions") CompletionCapabilities completions, - @JsonProperty("experimental") Map experimental, - @JsonProperty("logging") LoggingCapabilities logging, - @JsonProperty("prompts") PromptCapabilities prompts, - @JsonProperty("resources") ResourceCapabilities resources, - @JsonProperty("tools") ToolCapabilities tools) { - - @JsonInclude(JsonInclude.Include.NON_ABSENT) - public record CompletionCapabilities() { - } - - @JsonInclude(JsonInclude.Include.NON_ABSENT) - public record LoggingCapabilities() { - } - - @JsonInclude(JsonInclude.Include.NON_ABSENT) - public record PromptCapabilities( - @JsonProperty("listChanged") Boolean listChanged) { - } - - @JsonInclude(JsonInclude.Include.NON_ABSENT) - public record ResourceCapabilities( - @JsonProperty("subscribe") Boolean subscribe, - @JsonProperty("listChanged") Boolean listChanged) { - } - - @JsonInclude(JsonInclude.Include.NON_ABSENT) - public record ToolCapabilities( - @JsonProperty("listChanged") Boolean listChanged) { - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private CompletionCapabilities completions; - private Map experimental; - private LoggingCapabilities logging = new LoggingCapabilities(); - private PromptCapabilities prompts; - private ResourceCapabilities resources; - private ToolCapabilities tools; - - public Builder completions() { - this.completions = new CompletionCapabilities(); - return this; - } - - public Builder experimental(Map experimental) { - this.experimental = experimental; - return this; - } - - public Builder logging() { - this.logging = new LoggingCapabilities(); - return this; - } - - public Builder prompts(Boolean listChanged) { - this.prompts = new PromptCapabilities(listChanged); - return this; - } - - public Builder resources(Boolean subscribe, Boolean listChanged) { - this.resources = new ResourceCapabilities(subscribe, listChanged); - return this; - } - - public Builder tools(Boolean listChanged) { - this.tools = new ToolCapabilities(listChanged); - return this; - } - - public ServerCapabilities build() { - return new ServerCapabilities(completions, experimental, logging, prompts, resources, tools); - } - } - } // @formatter:on + @JsonProperty("completions") CompletionCapabilities completions, + @JsonProperty("experimental") Map experimental, + @JsonProperty("logging") LoggingCapabilities logging, + @JsonProperty("prompts") PromptCapabilities prompts, + @JsonProperty("resources") ResourceCapabilities resources, + @JsonProperty("tools") ToolCapabilities tools) { + + @JsonInclude(JsonInclude.Include.NON_ABSENT) + public record CompletionCapabilities() { + } + + @JsonInclude(JsonInclude.Include.NON_ABSENT) + public record LoggingCapabilities() { + } + + @JsonInclude(JsonInclude.Include.NON_ABSENT) + public record PromptCapabilities( + @JsonProperty("listChanged") Boolean listChanged) { + } + + @JsonInclude(JsonInclude.Include.NON_ABSENT) + public record ResourceCapabilities( + @JsonProperty("subscribe") Boolean subscribe, + @JsonProperty("listChanged") Boolean listChanged) { + } + + @JsonInclude(JsonInclude.Include.NON_ABSENT) + public record ToolCapabilities( + @JsonProperty("listChanged") Boolean listChanged) { + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private CompletionCapabilities completions; + private Map experimental; + private LoggingCapabilities logging = new LoggingCapabilities(); + private PromptCapabilities prompts; + private ResourceCapabilities resources; + private ToolCapabilities tools; + + public Builder completions() { + this.completions = new CompletionCapabilities(); + return this; + } + + public Builder experimental(Map experimental) { + this.experimental = experimental; + return this; + } + + public Builder logging() { + this.logging = new LoggingCapabilities(); + return this; + } + + public Builder prompts(Boolean listChanged) { + this.prompts = new PromptCapabilities(listChanged); + return this; + } + + public Builder resources(Boolean subscribe, Boolean listChanged) { + this.resources = new ResourceCapabilities(subscribe, listChanged); + return this; + } + + public Builder tools(Boolean listChanged) { + this.tools = new ToolCapabilities(listChanged); + return this; + } + + public ServerCapabilities build() { + return new ServerCapabilities(completions, experimental, logging, prompts, resources, tools); + } + } + } // @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record Implementation(// @formatter:off - @JsonProperty("name") String name, - @JsonProperty("version") String version) { - } // @formatter:on + @JsonProperty("name") String name, + @JsonProperty("version") String version) { + } // @formatter:on // Existing Enums and Base Types (from previous implementation) public enum Role {// @formatter:off - @JsonProperty("user") USER, - @JsonProperty("assistant") ASSISTANT - }// @formatter:on + @JsonProperty("user") USER, + @JsonProperty("assistant") ASSISTANT + }// @formatter:on // --------------------------- // Resource Interfaces @@ -475,9 +489,9 @@ public interface Annotated { @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record Annotations( // @formatter:off - @JsonProperty("audience") List audience, - @JsonProperty("priority") Double priority) { - } // @formatter:on + @JsonProperty("audience") List audience, + @JsonProperty("priority") Double priority) { + } // @formatter:on /** * A common interface for resource content, which includes metadata about the resource @@ -520,72 +534,72 @@ public interface ResourceContent { @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 { - - /** - * @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); - } - - 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 Builder name(String name) { - this.name = name; - return this; - } - - public Builder description(String description) { - this.description = description; - return this; - } - - public Builder mimeType(String mimeType) { - this.mimeType = mimeType; - return this; - } - - public Builder size(Long size) { - this.size = size; - return this; - } - - public Builder annotations(Annotations annotations) { - this.annotations = annotations; - 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); - } - } - } // @formatter:on + @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 { + + /** + * @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); + } + + 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 Builder name(String name) { + this.name = name; + return this; + } + + public Builder description(String description) { + this.description = description; + return this; + } + + public Builder mimeType(String mimeType) { + this.mimeType = mimeType; + return this; + } + + public Builder size(Long size) { + this.size = size; + return this; + } + + public Builder annotations(Annotations annotations) { + this.annotations = annotations; + 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); + } + } + } // @formatter:on /** * Resource templates allow servers to expose parameterized resources using URI @@ -606,38 +620,43 @@ public Resource build() { @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) implements Annotated { + } // @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) { + } // @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) { + } // @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record ReadResourceRequest( // @formatter:off - @JsonProperty("uri") String uri){ - } // @formatter:on + @JsonProperty("uri") String uri, + @JsonProperty("_meta") Map meta) implements Request { + + 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){ + } // @formatter:on /** * Sent from the client to request resources/updated notifications from the server @@ -649,14 +668,14 @@ public record ReadResourceResult( // @formatter:off @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record SubscribeRequest( // @formatter:off - @JsonProperty("uri") String uri){ - } // @formatter:on + @JsonProperty("uri") String uri){ + } // @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){ + } // @formatter:on /** * The contents of a specific resource or sub-resource. @@ -691,10 +710,10 @@ public sealed interface ResourceContents permits TextResourceContents, BlobResou @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) implements ResourceContents { + } // @formatter:on /** * Binary contents of a resource. @@ -708,10 +727,10 @@ public record TextResourceContents( // @formatter:off @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) implements ResourceContents { + } // @formatter:on // --------------------------- // Prompt Interfaces @@ -726,10 +745,10 @@ public record BlobResourceContents( // @formatter:off @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) { + } // @formatter:on /** * Describes an argument that a prompt can accept. @@ -741,10 +760,10 @@ public record Prompt( // @formatter:off @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record PromptArgument( // @formatter:off - @JsonProperty("name") String name, - @JsonProperty("description") String description, - @JsonProperty("required") Boolean required) { - }// @formatter:on + @JsonProperty("name") String name, + @JsonProperty("description") String description, + @JsonProperty("required") Boolean required) { + }// @formatter:on /** * Describes a message returned as part of a prompt. @@ -758,9 +777,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. @@ -772,9 +791,9 @@ public record PromptMessage( // @formatter:off @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) { + }// @formatter:on /** * Used by the client to get a prompt provided by the server. @@ -785,22 +804,27 @@ public record ListPromptsResult( // @formatter:off @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record GetPromptRequest(// @formatter:off - @JsonProperty("name") String name, - @JsonProperty("arguments") Map arguments) implements Request { - }// @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 + @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 // --------------------------- // Tool Interfaces @@ -815,31 +839,31 @@ public record GetPromptResult( // @formatter:off @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) { + }// @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 @@ -858,20 +882,20 @@ 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) { + @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); - } + public Tool(String name, String description, String schema) { + this(name, description, parseSchema(schema), 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, ToolAnnotations annotations) { + this(name, description, parseSchema(schema), annotations); + } - } // @formatter:on + } // @formatter:on private static JsonSchema parseSchema(String schema) { try { @@ -893,134 +917,181 @@ private static JsonSchema parseSchema(String schema) { @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record CallToolRequest(// @formatter:off - @JsonProperty("name") String name, - @JsonProperty("arguments") Map arguments) implements Request { - - public CallToolRequest(String name, String jsonArguments) { - this(name, parseJsonArguments(jsonArguments)); - } - - 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); - } - } - }// @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) { - - /** - * 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; - } - - /** - * 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; - } - - /** - * Builds a new {@link CallToolResult} instance. - * @return a new CallToolResult instance - */ - public CallToolResult build() { - return new CallToolResult(content, isError); - } - } - - } // @formatter:on + @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(); + } + + 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: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) { + + /** + * 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; + } + + /** + * 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; + } + + /** + * Builds a new {@link CallToolResult} instance. + * @return a new CallToolResult instance + */ + public CallToolResult build() { + return new CallToolResult(content, isError); + } + } + + } // @formatter:on // --------------------------- // Sampling Interfaces @@ -1028,53 +1099,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) { - - public static Builder builder() { - return new Builder(); - } - - 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 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 speedPriority(Double speedPriority) { - this.speedPriority = speedPriority; - return this; - } - - public Builder intelligencePriority(Double intelligencePriority) { - this.intelligencePriority = intelligencePriority; - return this; - } - - public ModelPreferences build() { - return new ModelPreferences(hints, costPriority, speedPriority, 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 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 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 speedPriority(Double speedPriority) { + this.speedPriority = speedPriority; + return this; + } + + public Builder intelligencePriority(Double intelligencePriority) { + this.intelligencePriority = intelligencePriority; + return this; + } + + public ModelPreferences build() { + return new ModelPreferences(hints, costPriority, speedPriority, intelligencePriority); + } + } } // @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) @@ -1088,159 +1159,184 @@ 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) implements Request { - - 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; - - 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; - } + @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 CreateMessageRequest build() { - return new CreateMessageRequest(messages, modelPreferences, systemPrompt, - includeContext, temperature, maxTokens, stopSequences, metadata); - } - } - }// @formatter:on + 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; + } + + 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) 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; - - 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); - } - } - - 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 Builder role(Role role) { - this.role = role; - return this; - } - - public Builder content(Content content) { - this.content = content; - return this; - } - - public Builder model(String model) { - this.model = model; - 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 CreateMessageResult build() { - return new CreateMessageResult(role, content, model, stopReason); - } - } - }// @formatter:on + @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; + + 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); + } + } + + 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 Builder role(Role role) { + this.role = role; + return this; + } + + public Builder content(Content content) { + this.content = content; + return this; + } + + public Builder model(String model) { + this.model = model; + 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 CreateMessageResult build() { + return new CreateMessageResult(role, content, model, stopReason); + } + } + }// @formatter:on // Elicitation /** @@ -1252,75 +1348,108 @@ public CreateMessageResult build() { @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record ElicitRequest(// @formatter:off - @JsonProperty("message") String message, - @JsonProperty("requestedSchema") Map requestedSchema) implements Request { - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private String message; - private Map requestedSchema; - - public Builder message(String message) { - this.message = message; - return this; - } + @JsonProperty("message") String message, + @JsonProperty("requestedSchema") Map requestedSchema, + @JsonProperty("_meta") Map meta) implements Request { - public Builder requestedSchema(Map requestedSchema) { - this.requestedSchema = requestedSchema; - return this; - } + // backwards compatibility constructor + public ElicitRequest(String message, Map requestedSchema) { + this(message, requestedSchema, null); + } - public ElicitRequest build() { - return new ElicitRequest(message, requestedSchema); - } - } - }// @formatter:on + 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; + } + + 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) { - - public enum Action { - @JsonProperty("accept") ACCEPT, - @JsonProperty("decline") DECLINE, - @JsonProperty("cancel") CANCEL - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private Action action; - private Map content; - - public Builder message(Action action) { - this.action = action; - return this; - } - - public Builder content(Map content) { - this.content = content; - return this; - } - - public ElicitResult build() { - return new ElicitResult(action, content); - } - } - }// @formatter:on + @JsonProperty("action") Action action, + @JsonProperty("content") Map content) { + + public enum Action { + @JsonProperty("accept") ACCEPT, + @JsonProperty("decline") DECLINE, + @JsonProperty("cancel") CANCEL + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Action action; + private Map content; + + public Builder message(Action action) { + this.action = action; + return this; + } + + public Builder content(Map content) { + this.content = content; + return this; + } + + public ElicitResult build() { + return new ElicitResult(action, content); + } + } + }// @formatter:on // --------------------------- // Pagination Interfaces // --------------------------- @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) - public record PaginatedRequest(@JsonProperty("cursor") String cursor) { + public record PaginatedRequest(// @formatter:off + @JsonProperty("cursor") String cursor, + @JsonProperty("_meta") Map meta) implements Request { // @formatter:on + + public PaginatedRequest(String cursor) { + this(cursor, null); + } + + /** + * Creates a new paginated request with an empty cursor. + */ + public PaginatedRequest() { + this(null); + } } @JsonInclude(JsonInclude.Include.NON_ABSENT) @@ -1331,12 +1460,25 @@ public record PaginatedResult(@JsonProperty("nextCursor") String nextCursor) { // --------------------------- // Progress and Logging // --------------------------- + /** + * The Model Context Protocol (MCP) supports optional progress tracking for + * long-running operations through notification messages. Either side can send + * progress notifications to provide updates about operation status. + * + * @param progressToken A unique token to identify the progress notification. MUST be + * unique across all active requests. + * @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. + */ + @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) { - }// @formatter:on + @JsonProperty("progressToken") String progressToken, + @JsonProperty("progress") Double progress, + @JsonProperty("total") Double total, + @JsonProperty("message") String message) { + }// @formatter:on /** * The Model Context Protocol (MCP) provides a standardized way for servers to send @@ -1346,8 +1488,8 @@ public record ProgressNotification(// @formatter:off */ @JsonIgnoreProperties(ignoreUnknown = true) public record ResourcesUpdatedNotification(// @formatter:off - @JsonProperty("uri") String uri) { - }// @formatter:on + @JsonProperty("uri") String uri) { + }// @formatter:on /** * The Model Context Protocol (MCP) provides a standardized way for servers to send @@ -1361,61 +1503,61 @@ public record ResourcesUpdatedNotification(// @formatter:off */ @JsonIgnoreProperties(ignoreUnknown = true) public record LoggingMessageNotification(// @formatter:off - @JsonProperty("level") LoggingLevel level, - @JsonProperty("logger") String logger, - @JsonProperty("data") String data) { - - 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; - } + @JsonProperty("level") LoggingLevel level, + @JsonProperty("logger") String logger, + @JsonProperty("data") String data) { + + 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 Builder data(String data) { + this.data = data; + return this; + } + + public LoggingMessageNotification build() { + return new LoggingMessageNotification(level, logger, data); + } + } + }// @formatter:on - public Builder logger(String logger) { - this.logger = logger; - return this; - } - - public Builder data(String data) { - this.data = data; - return this; - } + 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); - public LoggingMessageNotification build() { - return new LoggingMessageNotification(level, logger, data); - } - } - }// @formatter:on + private final int level; - 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; - } + 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) @@ -1436,56 +1578,61 @@ 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) implements McpSchema.CompleteReference { + @JsonProperty("type") String type, + @JsonProperty("name") String name) implements McpSchema.CompleteReference { - public PromptReference(String name) { - this("ref/prompt", name); - } + public PromptReference(String name) { + this("ref/prompt", name); + } - @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) implements Request { - - public record CompleteArgument( - @JsonProperty("name") String name, - @JsonProperty("value") String value) { - }// @formatter:on + @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 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 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 } // --------------------------- @@ -1523,8 +1670,8 @@ 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) implements Annotated, Content { // @formatter:on public TextContent(String content) { this(null, content); @@ -1558,9 +1705,9 @@ 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) implements Annotated, Content { // @formatter:on /** * @deprecated Only exists for backwards-compatibility purposes. Use @@ -1590,16 +1737,16 @@ 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) implements Annotated, Content { // @formatter:on } @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) implements Annotated, Content { // @formatter:on /** * @deprecated Only exists for backwards-compatibility purposes. Use @@ -1646,12 +1793,12 @@ public Double priority() { @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record ResourceLink( // @formatter:off - @JsonProperty("name") String name, - @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("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 public static Builder builder() { return new Builder(); @@ -1727,9 +1874,9 @@ public ResourceLink build() { @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) { + } // @formatter:on /** * The client's response to a roots/list request from the server. This result contains @@ -1745,12 +1892,12 @@ public record Root( // @formatter:off @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) public record ListRootsResult( // @formatter:off - @JsonProperty("roots") List roots, - @JsonProperty("nextCursor") String nextCursor) { - - public ListRootsResult(List roots) { - this(roots, null); - } - } // @formatter:on + @JsonProperty("roots") List roots, + @JsonProperty("nextCursor") String nextCursor) { + + public ListRootsResult(List roots) { + this(roots, 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 5b76ff09..ea063e4e 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java @@ -3,22 +3,23 @@ */ 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; /** @@ -106,6 +107,43 @@ void testAudioContentDeserialization() throws Exception { assertThat(audioContent.mimeType()).isEqualTo("audio/wav"); } + @Test + void testCreateMessageRequestWithMeta() throws Exception { + McpSchema.TextContent content = new McpSchema.TextContent("User message"); + McpSchema.SamplingMessage message = new McpSchema.SamplingMessage(McpSchema.Role.USER, content); + McpSchema.ModelHint hint = new McpSchema.ModelHint("gpt-4"); + McpSchema.ModelPreferences preferences = new McpSchema.ModelPreferences(Collections.singletonList(hint), 0.3, + 0.7, 0.9); + + Map metadata = new HashMap<>(); + metadata.put("session", "test-session"); + + Map meta = new HashMap<>(); + meta.put("progressToken", "create-message-token-456"); + + McpSchema.CreateMessageRequest request = McpSchema.CreateMessageRequest.builder() + .messages(Collections.singletonList(message)) + .modelPreferences(preferences) + .systemPrompt("You are a helpful assistant") + .includeContext(McpSchema.CreateMessageRequest.ContextInclusionStrategy.THIS_SERVER) + .temperature(0.7) + .maxTokens(1000) + .stopSequences(Arrays.asList("STOP", "END")) + .metadata(metadata) + .meta(meta) + .build(); + + String value = mapper.writeValueAsString(request); + assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) + .when(Option.IGNORING_EXTRA_ARRAY_ITEMS) + .isObject() + .containsEntry("_meta", Map.of("progressToken", "create-message-token-456")); + + // Test Request interface methods + assertThat(request.meta()).isEqualTo(meta); + assertThat(request.progressToken()).isEqualTo("create-message-token-456"); + } + @Test void testEmbeddedResource() throws Exception { McpSchema.TextResourceContents resourceContents = new McpSchema.TextResourceContents("resource://test", @@ -444,6 +482,36 @@ void testReadResourceRequest() throws Exception { {"uri":"resource://test"}""")); } + @Test + void testReadResourceRequestWithMeta() throws Exception { + Map meta = new HashMap<>(); + meta.put("progressToken", "read-resource-token-123"); + + McpSchema.ReadResourceRequest request = new McpSchema.ReadResourceRequest("resource://test", meta); + + String value = mapper.writeValueAsString(request); + assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) + .when(Option.IGNORING_EXTRA_ARRAY_ITEMS) + .isObject() + .isEqualTo(json(""" + {"uri":"resource://test","_meta":{"progressToken":"read-resource-token-123"}}""")); + + // Test Request interface methods + assertThat(request.meta()).isEqualTo(meta); + assertThat(request.progressToken()).isEqualTo("read-resource-token-123"); + } + + @Test + void testReadResourceRequestDeserialization() throws Exception { + McpSchema.ReadResourceRequest request = mapper.readValue(""" + {"uri":"resource://test","_meta":{"progressToken":"test-token"}}""", + McpSchema.ReadResourceRequest.class); + + assertThat(request.uri()).isEqualTo("resource://test"); + assertThat(request.meta()).containsEntry("progressToken", "test-token"); + assertThat(request.progressToken()).isEqualTo("test-token"); + } + @Test void testReadResourceResult() throws Exception { McpSchema.TextResourceContents contents1 = new McpSchema.TextResourceContents("resource://test1", "text/plain", @@ -529,6 +597,30 @@ void testGetPromptRequest() throws Exception { .isEqualTo(request); } + @Test + void testGetPromptRequestWithMeta() throws Exception { + Map arguments = new HashMap<>(); + arguments.put("arg1", "value1"); + arguments.put("arg2", 42); + + Map meta = new HashMap<>(); + meta.put("progressToken", "token123"); + + McpSchema.GetPromptRequest request = new McpSchema.GetPromptRequest("test-prompt", arguments, meta); + + String value = mapper.writeValueAsString(request); + assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) + .when(Option.IGNORING_EXTRA_ARRAY_ITEMS) + .isObject() + .isEqualTo( + json(""" + {"name":"test-prompt","arguments":{"arg1":"value1","arg2":42},"_meta":{"progressToken":"token123"}}""")); + + // Test that it implements Request interface methods + assertThat(request.meta()).isEqualTo(meta); + assertThat(request.progressToken()).isEqualTo("token123"); + } + @Test void testGetPromptResult() throws Exception { McpSchema.TextContent content1 = new McpSchema.TextContent("System message"); @@ -775,6 +867,65 @@ void testCallToolRequestJsonArguments() throws Exception { {"name":"test-tool","arguments":{"name":"test","value":42}}""")); } + @Test + void testCallToolRequestWithMeta() throws Exception { + + McpSchema.CallToolRequest request = McpSchema.CallToolRequest.builder() + .name("test-tool") + .arguments(Map.of("name", "test", "value", 42)) + .progressToken("tool-progress-123") + .build(); + String value = mapper.writeValueAsString(request); + + assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) + .when(Option.IGNORING_EXTRA_ARRAY_ITEMS) + .isObject() + .isEqualTo( + json(""" + {"name":"test-tool","arguments":{"name":"test","value":42},"_meta":{"progressToken":"tool-progress-123"}}""")); + + // Test that it implements Request interface methods + assertThat(request.meta()).isEqualTo(Map.of("progressToken", "tool-progress-123")); + assertThat(request.progressToken()).isEqualTo("tool-progress-123"); + } + + @Test + void testCallToolRequestBuilderWithJsonArguments() throws Exception { + Map meta = new HashMap<>(); + meta.put("progressToken", "json-builder-789"); + + McpSchema.CallToolRequest request = McpSchema.CallToolRequest.builder().name("test-tool").arguments(""" + { + "name": "test", + "value": 42 + } + """).meta(meta).build(); + + String value = mapper.writeValueAsString(request); + + assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) + .when(Option.IGNORING_EXTRA_ARRAY_ITEMS) + .isObject() + .isEqualTo( + json(""" + {"name":"test-tool","arguments":{"name":"test","value":42},"_meta":{"progressToken":"json-builder-789"}}""")); + + // Test that it implements Request interface methods + assertThat(request.meta()).isEqualTo(meta); + assertThat(request.progressToken()).isEqualTo("json-builder-789"); + } + + @Test + void testCallToolRequestBuilderNameRequired() { + Map arguments = new HashMap<>(); + arguments.put("name", "test"); + + McpSchema.CallToolRequest.Builder builder = McpSchema.CallToolRequest.builder().arguments(arguments); + + assertThatThrownBy(builder::build).isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("name must not be empty"); + } + @Test void testCallToolResult() throws Exception { McpSchema.TextContent content = new McpSchema.TextContent("Tool execution result"); @@ -991,6 +1142,141 @@ void testCreateElicitationResult() throws Exception { {"action":"accept","content":{"foo":"bar"}}""")); } + @Test + void testElicitRequestWithMeta() throws Exception { + Map requestedSchema = Map.of("type", "object", "required", List.of("name"), "properties", + Map.of("name", Map.of("type", "string"))); + + Map meta = new HashMap<>(); + meta.put("progressToken", "elicit-token-789"); + + McpSchema.ElicitRequest request = McpSchema.ElicitRequest.builder() + .message("Please provide your name") + .requestedSchema(requestedSchema) + .meta(meta) + .build(); + + String value = mapper.writeValueAsString(request); + assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) + .when(Option.IGNORING_EXTRA_ARRAY_ITEMS) + .isObject() + .containsEntry("_meta", Map.of("progressToken", "elicit-token-789")); + + // Test Request interface methods + assertThat(request.meta()).isEqualTo(meta); + assertThat(request.progressToken()).isEqualTo("elicit-token-789"); + } + + // Pagination Tests + + @Test + void testPaginatedRequestNoArgs() throws Exception { + McpSchema.PaginatedRequest request = new McpSchema.PaginatedRequest(); + + String value = mapper.writeValueAsString(request); + assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) + .when(Option.IGNORING_EXTRA_ARRAY_ITEMS) + .isObject() + .isEqualTo(json(""" + {}""")); + + // Test that it implements Request interface methods + assertThat(request.meta()).isNull(); + assertThat(request.progressToken()).isNull(); + } + + @Test + void testPaginatedRequestWithCursor() throws Exception { + McpSchema.PaginatedRequest request = new McpSchema.PaginatedRequest("cursor123"); + + String value = mapper.writeValueAsString(request); + assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) + .when(Option.IGNORING_EXTRA_ARRAY_ITEMS) + .isObject() + .isEqualTo(json(""" + {"cursor":"cursor123"}""")); + + // Test that it implements Request interface methods + assertThat(request.meta()).isNull(); + assertThat(request.progressToken()).isNull(); + } + + @Test + void testPaginatedRequestWithMeta() throws Exception { + Map meta = new HashMap<>(); + meta.put("progressToken", "pagination-progress-456"); + + McpSchema.PaginatedRequest request = new McpSchema.PaginatedRequest("cursor123", meta); + + String value = mapper.writeValueAsString(request); + assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) + .when(Option.IGNORING_EXTRA_ARRAY_ITEMS) + .isObject() + .isEqualTo(json(""" + {"cursor":"cursor123","_meta":{"progressToken":"pagination-progress-456"}}""")); + + // Test that it implements Request interface methods + assertThat(request.meta()).isEqualTo(meta); + assertThat(request.progressToken()).isEqualTo("pagination-progress-456"); + } + + @Test + void testPaginatedRequestDeserialization() throws Exception { + McpSchema.PaginatedRequest request = mapper.readValue(""" + {"cursor":"test-cursor","_meta":{"progressToken":"test-token"}}""", McpSchema.PaginatedRequest.class); + + assertThat(request.cursor()).isEqualTo("test-cursor"); + assertThat(request.meta()).containsEntry("progressToken", "test-token"); + assertThat(request.progressToken()).isEqualTo("test-token"); + } + + // Complete Request Tests + + @Test + void testCompleteRequest() throws Exception { + McpSchema.PromptReference promptRef = new McpSchema.PromptReference("test-prompt"); + McpSchema.CompleteRequest.CompleteArgument argument = new McpSchema.CompleteRequest.CompleteArgument("arg1", + "partial-value"); + + McpSchema.CompleteRequest request = new McpSchema.CompleteRequest(promptRef, argument); + + String value = mapper.writeValueAsString(request); + assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) + .when(Option.IGNORING_EXTRA_ARRAY_ITEMS) + .isObject() + .isEqualTo( + json(""" + {"ref":{"type":"ref/prompt","name":"test-prompt"},"argument":{"name":"arg1","value":"partial-value"}}""")); + + // Test that it implements Request interface methods + assertThat(request.meta()).isNull(); + assertThat(request.progressToken()).isNull(); + } + + @Test + void testCompleteRequestWithMeta() throws Exception { + McpSchema.ResourceReference resourceRef = new McpSchema.ResourceReference("file:///test.txt"); + McpSchema.CompleteRequest.CompleteArgument argument = new McpSchema.CompleteRequest.CompleteArgument("path", + "/partial/path"); + + Map meta = new HashMap<>(); + meta.put("progressToken", "complete-progress-789"); + + McpSchema.CompleteRequest request = new McpSchema.CompleteRequest(resourceRef, argument, meta); + + String value = mapper.writeValueAsString(request); + assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) + .when(Option.IGNORING_EXTRA_ARRAY_ITEMS) + .isObject() + .isEqualTo( + json(""" + {"ref":{"type":"ref/resource","uri":"file:///test.txt"},"argument":{"name":"path","value":"/partial/path"},"_meta":{"progressToken":"complete-progress-789"}}""")); + + // Test that it implements Request interface methods + assertThat(request.meta()).isEqualTo(meta); + assertThat(request.progressToken()).isEqualTo("complete-progress-789"); + } + // Roots Tests @Test @@ -1024,4 +1310,45 @@ void testListRootsResult() throws Exception { } + // Progress Notification Tests + + @Test + void testProgressNotificationWithMessage() throws Exception { + McpSchema.ProgressNotification notification = new McpSchema.ProgressNotification("progress-token-123", 0.5, 1.0, + "Processing file 1 of 2"); + + String value = mapper.writeValueAsString(notification); + assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) + .when(Option.IGNORING_EXTRA_ARRAY_ITEMS) + .isObject() + .isEqualTo( + json(""" + {"progressToken":"progress-token-123","progress":0.5,"total":1.0,"message":"Processing file 1 of 2"}""")); + } + + @Test + void testProgressNotificationDeserialization() throws Exception { + McpSchema.ProgressNotification notification = mapper.readValue(""" + {"progressToken":"token-456","progress":0.75,"total":1.0,"message":"Almost done"}""", + 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"); + } + + @Test + void testProgressNotificationWithoutMessage() throws Exception { + McpSchema.ProgressNotification notification = new McpSchema.ProgressNotification("progress-token-789", 0.25, + null, null); + + String value = mapper.writeValueAsString(notification); + assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) + .when(Option.IGNORING_EXTRA_ARRAY_ITEMS) + .isObject() + .isEqualTo(json(""" + {"progressToken":"progress-token-789","progress":0.25}""")); + } + }