/*
 * Copyright 2024-2026 the original author or authors.
 */

package io.modelcontextprotocol.spec;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.modelcontextprotocol.json.McpJsonMapper;
import io.modelcontextprotocol.json.TypeRef;
import io.modelcontextprotocol.util.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Based on the <a href="http://www.jsonrpc.org/specification">JSON-RPC 2.0
 * specification</a> and the <a href=
 * "https://github.com/modelcontextprotocol/specification/blob/main/schema/2024-11-05/schema.ts">Model
 * Context Protocol Schema</a>.
 *
 * @author Christian Tzolov
 * @author Luca Chang
 * @author Surbhi Bansal
 * @author Anurag Pant
 * @author Dariusz Jędrzejczyk
 */
public final class McpSchema {

	private static final Logger logger = LoggerFactory.getLogger(McpSchema.class);

	private McpSchema() {
	}

	public static final String JSONRPC_VERSION = "2.0";

	public static final String FIRST_PAGE = null;

	/**
	 * The JSON Schema 2020-12 meta-schema URI (SEP-1613). This is the default dialect for
	 * all schema objects in MCP when no explicit {@code $schema} field is present.
	 */
	public static final String JSON_SCHEMA_DIALECT_2020_12 = "https://json-schema.org/draft/2020-12/schema";

	// ---------------------------
	// Method Names
	// ---------------------------

	// Lifecycle Methods
	public static final String METHOD_INITIALIZE = "initialize";

	public static final String METHOD_NOTIFICATION_INITIALIZED = "notifications/initialized";

	public static final String METHOD_PING = "ping";

	public static final String METHOD_NOTIFICATION_PROGRESS = "notifications/progress";

	// Tool Methods
	public static final String METHOD_TOOLS_LIST = "tools/list";

	public static final String METHOD_TOOLS_CALL = "tools/call";

	public static final String METHOD_NOTIFICATION_TOOLS_LIST_CHANGED = "notifications/tools/list_changed";

	// Resources Methods
	public static final String METHOD_RESOURCES_LIST = "resources/list";

	public static final String METHOD_RESOURCES_READ = "resources/read";

	public static final String METHOD_NOTIFICATION_RESOURCES_LIST_CHANGED = "notifications/resources/list_changed";

	public static final String METHOD_NOTIFICATION_RESOURCES_UPDATED = "notifications/resources/updated";

	public static final String METHOD_RESOURCES_TEMPLATES_LIST = "resources/templates/list";

	public static final String METHOD_RESOURCES_SUBSCRIBE = "resources/subscribe";

	public static final String METHOD_RESOURCES_UNSUBSCRIBE = "resources/unsubscribe";

	// Prompt Methods
	public static final String METHOD_PROMPT_LIST = "prompts/list";

	public static final String METHOD_PROMPT_GET = "prompts/get";

	public static final String METHOD_NOTIFICATION_PROMPTS_LIST_CHANGED = "notifications/prompts/list_changed";

	public static final String METHOD_COMPLETION_COMPLETE = "completion/complete";

	// Logging Methods
	public static final String METHOD_LOGGING_SET_LEVEL = "logging/setLevel";

	public static final String METHOD_NOTIFICATION_MESSAGE = "notifications/message";

	// Roots Methods
	public static final String METHOD_ROOTS_LIST = "roots/list";

	public static final String METHOD_NOTIFICATION_ROOTS_LIST_CHANGED = "notifications/roots/list_changed";

	// Sampling Methods
	public static final String METHOD_SAMPLING_CREATE_MESSAGE = "sampling/createMessage";

	// Elicitation Methods
	public static final String METHOD_ELICITATION_CREATE = "elicitation/create";

	public static final String METHOD_NOTIFICATION_ELICITATION_COMPLETE = "notifications/elicitation/complete";

	// ---------------------------
	// JSON-RPC Error Codes
	// ---------------------------
	/**
	 * Standard error codes used in MCP JSON-RPC responses.
	 */
	public static final class ErrorCodes {

		/**
		 * Invalid JSON was received by the server.
		 */
		public static final int PARSE_ERROR = -32700;

		/**
		 * The JSON sent is not a valid Request object.
		 */
		public static final int INVALID_REQUEST = -32600;

		/**
		 * The method does not exist / is not available.
		 */
		public static final int METHOD_NOT_FOUND = -32601;

		/**
		 * Invalid method parameter(s).
		 */
		public static final int INVALID_PARAMS = -32602;

		/**
		 * Internal JSON-RPC error.
		 */
		public static final int INTERNAL_ERROR = -32603;

		/**
		 * Resource not found.
		 */
		public static final int RESOURCE_NOT_FOUND = -32002;

		/**
		 * URL elicitation is required before the request can proceed.
		 */
		public static final int URL_ELICITATION_REQUIRED = -32042;

	}

	/**
	 * Base interface for MCP objects that include optional metadata in the `_meta` field.
	 */
	public interface Meta {

		/**
		 * @see <a href=
		 * "https://modelcontextprotocol.io/specification/2025-06-18/basic/index#meta">Specification</a>
		 * for notes on _meta usage
		 * @return additional metadata related to this resource.
		 */
		Map<String, Object> meta();

	}

	public interface Request extends Meta {

		default Object progressToken() {
			if (meta() != null && meta().containsKey("progressToken")) {
				return meta().get("progressToken");
			}
			return null;
		}

	}

	public interface Result extends Meta {

	}

	public interface Notification extends Meta {

	}

	private static final TypeRef<HashMap<String, Object>> MAP_TYPE_REF = new TypeRef<>() {
	};

	/**
	 * Deserializes a JSON string into a JSONRPCMessage object.
	 * @param jsonMapper The JsonMapper instance to use for deserialization
	 * @param jsonText The JSON string to deserialize
	 * @return A JSONRPCMessage instance using either the {@link JSONRPCRequest},
	 * {@link JSONRPCNotification}, or {@link JSONRPCResponse} classes.
	 * @throws IOException If there's an error during deserialization
	 * @throws IllegalArgumentException If the JSON structure doesn't match any known
	 * message type
	 */
	public static JSONRPCMessage deserializeJsonRpcMessage(McpJsonMapper jsonMapper, String jsonText)
			throws IOException {
		logger.debug("Received JSON message: {}", jsonText);

		var map = jsonMapper.readValue(jsonText, MAP_TYPE_REF);

		// Determine message type based on specific JSON structure
		if (map.containsKey("method") && map.containsKey("id")) {
			return jsonMapper.convertValue(map, JSONRPCRequest.class);
		}
		else if (map.containsKey("method") && !map.containsKey("id")) {
			return jsonMapper.convertValue(map, JSONRPCNotification.class);
		}
		else if (map.containsKey("result") || map.containsKey("error")) {
			return jsonMapper.convertValue(map, JSONRPCResponse.class);
		}

		throw new IllegalArgumentException("Cannot deserialize JSONRPCMessage: " + jsonText);
	}

	// ---------------------------
	// JSON-RPC Message Types
	// ---------------------------
	public interface JSONRPCMessage {

		String jsonrpc();

	}

	/**
	 * A request that expects a response.
	 *
	 * @param jsonrpc The JSON-RPC version (must be "2.0")
	 * @param method The name of the method to be invoked
	 * @param id A unique identifier for the request
	 * @param params Parameters for the method call
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	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

		/**
		 * Constructor that validates MCP-specific ID requirements. Unlike base JSON-RPC,
		 * MCP requires that: (1) Requests MUST include a string or integer ID; (2) The ID
		 * MUST NOT be null
		 */
		public JSONRPCRequest {
			Assert.hasText(jsonrpc, "jsonrpc must not be empty");
			Assert.notNull(id, "MCP requests MUST include an ID - null IDs are not allowed");
			Assert.isTrue(id instanceof String || id instanceof Integer || id instanceof Long,
					"MCP requests MUST have an ID that is either a string or integer");
			Assert.notNull(method, "MCP request method must not be null");
		}

		public JSONRPCRequest(String method, Object id, Object params) {
			this(JSONRPC_VERSION, method, id, params);
		}

		public JSONRPCRequest(String method, Object id) {
			this(JSONRPC_VERSION, method, id, null);
		}
	}

	/**
	 * A notification which does not expect a response.
	 *
	 * @param jsonrpc The JSON-RPC version (must be "2.0")
	 * @param method The name of the method being notified
	 * @param params Parameters for the notification
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record JSONRPCNotification( // @formatter:off
		@JsonProperty("jsonrpc") String jsonrpc,
		@JsonProperty("method") String method,
		@JsonProperty("params") Object params) implements JSONRPCMessage { // @formatter:on

		public JSONRPCNotification {
			Assert.hasText(jsonrpc, "jsonrpc must not be empty");
			Assert.notNull(method, "MCP notification method must not be null");
		}

		public JSONRPCNotification(String method, Object params) {
			this(JSONRPC_VERSION, method, params);
		}

		public JSONRPCNotification(String method) {
			this(JSONRPC_VERSION, method, null);
		}
	}

	/**
	 * A response to a request (successful, or error).
	 *
	 * @param jsonrpc The JSON-RPC version (must be "2.0")
	 * @param id The request identifier that this response corresponds to
	 * @param result The result of the successful request; null if error
	 * @param error Error information if the request failed; null if has result
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record JSONRPCResponse( // @formatter:off
		@JsonProperty("jsonrpc") String jsonrpc,
		@JsonProperty("id") Object id,
		@JsonProperty("result") Object result,
		@JsonProperty("error") JSONRPCError error) implements JSONRPCMessage { // @formatter:on

		public JSONRPCResponse {
			Assert.hasText(jsonrpc, "jsonrpc must not be empty");
			Assert.notNull(id, "MCP responses MUST include an ID - null IDs are not allowed");
			Assert.isTrue(id instanceof String || id instanceof Integer || id instanceof Long,
					"MCP responses MUST have an ID that is either a string or integer");
			Assert.isTrue((result != null) ^ (error != null), "MCP responses MUST either have a result or error");
		}

		public static JSONRPCResponse result(Object id, Object result) {
			return new JSONRPCResponse(JSONRPC_VERSION, id, result, null);
		}

		public static JSONRPCResponse error(Object id, JSONRPCError error) {
			return new JSONRPCResponse(JSONRPC_VERSION, id, null, error);
		}

		/**
		 * A response to a request that indicates an error occurred.
		 *
		 * @param code The error type that occurred
		 * @param message A short description of the error. The message SHOULD be limited
		 * to a concise single sentence
		 * @param data Additional information about the error. The value of this member is
		 * defined by the sender (e.g. detailed error information, nested errors etc.)
		 */
		@JsonInclude(JsonInclude.Include.NON_ABSENT)
		@JsonIgnoreProperties(ignoreUnknown = true)
		public record JSONRPCError( // @formatter:off
			@JsonProperty("code") Integer code,
			@JsonProperty("message") String message,
			@JsonProperty("data") Object data) { // @formatter:on

			public JSONRPCError {
				Assert.notNull(code, "code must not be null");
				Assert.notNull(message, "message must not be null");
			}

			public JSONRPCError(Integer code, String message) {
				this(code, message, null);
			}

		}
	}

	// ---------------------------
	// Initialization
	// ---------------------------
	/**
	 * This request is sent from the client to the server when it first connects, asking
	 * it to begin initialization.
	 *
	 * @param protocolVersion The latest version of the Model Context Protocol that the
	 * client supports. The client MAY decide to support older versions as well
	 * @param capabilities The capabilities that the client supports
	 * @param clientInfo Information about the client implementation
	 * @param meta See specification for notes on _meta usage
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record InitializeRequest( // @formatter:off
		@JsonProperty("protocolVersion") String protocolVersion,
		@JsonProperty("capabilities") ClientCapabilities capabilities,
		@JsonProperty("clientInfo") Implementation clientInfo,
		@JsonProperty("_meta") Map<String, Object> meta) implements Request { // @formatter:on

		public InitializeRequest {
			Assert.notNull(protocolVersion, "protocolVersion must not be null");
			Assert.notNull(capabilities, "capabilities must not be null");
			Assert.notNull(clientInfo, "clientInfo must not be null");
		}

		@JsonCreator
		static InitializeRequest fromJson(@JsonProperty("protocolVersion") String protocolVersion,
				@JsonProperty("capabilities") ClientCapabilities capabilities,
				@JsonProperty("clientInfo") Implementation clientInfo,
				@JsonProperty("_meta") Map<String, Object> meta) {
			if (protocolVersion == null || capabilities == null || clientInfo == null) {
				List<String> missing = new ArrayList<>();
				if (protocolVersion == null) {
					missing.add("protocolVersion -> ''");
					protocolVersion = "";
				}
				if (capabilities == null) {
					missing.add("capabilities -> {}");
					capabilities = new ClientCapabilities(null, null, null, null);
				}
				if (clientInfo == null) {
					missing.add("clientInfo -> {name='', version=''}");
					clientInfo = new Implementation("", "");
				}
				logger.warn("InitializeRequest: missing required fields during deserialization: {}",
						String.join(", ", missing));
			}
			return new InitializeRequest(protocolVersion, capabilities, clientInfo, meta);
		}

		/**
		 * @deprecated Use {@link #builder(String, ClientCapabilities, Implementation)}
		 * instead.
		 */
		@Deprecated
		public InitializeRequest(String protocolVersion, ClientCapabilities capabilities, Implementation clientInfo) {
			this(protocolVersion, capabilities, clientInfo, null);
		}

		public static Builder builder(String protocolVersion, ClientCapabilities capabilities,
				Implementation clientInfo) {
			return new Builder(protocolVersion, capabilities, clientInfo);
		}

		public static class Builder {

			private final String protocolVersion;

			private final ClientCapabilities capabilities;

			private final Implementation clientInfo;

			private Map<String, Object> meta;

			private Builder(String protocolVersion, ClientCapabilities capabilities, Implementation clientInfo) {
				Assert.hasText(protocolVersion, "protocolVersion must not be empty");
				Assert.notNull(capabilities, "capabilities must not be null");
				Assert.notNull(clientInfo, "clientInfo must not be null");
				this.protocolVersion = protocolVersion;
				this.capabilities = capabilities;
				this.clientInfo = clientInfo;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public InitializeRequest build() {
				return new InitializeRequest(protocolVersion, capabilities, clientInfo, meta);
			}

		}
	}

	/**
	 * After receiving an initialize request from the client, the server sends this
	 * response.
	 *
	 * @param protocolVersion The version of the Model Context Protocol that the server
	 * wants to use. This may not match the version that the client requested. If the
	 * client cannot support this version, it MUST disconnect
	 * @param capabilities The capabilities that the server supports
	 * @param serverInfo Information about the server implementation
	 * @param instructions Instructions describing how to use the server and its features.
	 * This can be used by clients to improve the LLM's understanding of available tools,
	 * resources, etc. It can be thought of like a "hint" to the model. For example, this
	 * information MAY be added to the system prompt
	 * @param meta See specification for notes on _meta usage
	 */
	@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,
		@JsonProperty("_meta") Map<String, Object> meta) implements Result { // @formatter:on

		public InitializeResult {
			Assert.notNull(protocolVersion, "protocolVersion must not be null");
			Assert.notNull(capabilities, "capabilities must not be null");
			Assert.notNull(serverInfo, "serverInfo must not be null");
		}

		@JsonCreator
		static InitializeResult fromJson(@JsonProperty("protocolVersion") String protocolVersion,
				@JsonProperty("capabilities") ServerCapabilities capabilities,
				@JsonProperty("serverInfo") Implementation serverInfo,
				@JsonProperty("instructions") String instructions, @JsonProperty("_meta") Map<String, Object> meta) {
			if (protocolVersion == null || capabilities == null || serverInfo == null) {
				List<String> missing = new ArrayList<>();
				if (protocolVersion == null) {
					missing.add("protocolVersion -> ''");
					protocolVersion = "";
				}
				if (capabilities == null) {
					missing.add("capabilities -> {}");
					capabilities = new ServerCapabilities(null, null, null, null, null, null);
				}
				if (serverInfo == null) {
					missing.add("serverInfo -> {name='', version=''}");
					serverInfo = new Implementation("", "");
				}
				logger.warn("InitializeResult: missing required fields during deserialization: {}",
						String.join(", ", missing));
			}
			return new InitializeResult(protocolVersion, capabilities, serverInfo, instructions, meta);
		}

		/**
		 * @deprecated Use {@link #builder(String, ServerCapabilities, Implementation)}
		 * instead.
		 */
		@Deprecated
		public InitializeResult(String protocolVersion, ServerCapabilities capabilities, Implementation serverInfo,
				String instructions) {
			this(protocolVersion, capabilities, serverInfo, instructions, null);
		}

		public static Builder builder(String protocolVersion, ServerCapabilities capabilities,
				Implementation serverInfo) {
			return new Builder(protocolVersion, capabilities, serverInfo);
		}

		public static class Builder {

			private final String protocolVersion;

			private final ServerCapabilities capabilities;

			private final Implementation serverInfo;

			private String instructions;

			private Map<String, Object> meta;

			private Builder(String protocolVersion, ServerCapabilities capabilities, Implementation serverInfo) {
				Assert.hasText(protocolVersion, "protocolVersion must not be empty");
				Assert.notNull(capabilities, "capabilities must not be null");
				Assert.notNull(serverInfo, "serverInfo must not be null");
				this.protocolVersion = protocolVersion;
				this.capabilities = capabilities;
				this.serverInfo = serverInfo;
			}

			public Builder instructions(String instructions) {
				this.instructions = instructions;
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public InitializeResult build() {
				return new InitializeResult(protocolVersion, capabilities, serverInfo, instructions, meta);
			}

		}
	}

	/**
	 * Capabilities a client may support. Known capabilities are defined here, in this
	 * schema, but this is not a closed set: any client can define its own, additional
	 * capabilities.
	 *
	 * @param experimental Experimental, non-standard capabilities that the client
	 * supports
	 * @param roots Present if the client supports listing roots
	 * @param sampling Present if the client supports sampling from an LLM
	 * @param elicitation Present if the client supports elicitation from the server
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record ClientCapabilities( // @formatter:off
		@JsonProperty("experimental") Map<String, Object> experimental,
		@JsonProperty("roots") RootCapabilities roots,
		@JsonProperty("sampling") Sampling sampling,
		@JsonProperty("elicitation") Elicitation elicitation) { // @formatter:on

		/**
		 * Present if the client supports listing roots.
		 *
		 * @param listChanged Whether the client supports notifications for changes to the
		 * roots list
		 */
		@JsonInclude(JsonInclude.Include.NON_ABSENT)
		@JsonIgnoreProperties(ignoreUnknown = true)
		public record RootCapabilities(@JsonProperty("listChanged") Boolean listChanged) {

			public static Builder builder() {
				return new Builder();
			}

			public static class Builder {

				private Boolean listChanged;

				public Builder listChanged(Boolean listChanged) {
					this.listChanged = listChanged;
					return this;
				}

				public RootCapabilities build() {
					return new RootCapabilities(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)
		@JsonIgnoreProperties(ignoreUnknown = true)
		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.
		 *
		 * <p>
		 * Per the 2025-11-25 spec, clients can declare support for specific elicitation
		 * modes:
		 * <ul>
		 * <li>{@code form} - In-band structured data collection with optional schema
		 * validation</li>
		 * <li>{@code url} - Out-of-band interaction via URL navigation</li>
		 * </ul>
		 *
		 * <p>
		 * For backward compatibility, an empty elicitation object {@code {}} is
		 * equivalent to declaring support for form mode only.
		 *
		 * @param form support for in-band form-based elicitation
		 * @param url support for out-of-band URL-based elicitation
		 */
		@JsonInclude(JsonInclude.Include.NON_ABSENT)
		@JsonIgnoreProperties(ignoreUnknown = true)
		public record Elicitation(@JsonProperty("form") Form form, @JsonProperty("url") Url url) {

			/**
			 * Marker record indicating support for form-based elicitation mode.
			 */
			@JsonInclude(JsonInclude.Include.NON_ABSENT)
			@JsonIgnoreProperties(ignoreUnknown = true)
			public record Form() {
			}

			/**
			 * Marker record indicating support for URL-based elicitation mode.
			 */
			@JsonInclude(JsonInclude.Include.NON_ABSENT)
			@JsonIgnoreProperties(ignoreUnknown = true)
			public record Url() {
			}

			/**
			 * Creates an Elicitation with default settings (backward compatible, produces
			 * empty JSON object).
			 * @deprecated Use {@link #builder()} instead.
			 */
			@Deprecated
			public Elicitation() {
				this(null, null);
			}

			public static Builder builder() {
				return new Builder();
			}

			public static class Builder {

				private Form form;

				private Url url;

				public Builder form(Form form) {
					this.form = form;
					return this;
				}

				public Builder url(Url url) {
					this.url = url;
					return this;
				}

				public Elicitation build() {
					return new Elicitation(form, url);
				}

			}
		}

		public static Builder builder() {
			return new Builder();
		}

		public static class Builder {

			private Map<String, Object> experimental;

			private RootCapabilities roots;

			private Sampling sampling;

			private Elicitation elicitation;

			public Builder experimental(Map<String, Object> 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;
			}

			/**
			 * Enables elicitation capability with default settings (backward compatible,
			 * produces empty JSON object).
			 * @return this builder
			 */
			public Builder elicitation() {
				this.elicitation = Elicitation.builder().build();
				return this;
			}

			/**
			 * Enables elicitation capability with explicit form and/or url mode support.
			 * @param form whether to support form-based elicitation
			 * @param url whether to support URL-based elicitation
			 * @return this builder
			 */
			public Builder elicitation(boolean form, boolean url) {
				this.elicitation = new Elicitation(form ? new Elicitation.Form() : null,
						url ? new Elicitation.Url() : null);
				return this;
			}

			public Builder elicitation(Elicitation elicitation) {
				this.elicitation = elicitation;
				return this;
			}

			public ClientCapabilities build() {
				return new ClientCapabilities(experimental, roots, sampling, elicitation);
			}

		}
	}

	/**
	 * Capabilities that a server may support. Known capabilities are defined here, in
	 * this schema, but this is not a closed set: any server can define its own,
	 * additional capabilities.
	 *
	 * @param completions Present if the server supports argument autocompletion
	 * suggestions
	 * @param experimental Experimental, non-standard capabilities that the server
	 * supports
	 * @param logging Present if the server supports sending log messages to the client
	 * @param prompts Present if the server offers any prompt templates
	 * @param resources Present if the server offers any resources to read
	 * @param tools Present if the server offers any tools to call
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record ServerCapabilities( // @formatter:off
		@JsonProperty("completions") CompletionCapabilities completions,
		@JsonProperty("experimental") Map<String, Object> experimental,
		@JsonProperty("logging") LoggingCapabilities logging,
		@JsonProperty("prompts") PromptCapabilities prompts,
		@JsonProperty("resources") ResourceCapabilities resources,
		@JsonProperty("tools") ToolCapabilities tools) { // @formatter:on

		/**
		 * Present if the server supports argument autocompletion suggestions.
		 */
		@JsonInclude(JsonInclude.Include.NON_ABSENT)
		@JsonIgnoreProperties(ignoreUnknown = true)
		public record CompletionCapabilities() {
		}

		/**
		 * Present if the server supports sending log messages to the client.
		 */
		@JsonInclude(JsonInclude.Include.NON_ABSENT)
		@JsonIgnoreProperties(ignoreUnknown = true)
		public record LoggingCapabilities() {
		}

		/**
		 * Present if the server offers any prompt templates.
		 *
		 * @param listChanged Whether this server supports notifications for changes to
		 * the prompt list
		 */
		@JsonInclude(JsonInclude.Include.NON_ABSENT)
		@JsonIgnoreProperties(ignoreUnknown = true)
		public record PromptCapabilities(@JsonProperty("listChanged") Boolean listChanged) {

			public static Builder builder() {
				return new Builder();
			}

			public static class Builder {

				private Boolean listChanged;

				public Builder listChanged(Boolean listChanged) {
					this.listChanged = listChanged;
					return this;
				}

				public PromptCapabilities build() {
					return new PromptCapabilities(listChanged);
				}

			}
		}

		/**
		 * Present if the server offers any resources to read.
		 *
		 * @param subscribe Whether this server supports subscribing to resource updates
		 * @param listChanged Whether this server supports notifications for changes to
		 * the resource list
		 */
		@JsonInclude(JsonInclude.Include.NON_ABSENT)
		@JsonIgnoreProperties(ignoreUnknown = true)
		public record ResourceCapabilities(@JsonProperty("subscribe") Boolean subscribe,
				@JsonProperty("listChanged") Boolean listChanged) {

			public static Builder builder() {
				return new Builder();
			}

			public static class Builder {

				private Boolean subscribe;

				private Boolean listChanged;

				public Builder subscribe(Boolean subscribe) {
					this.subscribe = subscribe;
					return this;
				}

				public Builder listChanged(Boolean listChanged) {
					this.listChanged = listChanged;
					return this;
				}

				public ResourceCapabilities build() {
					return new ResourceCapabilities(subscribe, listChanged);
				}

			}
		}

		/**
		 * Present if the server offers any tools to call.
		 *
		 * @param listChanged Whether this server supports notifications for changes to
		 * the tool list
		 */
		@JsonInclude(JsonInclude.Include.NON_ABSENT)
		@JsonIgnoreProperties(ignoreUnknown = true)
		public record ToolCapabilities(@JsonProperty("listChanged") Boolean listChanged) {

			public static Builder builder() {
				return new Builder();
			}

			public static class Builder {

				private Boolean listChanged;

				public Builder listChanged(Boolean listChanged) {
					this.listChanged = listChanged;
					return this;
				}

				public ToolCapabilities build() {
					return new ToolCapabilities(listChanged);
				}

			}
		}

		/**
		 * Create a mutated copy of this object with the specified changes.
		 * @return A new Builder instance with the same values as this object.
		 */
		public Builder mutate() {
			var builder = new Builder();
			builder.completions = this.completions;
			builder.experimental = this.experimental;
			builder.logging = this.logging;
			builder.prompts = this.prompts;
			builder.resources = this.resources;
			builder.tools = this.tools;
			return builder;
		}

		public static Builder builder() {
			return new Builder();
		}

		public static class Builder {

			private CompletionCapabilities completions;

			private Map<String, Object> experimental;

			private LoggingCapabilities logging;

			private PromptCapabilities prompts;

			private ResourceCapabilities resources;

			private ToolCapabilities tools;

			public Builder completions() {
				this.completions = new CompletionCapabilities();
				return this;
			}

			public Builder experimental(Map<String, Object> 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);
			}

		}
	}

	/**
	 * Describes the name and version of an MCP implementation, with an optional title for
	 * UI representation.
	 *
	 * @param name Intended for programmatic or logical use, but used as a display name in
	 * past specs or fallback (if title isn't present).
	 * @param title Intended for UI and end-user contexts
	 * @param version The version of the implementation.
	 * @param description An optional human-readable description of this implementation.
	 * @param icons An optional list of icons for this implementation.
	 * @param websiteUrl An optional URL of the website for this implementation.
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record Implementation( // @formatter:off
		@JsonProperty("name") String name,
		@JsonProperty("title") String title,
		@JsonProperty("version") String version,
		@JsonProperty("description") String description,
		@JsonProperty("icons") List<Icon> icons,
		@JsonProperty("websiteUrl") String websiteUrl) implements Identifier { // @formatter:on

		public Implementation {
			Assert.notNull(name, "name must not be null");
			Assert.notNull(version, "version must not be null");
		}

		@JsonCreator
		static Implementation fromJson(@JsonProperty("name") String name, @JsonProperty("title") String title,
				@JsonProperty("version") String version, @JsonProperty("description") String description,
				@JsonProperty("icons") List<Icon> icons, @JsonProperty("websiteUrl") String websiteUrl) {
			if (name == null || version == null) {
				List<String> missing = new ArrayList<>();
				if (name == null) {
					missing.add("name -> ''");
					name = "";
				}
				if (version == null) {
					missing.add("version -> ''");
					version = "";
				}
				logger.warn("Implementation: missing required fields during deserialization: {}",
						String.join(", ", missing));
			}
			return new Implementation(name, title, version, description, icons, websiteUrl);
		}

		/**
		 * @deprecated Use {@link #builder(String, String)}
		 */
		@Deprecated
		public Implementation(String name, String version) {
			this(name, null, version, null, null, null);
		}

		/**
		 * @deprecated Use {@link #builder(String, String)}
		 */
		@Deprecated
		public Implementation(String name, String title, String version) {
			this(name, title, version, null, null, null);
		}

		public static Builder builder(String name, String version) {
			return new Builder(name, version);
		}

		public static class Builder {

			private final String name;

			private String title;

			private final String version;

			private String description;

			private List<Icon> icons;

			private String websiteUrl;

			private Builder(String name, String version) {
				Assert.hasText(name, "name must not be empty");
				Assert.hasText(version, "version must not be empty");
				this.name = name;
				this.version = version;
			}

			public Builder title(String title) {
				this.title = title;
				return this;
			}

			public Builder description(String description) {
				this.description = description;
				return this;
			}

			public Builder icons(List<Icon> icons) {
				this.icons = icons;
				return this;
			}

			public Builder websiteUrl(String websiteUrl) {
				this.websiteUrl = websiteUrl;
				return this;
			}

			public Implementation build() {
				return new Implementation(name, title, version, description, icons, websiteUrl);
			}

		}
	}

	/**
	 * Represents an icon that can be displayed in a user interface.
	 *
	 * @param src A URI pointing to an icon resource or a base64-encoded data URI.
	 * @param mimeType Optional MIME type override if the server's MIME type is missing or
	 * generic.
	 * @param sizes Optional array of strings specifying sizes at which the icon can be
	 * used. Each string should be in WxH format (e.g., "48x48", "96x96") or "any" for
	 * scalable formats like SVG.
	 * @param theme Optional specifier for the theme this icon is designed for. "light"
	 * indicates the icon is designed for a light background, "dark" indicates the icon is
	 * designed for a dark background. If not provided, the client should assume the icon
	 * can be used with any theme.
	 * @see <a href=
	 * "https://github.com/modelcontextprotocol/modelcontextprotocol/issues/973">SEP-973</a>
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record Icon( // @formatter:off
		@JsonProperty("src") String src,
		@JsonProperty("mimeType") String mimeType,
		@JsonProperty("sizes") List<String> sizes,
		@JsonProperty("theme") String theme) { // @formatter:on

		public Icon {
			Assert.notNull(src, "Icon src must not be null");
		}

		@JsonCreator
		static Icon fromJson(@JsonProperty("src") String src, @JsonProperty("mimeType") String mimeType,
				@JsonProperty("sizes") List<String> sizes, @JsonProperty("theme") String theme) {
			if (src == null) {
				logger.warn("Icon: missing required field 'src' during deserialization, using default ''");
				src = "";
			}
			return new Icon(src, mimeType, sizes, theme);
		}

		public static Builder builder(String src) {
			return new Builder(src);
		}

		public static class Builder {

			private final String src;

			private String mimeType;

			private List<String> sizes;

			private String theme;

			private Builder(String src) {
				Assert.hasText(src, "src must not be empty");
				this.src = src;
			}

			public Builder mimeType(String mimeType) {
				this.mimeType = mimeType;
				return this;
			}

			public Builder sizes(List<String> sizes) {
				this.sizes = sizes;
				return this;
			}

			public Builder theme(String theme) {
				this.theme = theme;
				return this;
			}

			public Icon build() {
				return new Icon(src, mimeType, sizes, theme);
			}

		}
	}

	// Existing Enums and Base Types (from previous implementation)
	public enum Role {

	// @formatter:off
		@JsonProperty("user") USER,
		@JsonProperty("assistant") ASSISTANT
	} // @formatter:on

	// ---------------------------
	// Resource Interfaces
	// ---------------------------
	/**
	 * Base for objects that include optional annotations for the client. The client can
	 * use annotations to inform how objects are used or displayed
	 */
	public interface Annotated {

		Annotations annotations();

	}

	/**
	 * Optional annotations for the client. The client can use annotations to inform how
	 * objects are used or displayed.
	 *
	 * @param audience Describes who the intended customer of this object or data is. It
	 * can include multiple entries to indicate content useful for multiple audiences
	 * (e.g., `["user", "assistant"]`).
	 * @param priority Describes how important this data is for operating the server. A
	 * value of 1 means "most important," and indicates that the data is effectively
	 * required, while 0 means "least important," and indicates that the data is entirely
	 * optional. It is a number between 0 and 1.
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record Annotations( // @formatter:off
		@JsonProperty("audience") List<Role> audience,
		@JsonProperty("priority") Double priority,
		@JsonProperty("lastModified") String lastModified
		) { // @formatter:on

		/**
		 * @deprecated Use {@link #builder()} instead.
		 */
		@Deprecated
		public Annotations(List<Role> audience, Double priority) {
			this(audience, priority, null);
		}

		public static Builder builder() {
			return new Builder();
		}

		public static class Builder {

			private List<Role> audience;

			private Double priority;

			private String lastModified;

			public Builder audience(List<Role> audience) {
				this.audience = audience;
				return this;
			}

			public Builder priority(Double priority) {
				this.priority = priority;
				return this;
			}

			public Builder lastModified(String lastModified) {
				this.lastModified = lastModified;
				return this;
			}

			public Annotations build() {
				return new Annotations(audience, priority, lastModified);
			}

		}
	}

	/**
	 * A common interface for resource content, which includes metadata about the resource
	 * such as its URI, name, description, MIME type, size, and annotations. This
	 * interface is implemented by both {@link Resource} and {@link ResourceLink} to
	 * provide a consistent way to access resource metadata.
	 */
	public interface ResourceContent extends Identifier, Annotated, Meta {

		// name & title from Identifier

		String uri();

		String description();

		String mimeType();

		Long size();

		// annotations from Annotated
		// meta from Meta

	}

	/**
	 * Base interface with name (identifier) and title (display name) properties.
	 */
	public interface Identifier {

		/**
		 * Intended for programmatic or logical use, but used as a display name in past
		 * specs or fallback (if title isn't present).
		 */
		String name();

		/**
		 * Intended for UI and end-user contexts — optimized to be human-readable and
		 * easily understood, even by those unfamiliar with domain-specific terminology.
		 *
		 * If not provided, the name should be used for display.
		 */
		String title();

	}

	/**
	 * A known resource that the server is capable of reading.
	 *
	 * @param uri the URI of the resource.
	 * @param name A human-readable name for this resource. This can be used by clients to
	 * populate UI elements.
	 * @param title An optional title for this resource.
	 * @param description A description of what this resource represents. This can be used
	 * by clients to improve the LLM's understanding of available resources. It can be
	 * thought of like a "hint" to the model.
	 * @param mimeType The MIME type of this resource, if known.
	 * @param size The size of the raw resource content, in bytes (i.e., before base64
	 * encoding or any tokenization), if known. This can be used by Hosts to display file
	 * 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 icons Optional list of icons for this resource.
	 * @param meta See specification for notes on _meta usage
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record Resource( // @formatter:off
		@JsonProperty("uri") String uri,
		@JsonProperty("name") String name,
		@JsonProperty("title") String title,
		@JsonProperty("description") String description,
		@JsonProperty("mimeType") String mimeType,
		@JsonProperty("size") Long size,
		@JsonProperty("annotations") Annotations annotations,
		@JsonProperty("_meta") Map<String, Object> meta,
		@JsonProperty("icons") List<Icon> icons) implements ResourceContent { // @formatter:on

		public Resource {
			Assert.hasText(uri, "uri must not be empty");
			Assert.hasText(name, "name must not be empty");
		}

		/**
		 * @deprecated Use {@link #builder(String, String)}
		 */
		@Deprecated
		public Resource(String uri, String name, String title, String description, String mimeType, Long size,
				Annotations annotations, Map<String, Object> meta) {
			this(uri, name, title, description, mimeType, size, annotations, meta, null);
		}

		public static Builder builder(String uri, String name) {
			return new Builder(uri, name);
		}

		@Deprecated
		public static Builder builder() {
			return new Builder();
		}

		public static class Builder {

			private /* final */ String uri;

			private /* final */ String name;

			private String title;

			private String description;

			private String mimeType;

			private Long size;

			private Annotations annotations;

			private List<Icon> icons;

			private Map<String, Object> meta;

			@Deprecated
			public Builder() {
			}

			@Deprecated
			public Builder uri(String uri) {
				Assert.hasText(uri, "uri must not be empty");
				this.uri = uri;
				return this;
			}

			@Deprecated
			public Builder name(String name) {
				this.name = name;
				Assert.hasText(name, "name must not be empty");
				return this;
			}

			private Builder(String uri, String name) {
				Assert.hasText(uri, "uri must not be empty");
				Assert.hasText(name, "name must not be empty");
				this.uri = uri;
				this.name = name;
			}

			public Builder title(String title) {
				this.title = title;
				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 Builder icons(List<Icon> icons) {
				this.icons = icons;
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public Resource build() {
				return new Resource(uri, name, title, description, mimeType, size, annotations, meta, icons);
			}

		}
	}

	/**
	 * Resource templates allow servers to expose parameterized resources using URI
	 *
	 * @param uriTemplate A URI template that can be used to generate URIs for this
	 * resource.
	 * @param name A human-readable name for this resource. This can be used by clients to
	 * populate UI elements.
	 * @param title An optional title for this resource.
	 * @param description A description of what this resource represents. This can be used
	 * by clients to improve the LLM's understanding of available resources. It can be
	 * thought of like a "hint" to the model.
	 * @param mimeType The MIME type of this resource, if known.
	 * @param annotations Optional annotations for the client. The client can use
	 * annotations to inform how objects are used or displayed.
	 * @param icons Optional list of icons for this resource template.
	 * @see <a href="https://datatracker.ietf.org/doc/html/rfc6570">RFC 6570</a>
	 * @param meta See 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,
		@JsonProperty("_meta") Map<String, Object> meta,
		@JsonProperty("icons") List<Icon> icons) implements Annotated, Identifier, Meta { // @formatter:on

		public ResourceTemplate {
			Assert.hasText(uriTemplate, "uriTemplate must not be empty");
			Assert.hasText(name, "name must not be empty");
		}

		/**
		 * @deprecated Use {@link #builder(String, String)}.
		 */
		@Deprecated
		public ResourceTemplate(String uriTemplate, String name, String title, String description, String mimeType,
				Annotations annotations, Map<String, Object> meta) {
			this(uriTemplate, name, title, description, mimeType, annotations, meta, null);
		}

		/**
		 * @deprecated Use {@link #builder(String, String)}.
		 */
		@Deprecated
		public ResourceTemplate(String uriTemplate, String name, String title, String description, String mimeType,
				Annotations annotations) {
			this(uriTemplate, name, title, description, mimeType, annotations, null, null);
		}

		/**
		 * @deprecated Use {@link #builder(String, String)}.
		 */
		@Deprecated
		public ResourceTemplate(String uriTemplate, String name, String description, String mimeType,
				Annotations annotations) {
			this(uriTemplate, name, null, description, mimeType, annotations, null, null);
		}

		public static Builder builder(String uriTemplate, String name) {
			return new Builder(uriTemplate, name);
		}

		@Deprecated
		public static Builder builder() {
			return new Builder();
		}

		public static class Builder {

			private /* final */ String uriTemplate;

			private /* final */ String name;

			private String title;

			private String description;

			private String mimeType;

			private Annotations annotations;

			private List<Icon> icons;

			private Map<String, Object> meta;

			@Deprecated
			private Builder() {

			}

			private Builder(String uriTemplate, String name) {
				Assert.hasText(uriTemplate, "uriTemplate must not be empty");
				Assert.hasText(name, "name must not be empty");
				this.uriTemplate = uriTemplate;
				this.name = name;
			}

			@Deprecated
			public Builder uriTemplate(String uriTemplate) {
				Assert.hasText(uriTemplate, "uriTemplate must not be empty");
				this.uriTemplate = uriTemplate;
				return this;
			}

			@Deprecated
			public Builder name(String name) {
				Assert.hasText(name, "name must not be empty");
				this.name = name;
				return this;
			}

			public Builder title(String title) {
				this.title = title;
				return this;
			}

			public Builder description(String description) {
				this.description = description;
				return this;
			}

			public Builder mimeType(String mimeType) {
				this.mimeType = mimeType;
				return this;
			}

			public Builder annotations(Annotations annotations) {
				this.annotations = annotations;
				return this;
			}

			public Builder icons(List<Icon> icons) {
				this.icons = icons;
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public ResourceTemplate build() {
				return new ResourceTemplate(uriTemplate, name, title, description, mimeType, annotations, meta, icons);
			}

		}
	}

	/**
	 * The server's response to a resources/list request from the client.
	 *
	 * @param resources A list of resources that the server provides
	 * @param nextCursor An opaque token representing the pagination position after the
	 * last returned result. If present, there may be more results available
	 * @param meta See specification for notes on _meta usage
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record ListResourcesResult( // @formatter:off
		@JsonProperty("resources") List<Resource> resources,
		@JsonProperty("nextCursor") String nextCursor,
		@JsonProperty("_meta") Map<String, Object> meta) implements Result { // @formatter:on

		public ListResourcesResult {
			Assert.notNull(resources, "resources must not be null");
		}

		@JsonCreator
		static ListResourcesResult fromJson(@JsonProperty("resources") List<Resource> resources,
				@JsonProperty("nextCursor") String nextCursor, @JsonProperty("_meta") Map<String, Object> meta) {
			if (resources == null) {
				logger.warn(
						"ListResourcesResult: missing required field 'resources' during deserialization, using default []");
				resources = List.of();
			}
			return new ListResourcesResult(resources, nextCursor, meta);
		}

		@Deprecated
		public ListResourcesResult(List<Resource> resources, String nextCursor) {
			this(resources, nextCursor, null);
		}

		public static Builder builder(List<Resource> resources) {
			return new Builder(resources);
		}

		public static class Builder {

			private final List<Resource> resources;

			private String nextCursor;

			private Map<String, Object> meta;

			private Builder(List<Resource> resources) {
				Assert.notNull(resources, "resources must not be null");
				this.resources = resources;
			}

			public Builder nextCursor(String nextCursor) {
				this.nextCursor = nextCursor;
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public ListResourcesResult build() {
				return new ListResourcesResult(resources, nextCursor, meta);
			}

		}
	}

	/**
	 * The server's response to a resources/templates/list request from the client.
	 *
	 * @param resourceTemplates A list of resource templates that the server provides
	 * @param nextCursor An opaque token representing the pagination position after the
	 * last returned result. If present, there may be more results available
	 * @param meta See specification for notes on _meta usage
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record ListResourceTemplatesResult( // @formatter:off
		@JsonProperty("resourceTemplates") List<ResourceTemplate> resourceTemplates,
		@JsonProperty("nextCursor") String nextCursor,
		@JsonProperty("_meta") Map<String, Object> meta) implements Result { // @formatter:on

		public ListResourceTemplatesResult {
			Assert.notNull(resourceTemplates, "resourceTemplates must not be null");
		}

		@JsonCreator
		static ListResourceTemplatesResult fromJson(
				@JsonProperty("resourceTemplates") List<ResourceTemplate> resourceTemplates,
				@JsonProperty("nextCursor") String nextCursor, @JsonProperty("_meta") Map<String, Object> meta) {
			if (resourceTemplates == null) {
				logger.warn(
						"ListResourceTemplatesResult: missing required field 'resourceTemplates' during deserialization, using default []");
				resourceTemplates = List.of();
			}
			return new ListResourceTemplatesResult(resourceTemplates, nextCursor, meta);
		}

		@Deprecated
		public ListResourceTemplatesResult(List<ResourceTemplate> resourceTemplates, String nextCursor) {
			this(resourceTemplates, nextCursor, null);
		}

		public static Builder builder(List<ResourceTemplate> resourceTemplates) {
			return new Builder(resourceTemplates);
		}

		public static class Builder {

			private final List<ResourceTemplate> resourceTemplates;

			private String nextCursor;

			private Map<String, Object> meta;

			private Builder(List<ResourceTemplate> resourceTemplates) {
				Assert.notNull(resourceTemplates, "resourceTemplates must not be null");
				this.resourceTemplates = resourceTemplates;
			}

			public Builder nextCursor(String nextCursor) {
				this.nextCursor = nextCursor;
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public ListResourceTemplatesResult build() {
				return new ListResourceTemplatesResult(resourceTemplates, nextCursor, meta);
			}

		}
	}

	/**
	 * Sent from the client to the server, to read a specific resource URI.
	 *
	 * @param uri The URI of the resource to read. The URI can use any protocol; it is up
	 * to the server how to interpret it
	 * @param meta See specification for notes on _meta usage
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record ReadResourceRequest( // @formatter:off
		@JsonProperty("uri") String uri,
		@JsonProperty("_meta") Map<String, Object> meta) implements Request { // @formatter:on

		public ReadResourceRequest {
			Assert.notNull(uri, "uri must not be null");
		}

		@JsonCreator
		static ReadResourceRequest fromJson(@JsonProperty("uri") String uri,
				@JsonProperty("_meta") Map<String, Object> meta) {
			if (uri == null) {
				logger
					.warn("ReadResourceRequest: missing required field 'uri' during deserialization, using default ''");
				uri = "";
			}
			return new ReadResourceRequest(uri, meta);
		}

		@Deprecated
		public ReadResourceRequest(String uri) {
			this(uri, null);
		}

		public static Builder builder(String uri) {
			return new Builder(uri);
		}

		public static class Builder {

			private final String uri;

			private Map<String, Object> meta;

			private Builder(String uri) {
				Assert.hasText(uri, "uri must not be empty");
				this.uri = uri;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public ReadResourceRequest build() {
				return new ReadResourceRequest(uri, meta);
			}

		}
	}

	/**
	 * The server's response to a resources/read request from the client.
	 *
	 * @param contents The contents of the resource
	 * @param meta See specification for notes on _meta usage
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record ReadResourceResult( // @formatter:off
		@JsonProperty("contents") List<ResourceContents> contents,
		@JsonProperty("_meta") Map<String, Object> meta) implements Result { // @formatter:on

		public ReadResourceResult {
			Assert.notNull(contents, "contents must not be null");
		}

		@JsonCreator
		static ReadResourceResult fromJson(@JsonProperty("contents") List<ResourceContents> contents,
				@JsonProperty("_meta") Map<String, Object> meta) {
			if (contents == null) {
				logger.warn(
						"ReadResourceResult: missing required field 'contents' during deserialization, using default []");
				contents = List.of();
			}
			return new ReadResourceResult(contents, meta);
		}

		@Deprecated
		public ReadResourceResult(List<ResourceContents> contents) {
			this(contents, null);
		}

		public static Builder builder(List<ResourceContents> contents) {
			return new Builder(contents);
		}

		public static class Builder {

			private final List<ResourceContents> contents;

			private Map<String, Object> meta;

			private Builder(List<ResourceContents> contents) {
				Assert.notNull(contents, "contents must not be null");
				this.contents = contents;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public ReadResourceResult build() {
				return new ReadResourceResult(contents, meta);
			}

		}
	}

	/**
	 * Sent from the client to request resources/updated notifications from the server
	 * whenever a particular resource changes.
	 *
	 * @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 See specification for notes on _meta usage
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record SubscribeRequest( // @formatter:off
		@JsonProperty("uri") String uri,
		@JsonProperty("_meta") Map<String, Object> meta) implements Request { // @formatter:on

		public SubscribeRequest {
			Assert.notNull(uri, "uri must not be null");
		}

		@JsonCreator
		static SubscribeRequest fromJson(@JsonProperty("uri") String uri,
				@JsonProperty("_meta") Map<String, Object> meta) {
			if (uri == null) {
				logger.warn("SubscribeRequest: missing required field 'uri' during deserialization, using default ''");
				uri = "";
			}
			return new SubscribeRequest(uri, meta);
		}

		@Deprecated
		public SubscribeRequest(String uri) {
			this(uri, null);
		}

		public static Builder builder(String uri) {
			return new Builder(uri);
		}

		public static class Builder {

			private final String uri;

			private Map<String, Object> meta;

			private Builder(String uri) {
				Assert.hasText(uri, "uri must not be empty");
				this.uri = uri;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public SubscribeRequest build() {
				return new SubscribeRequest(uri, meta);
			}

		}
	}

	/**
	 * Sent from the client to request cancellation of resources/updated notifications
	 * from the server. This should follow a previous resources/subscribe request.
	 *
	 * @param uri The URI of the resource to unsubscribe from
	 * @param meta See specification for notes on _meta usage
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record UnsubscribeRequest( // @formatter:off
		@JsonProperty("uri") String uri,
		@JsonProperty("_meta") Map<String, Object> meta) implements Request { // @formatter:on

		public UnsubscribeRequest {
			Assert.notNull(uri, "uri must not be null");
		}

		@JsonCreator
		static UnsubscribeRequest fromJson(@JsonProperty("uri") String uri,
				@JsonProperty("_meta") Map<String, Object> meta) {
			if (uri == null) {
				logger
					.warn("UnsubscribeRequest: missing required field 'uri' during deserialization, using default ''");
				uri = "";
			}
			return new UnsubscribeRequest(uri, meta);
		}

		@Deprecated
		public UnsubscribeRequest(String uri) {
			this(uri, null);
		}

		public static Builder builder(String uri) {
			return new Builder(uri);
		}

		public static class Builder {

			private final String uri;

			private Map<String, Object> meta;

			private Builder(String uri) {
				Assert.hasText(uri, "uri must not be empty");
				this.uri = uri;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public UnsubscribeRequest build() {
				return new UnsubscribeRequest(uri, meta);
			}

		}
	}

	/**
	 * The contents of a specific resource or sub-resource.
	 */
	@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
	@JsonSubTypes({ @JsonSubTypes.Type(value = TextResourceContents.class),
			@JsonSubTypes.Type(value = BlobResourceContents.class) })
	public interface ResourceContents extends Meta {

		/**
		 * The URI of this resource.
		 * @return the URI of this resource.
		 */
		String uri();

		/**
		 * The MIME type of this resource.
		 * @return the MIME type of this resource.
		 */
		String mimeType();

	}

	/**
	 * Text contents of a resource.
	 *
	 * @param uri the URI of this resource.
	 * @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 See specification for notes on _meta usage
	 */
	@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,
		@JsonProperty("_meta") Map<String, Object> meta) implements ResourceContents { // @formatter:on

		public TextResourceContents {
			Assert.notNull(uri, "uri must not be null");
			Assert.notNull(text, "text must not be null");
		}

		@JsonCreator
		static TextResourceContents fromJson(@JsonProperty("uri") String uri, @JsonProperty("mimeType") String mimeType,
				@JsonProperty("text") String text, @JsonProperty("_meta") Map<String, Object> meta) {
			if (uri == null || text == null) {
				List<String> missing = new ArrayList<>();
				if (uri == null) {
					missing.add("uri -> ''");
					uri = "";
				}
				if (text == null) {
					missing.add("text -> ''");
					text = "";
				}
				logger.warn("TextResourceContents: missing required fields during deserialization: {}",
						String.join(", ", missing));
			}
			return new TextResourceContents(uri, mimeType, text, meta);
		}

		@Deprecated
		public TextResourceContents(String uri, String mimeType, String text) {
			this(uri, mimeType, text, null);
		}

		public static Builder builder(String uri, String text) {
			return new Builder(uri, text);
		}

		public static class Builder {

			private final String uri;

			private String mimeType;

			private final String text;

			private Map<String, Object> meta;

			private Builder(String uri, String text) {
				Assert.hasText(uri, "uri must not be empty");
				Assert.notNull(text, "text must not be null");
				this.uri = uri;
				this.text = text;
			}

			public Builder mimeType(String mimeType) {
				this.mimeType = mimeType;
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public TextResourceContents build() {
				return new TextResourceContents(uri, mimeType, text, meta);
			}

		}
	}

	/**
	 * Binary contents of a resource.
	 *
	 * @param uri the URI of this resource.
	 * @param mimeType the MIME type of this resource.
	 * @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 See specification for notes on _meta usage
	 */
	@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,
		@JsonProperty("_meta") Map<String, Object> meta) implements ResourceContents { // @formatter:on

		public BlobResourceContents {
			Assert.notNull(uri, "uri must not be null");
			Assert.notNull(blob, "blob must not be null");
		}

		@JsonCreator
		static BlobResourceContents fromJson(@JsonProperty("uri") String uri, @JsonProperty("mimeType") String mimeType,
				@JsonProperty("blob") String blob, @JsonProperty("_meta") Map<String, Object> meta) {
			if (uri == null || blob == null) {
				List<String> missing = new ArrayList<>();
				if (uri == null) {
					missing.add("uri -> ''");
					uri = "";
				}
				if (blob == null) {
					missing.add("blob -> ''");
					blob = "";
				}
				logger.warn("BlobResourceContents: missing required fields during deserialization: {}",
						String.join(", ", missing));
			}
			return new BlobResourceContents(uri, mimeType, blob, meta);
		}

		@Deprecated
		public BlobResourceContents(String uri, String mimeType, String blob) {
			this(uri, mimeType, blob, null);
		}

		public static Builder builder(String uri, String blob) {
			return new Builder(uri, blob);
		}

		public static class Builder {

			private final String uri;

			private String mimeType;

			private final String blob;

			private Map<String, Object> meta;

			private Builder(String uri, String blob) {
				Assert.hasText(uri, "uri must not be empty");
				Assert.notNull(blob, "blob must not be null");
				this.uri = uri;
				this.blob = blob;
			}

			public Builder mimeType(String mimeType) {
				this.mimeType = mimeType;
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public BlobResourceContents build() {
				return new BlobResourceContents(uri, mimeType, blob, meta);
			}

		}
	}

	// ---------------------------
	// Prompt Interfaces
	// ---------------------------
	/**
	 * A prompt or prompt template that the server offers.
	 *
	 * @param name The name of the prompt or prompt template.
	 * @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 icons Optional list of icons for this prompt.
	 * @param meta See specification for notes on _meta usage
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record Prompt( // @formatter:off
		@JsonProperty("name") String name,
		@JsonProperty("title") String title,
		@JsonProperty("description") String description,
		@JsonProperty("arguments") List<PromptArgument> arguments,
		@JsonProperty("_meta") Map<String, Object> meta,
		@JsonProperty("icons") List<Icon> icons) implements Identifier { // @formatter:on

		public Prompt {
			Assert.notNull(name, "name must not be null");
		}

		@JsonCreator
		static Prompt fromJson(@JsonProperty("name") String name, @JsonProperty("title") String title,
				@JsonProperty("description") String description,
				@JsonProperty("arguments") List<PromptArgument> arguments,
				@JsonProperty("_meta") Map<String, Object> meta, @JsonProperty("icons") List<Icon> icons) {
			if (name == null) {
				logger.warn("Prompt: missing required field 'name' during deserialization, using default ''");
				name = "";
			}
			return new Prompt(name, title, description, arguments, meta, icons);
		}

		@Deprecated
		public Prompt(String name, String description, List<PromptArgument> arguments) {
			this(name, null, description, arguments, null, null);
		}

		@Deprecated
		public Prompt(String name, String title, String description, List<PromptArgument> arguments) {
			this(name, title, description, arguments, null, null);
		}

		@Deprecated
		public Prompt(String name, String title, String description, List<PromptArgument> arguments,
				Map<String, Object> meta) {
			this(name, title, description, arguments, meta, null);
		}

		public static Builder builder(String name) {
			return new Builder(name);
		}

		public static class Builder {

			private final String name;

			private String title;

			private String description;

			private List<PromptArgument> arguments;

			private List<Icon> icons;

			private Map<String, Object> meta;

			private Builder(String name) {
				Assert.hasText(name, "name must not be empty");
				this.name = name;
			}

			public Builder title(String title) {
				this.title = title;
				return this;
			}

			public Builder description(String description) {
				this.description = description;
				return this;
			}

			public Builder arguments(List<PromptArgument> arguments) {
				this.arguments = arguments;
				return this;
			}

			public Builder icons(List<Icon> icons) {
				this.icons = icons;
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public Prompt build() {
				return new Prompt(name, title, description, arguments, meta, icons);
			}

		}
	}

	/**
	 * Describes an argument that a prompt can accept.
	 *
	 * @param name The name of the argument.
	 * @param title An optional title for the argument, which can be used in UI
	 * @param description A human-readable description of the argument.
	 * @param required Whether this argument must be provided.
	 */
	@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 Identifier { // @formatter:on

		public PromptArgument {
			Assert.hasText(name, "name must not be empty");
		}

		@Deprecated
		public PromptArgument(String name, String description, Boolean required) {
			this(name, null, description, required);
		}

		public static Builder builder(String name) {
			return new Builder(name);
		}

		public static class Builder {

			private final String name;

			private String title;

			private String description;

			private Boolean required;

			private Builder(String name) {
				Assert.hasText(name, "name must not be empty");
				this.name = name;
			}

			public Builder title(String title) {
				this.title = title;
				return this;
			}

			public Builder description(String description) {
				this.description = description;
				return this;
			}

			public Builder required(Boolean required) {
				this.required = required;
				return this;
			}

			public PromptArgument build() {
				return new PromptArgument(name, title, description, required);
			}

		}
	}

	/**
	 * Describes a message returned as part of a prompt.
	 *
	 * This is similar to `SamplingMessage`, but also supports the embedding of resources
	 * from the MCP server.
	 *
	 * @param role The sender or recipient of messages and data in a conversation.
	 * @param content The content of the message of type {@link Content}.
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record PromptMessage( // @formatter:off
		@JsonProperty("role") Role role,
		@JsonProperty("content") Content content) { // @formatter:on

		public PromptMessage {
			Assert.notNull(role, "role must not be null");
			Assert.notNull(content, "content must not be null");
		}

		@JsonCreator
		static PromptMessage fromJson(@JsonProperty("role") Role role, @JsonProperty("content") Content content) {
			if (role == null || content == null) {
				List<String> missing = new ArrayList<>();
				if (role == null) {
					missing.add("role -> 'user'");
					role = Role.USER;
				}
				if (content == null) {
					missing.add("content -> ''");
					content = TextContent.builder("").build();
				}
				logger.warn("PromptMessage: missing required fields during deserialization: {}",
						String.join(", ", missing));
			}
			return new PromptMessage(role, content);
		}

		public static Builder builder(Role role, Content content) {
			return new Builder(role, content);
		}

		public static class Builder {

			private final Role role;

			private final Content content;

			private Builder(Role role, Content content) {
				Assert.notNull(role, "role must not be null");
				Assert.notNull(content, "content must not be null");
				this.role = role;
				this.content = content;
			}

			public PromptMessage build() {
				return new PromptMessage(role, content);
			}

		}
	}

	/**
	 * The server's response to a prompts/list request from the client.
	 *
	 * @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 See specification for notes on _meta usage
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record ListPromptsResult( // @formatter:off
		@JsonProperty("prompts") List<Prompt> prompts,
		@JsonProperty("nextCursor") String nextCursor,
		@JsonProperty("_meta") Map<String, Object> meta) implements Result  { // @formatter:on

		public ListPromptsResult {
			Assert.notNull(prompts, "prompts must not be null");
		}

		@JsonCreator
		static ListPromptsResult fromJson(@JsonProperty("prompts") List<Prompt> prompts,
				@JsonProperty("nextCursor") String nextCursor, @JsonProperty("_meta") Map<String, Object> meta) {
			if (prompts == null) {
				logger.warn(
						"ListPromptsResult: missing required field 'prompts' during deserialization, using default []");
				prompts = List.of();
			}
			return new ListPromptsResult(prompts, nextCursor, meta);
		}

		@Deprecated
		public ListPromptsResult(List<Prompt> prompts, String nextCursor) {
			this(prompts, nextCursor, null);
		}

		public static Builder builder(List<Prompt> prompts) {
			return new Builder(prompts);
		}

		public static class Builder {

			private final List<Prompt> prompts;

			private String nextCursor;

			private Map<String, Object> meta;

			private Builder(List<Prompt> prompts) {
				Assert.notNull(prompts, "prompts must not be null");
				this.prompts = prompts;
			}

			public Builder nextCursor(String nextCursor) {
				this.nextCursor = nextCursor;
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public ListPromptsResult build() {
				return new ListPromptsResult(prompts, nextCursor, meta);
			}

		}
	}

	/**
	 * 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 See specification for notes on _meta usage
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record GetPromptRequest( // @formatter:off
		@JsonProperty("name") String name,
		@JsonProperty("arguments") Map<String, Object> arguments,
		@JsonProperty("_meta") Map<String, Object> meta) implements Request { // @formatter:on

		public GetPromptRequest {
			Assert.notNull(name, "name must not be null");
		}

		@JsonCreator
		static GetPromptRequest fromJson(@JsonProperty("name") String name,
				@JsonProperty("arguments") Map<String, Object> arguments,
				@JsonProperty("_meta") Map<String, Object> meta) {
			if (name == null) {
				logger.warn("GetPromptRequest: missing required field 'name' during deserialization, using default ''");
				name = "";
			}
			return new GetPromptRequest(name, arguments, meta);
		}

		@Deprecated
		public GetPromptRequest(String name, Map<String, Object> arguments) {
			this(name, arguments, null);
		}

		public static Builder builder(String name) {
			return new Builder(name);
		}

		public static class Builder {

			private final String name;

			private Map<String, Object> arguments;

			private Map<String, Object> meta;

			private Builder(String name) {
				Assert.hasText(name, "name must not be empty");
				this.name = name;
			}

			public Builder arguments(Map<String, Object> arguments) {
				this.arguments = arguments;
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public GetPromptRequest build() {
				return new GetPromptRequest(name, arguments, meta);
			}

		}
	}

	/**
	 * 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 See specification for notes on _meta usage
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record GetPromptResult( // @formatter:off
		@JsonProperty("description") String description,
		@JsonProperty("messages") List<PromptMessage> messages,
		@JsonProperty("_meta") Map<String, Object> meta) implements Result { // @formatter:on

		public GetPromptResult {
			Assert.notNull(messages, "messages must not be null");
		}

		@JsonCreator
		static GetPromptResult fromJson(@JsonProperty("description") String description,
				@JsonProperty("messages") List<PromptMessage> messages,
				@JsonProperty("_meta") Map<String, Object> meta) {
			if (messages == null) {
				logger.warn(
						"GetPromptResult: missing required field 'messages' during deserialization, using default []");
				messages = List.of();
			}
			return new GetPromptResult(description, messages, meta);
		}

		@Deprecated
		public GetPromptResult(String description, List<PromptMessage> messages) {
			this(description, messages, null);
		}

		public static Builder builder(List<PromptMessage> messages) {
			return new Builder(messages);
		}

		public static class Builder {

			private String description;

			private final List<PromptMessage> messages;

			private Map<String, Object> meta;

			private Builder(List<PromptMessage> messages) {
				Assert.notNull(messages, "messages must not be null");
				this.messages = messages;
			}

			public Builder description(String description) {
				this.description = description;
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public GetPromptResult build() {
				return new GetPromptResult(description, messages, meta);
			}

		}
	}

	// ---------------------------
	// Tool Interfaces
	// ---------------------------
	/**
	 * The server's response to a tools/list request from the client.
	 *
	 * @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 See specification for notes on _meta usage
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record ListToolsResult( // @formatter:off
		@JsonProperty("tools") List<Tool> tools,
		@JsonProperty("nextCursor") String nextCursor,
		@JsonProperty("_meta") Map<String, Object> meta) implements Result { // @formatter:on

		public ListToolsResult {
			Assert.notNull(tools, "tools must not be null");
		}

		@JsonCreator
		static ListToolsResult fromJson(@JsonProperty("tools") List<Tool> tools,
				@JsonProperty("nextCursor") String nextCursor, @JsonProperty("_meta") Map<String, Object> meta) {
			if (tools == null) {
				logger.warn("ListToolsResult: missing required field 'tools' during deserialization, using default []");
				tools = List.of();
			}
			return new ListToolsResult(tools, nextCursor, meta);
		}

		@Deprecated
		public ListToolsResult(List<Tool> tools, String nextCursor) {
			this(tools, nextCursor, null);
		}

		public static Builder builder(List<Tool> tools) {
			return new Builder(tools);
		}

		public static class Builder {

			private final List<Tool> tools;

			private String nextCursor;

			private Map<String, Object> meta;

			private Builder(List<Tool> tools) {
				Assert.notNull(tools, "tools must not be null");
				this.tools = tools;
			}

			public Builder nextCursor(String nextCursor) {
				this.nextCursor = nextCursor;
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public ListToolsResult build() {
				return new ListToolsResult(tools, nextCursor, meta);
			}

		}
	}

	/**
	 * A JSON Schema object that describes the expected structure of arguments or output.
	 *
	 * @param type The type of the schema (e.g., "object")
	 * @param properties The properties of the schema object
	 * @param required List of required property names
	 * @param additionalProperties Whether additional properties are allowed
	 * @param defs Schema definitions using the newer $defs keyword
	 * @param definitions Schema definitions using the legacy definitions keyword
	 * @deprecated use {@link Map} instead.
	 */
	@Deprecated
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record JsonSchema( // @formatter:off
		@JsonProperty("type") String type,
		@JsonProperty("properties") Map<String, Object> properties,
		@JsonProperty("required") List<String> required,
		@JsonProperty("additionalProperties") Boolean additionalProperties,
		@JsonProperty("$defs") Map<String, Object> defs,
		@JsonProperty("definitions") Map<String, Object> definitions) { // @formatter:on

		public static Builder builder() {
			return new Builder();
		}

		public static class Builder {

			private String type;

			private Map<String, Object> properties;

			private List<String> required;

			private Boolean additionalProperties;

			private Map<String, Object> defs;

			private Map<String, Object> definitions;

			public Builder type(String type) {
				this.type = type;
				return this;
			}

			public Builder properties(Map<String, Object> properties) {
				this.properties = properties;
				return this;
			}

			public Builder required(List<String> required) {
				this.required = required;
				return this;
			}

			public Builder additionalProperties(Boolean additionalProperties) {
				this.additionalProperties = additionalProperties;
				return this;
			}

			public Builder defs(Map<String, Object> defs) {
				this.defs = defs;
				return this;
			}

			public Builder definitions(Map<String, Object> definitions) {
				this.definitions = definitions;
				return this;
			}

			public JsonSchema build() {
				return new JsonSchema(type, properties, required, additionalProperties, defs, definitions);
			}

		}

	}

	/**
	 * Additional properties describing a Tool to clients.
	 *
	 * NOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to
	 * provide a faithful description of tool behavior (including descriptive properties
	 * like `title`).
	 *
	 * Clients should never make tool use decisions based on ToolAnnotations received from
	 * untrusted servers.
	 */
	@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

		public static Builder builder() {
			return new Builder();
		}

		public static class Builder {

			private String title;

			private Boolean readOnlyHint;

			private Boolean destructiveHint;

			private Boolean idempotentHint;

			private Boolean openWorldHint;

			private Boolean returnDirect;

			public Builder title(String title) {
				this.title = title;
				return this;
			}

			public Builder readOnlyHint(Boolean readOnlyHint) {
				this.readOnlyHint = readOnlyHint;
				return this;
			}

			public Builder destructiveHint(Boolean destructiveHint) {
				this.destructiveHint = destructiveHint;
				return this;
			}

			public Builder idempotentHint(Boolean idempotentHint) {
				this.idempotentHint = idempotentHint;
				return this;
			}

			public Builder openWorldHint(Boolean openWorldHint) {
				this.openWorldHint = openWorldHint;
				return this;
			}

			public Builder returnDirect(Boolean returnDirect) {
				this.returnDirect = returnDirect;
				return this;
			}

			public ToolAnnotations build() {
				return new ToolAnnotations(title, readOnlyHint, destructiveHint, idempotentHint, openWorldHint,
						returnDirect);
			}

		}
	}

	/**
	 * Represents a tool that the server provides. Tools enable servers to expose
	 * executable functionality to the system. Through these tools, you can interact with
	 * external systems, perform computations, and take actions in the real world.
	 *
	 * @param name A unique identifier for the tool. This name is used when calling the
	 * tool.
	 * @param title A human-readable title for the tool.
	 * @param description A human-readable description of what the tool does. This can be
	 * used by clients to improve the LLM's understanding of available tools.
	 * @param inputSchema A JSON Schema object that describes the expected structure of
	 * the arguments when calling this tool. Per SEP-1613, the dialect defaults to JSON
	 * Schema 2020-12 ({@link #JSON_SCHEMA_DIALECT_2020_12}) when no explicit
	 * {@code $schema} entry is present. To declare a different dialect, include a
	 * {@code "$schema"} key in the map. For tools with no parameters the spec recommends
	 * {@code {"type":"object","additionalProperties":false}}.
	 * @param outputSchema An optional JSON Schema object defining the structure of the
	 * tool's output returned in the structuredContent field of a CallToolResult. Same
	 * dialect rules as {@code inputSchema}.
	 * @param annotations Optional additional tool information.
	 * @param icons Optional list of icons for this tool.
	 * @param meta See specification for notes on _meta usage
	 */
	@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") Map<String, Object> inputSchema,
		@JsonProperty("outputSchema") Map<String, Object> outputSchema,
		@JsonProperty("annotations") ToolAnnotations annotations,
		@JsonProperty("_meta") Map<String, Object> meta,
		@JsonProperty("icons") List<Icon> icons) { // @formatter:on

		public Tool {
			Assert.notNull(name, "name must not be null");
			Assert.notNull(inputSchema, "inputSchema must not be null");
		}

		/**
		 * @deprecated Use {@link #builder(String, Map)}
		 */
		@Deprecated
		public Tool(String name, String title, String description, Map<String, Object> inputSchema,
				Map<String, Object> outputSchema, ToolAnnotations annotations, Map<String, Object> meta) {
			this(name, title, description, inputSchema, outputSchema, annotations, meta, null);
		}

		@JsonCreator
		static Tool fromJson(@JsonProperty("name") String name, @JsonProperty("title") String title,
				@JsonProperty("description") String description,
				@JsonProperty("inputSchema") Map<String, Object> inputSchema,
				@JsonProperty("outputSchema") Map<String, Object> outputSchema,
				@JsonProperty("annotations") ToolAnnotations annotations,
				@JsonProperty("_meta") Map<String, Object> meta, @JsonProperty("icons") List<Icon> icons) {
			if (name == null || inputSchema == null) {
				List<String> missing = new ArrayList<>();
				if (name == null) {
					missing.add("name -> ''");
					name = "";
				}
				if (inputSchema == null) {
					missing.add("inputSchema -> {}");
					inputSchema = Map.of();
				}
				logger.warn("Tool: missing required fields during deserialization: {}", String.join(", ", missing));
			}
			return new Tool(name, title, description, inputSchema, outputSchema, annotations, meta, icons);
		}

		/**
		 * @deprecated Use {@link #builder(String, Map)} instead.
		 */
		@Deprecated
		public static Builder builder() {
			return new Builder();
		}

		/**
		 * Uses empty input schema.
		 * @param name
		 * @return
		 */
		@Deprecated
		public static Builder builder(String name) {
			return new Builder(name);
		}

		public static Builder builder(String name, Map<String, Object> inputSchema) {
			return new Builder(name, inputSchema);
		}

		public static Builder builder(String name, McpJsonMapper jsonMapper, String inputSchema) {
			return new Builder(name, schemaToMap(jsonMapper, inputSchema));
		}

		public static class Builder {

			private String name;

			private String title;

			private String description;

			private Map<String, Object> inputSchema;

			private Map<String, Object> outputSchema;

			private ToolAnnotations annotations;

			private List<Icon> icons;

			private Map<String, Object> meta;

			/**
			 * @deprecated Use {@link Tool#builder(String, Map)} instead.
			 */
			@Deprecated
			public Builder() {
			}

			/**
			 * @deprecated Use {@link Tool#builder(String, Map)} instead.
			 */
			@Deprecated
			private Builder(String name) {
				Assert.hasText(name, "name must not be empty");
				this.name = name;
			}

			private Builder(String name, Map<String, Object> inputSchema) {
				Assert.hasText(name, "name must not be empty");
				Assert.notNull(inputSchema, "inputSchema must not be null");
				this.name = name;
				this.inputSchema = inputSchema;
			}

			public Builder name(String name) {
				this.name = name;
				return this;
			}

			public Builder title(String title) {
				this.title = title;
				return this;
			}

			public Builder description(String description) {
				this.description = description;
				return this;
			}

			/**
			 * @deprecated use {@link #inputSchema(Map)} instead.
			 */
			@Deprecated
			public Builder inputSchema(JsonSchema inputSchema) {
				Map<String, Object> schema = new HashMap<>();
				if (inputSchema.type() != null)
					schema.put("type", inputSchema.type());
				if (inputSchema.properties() != null)
					schema.put("properties", inputSchema.properties());
				if (inputSchema.required() != null)
					schema.put("required", inputSchema.required());
				if (inputSchema.additionalProperties() != null)
					schema.put("additionalProperties", inputSchema.additionalProperties());
				if (inputSchema.defs() != null)
					schema.put("$defs", inputSchema.defs());
				if (inputSchema.definitions() != null)
					schema.put("definitions", inputSchema.definitions());
				return inputSchema(schema);
			}

			public Builder inputSchema(Map<String, Object> inputSchema) {
				this.inputSchema = inputSchema;
				return this;
			}

			public Builder inputSchema(McpJsonMapper jsonMapper, String inputSchema) {
				this.inputSchema = schemaToMap(jsonMapper, inputSchema);
				return this;
			}

			public Builder outputSchema(Map<String, Object> outputSchema) {
				this.outputSchema = outputSchema;
				return this;
			}

			public Builder outputSchema(McpJsonMapper jsonMapper, String outputSchema) {
				this.outputSchema = schemaToMap(jsonMapper, outputSchema);
				return this;
			}

			public Builder annotations(ToolAnnotations annotations) {
				this.annotations = annotations;
				return this;
			}

			public Builder icons(List<Icon> icons) {
				this.icons = icons;
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public Tool build() {
				Assert.hasText(name, "name must not be empty");
				if (inputSchema == null) {
					logger.warn("Input schema was not set, falling back to empty schema");
					inputSchema = Map.of("type", "object");
				}
				return new Tool(name, title, description, inputSchema, outputSchema, annotations, meta, icons);
			}

		}
	}

	private static Map<String, Object> schemaToMap(McpJsonMapper jsonMapper, String schema) {
		try {
			return jsonMapper.readValue(schema, MAP_TYPE_REF);
		}
		catch (IOException e) {
			throw new IllegalArgumentException("Invalid schema: " + schema, e);
		}
	}

	/**
	 * Used by the client to call a tool provided by the server.
	 *
	 * @param name The name of the tool to call. This must match a tool name from
	 * tools/list.
	 * @param arguments Arguments to pass to the tool. These must conform to the tool's
	 * input schema.
	 * @param meta Optional metadata about the request. This can include additional
	 * information like `progressToken`
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record CallToolRequest( // @formatter:off
		@JsonProperty("name") String name,
		@JsonProperty("arguments") Map<String, Object> arguments,
		@JsonProperty("_meta") Map<String, Object> meta) implements Request { // @formatter:on

		public CallToolRequest {
			Assert.notNull(name, "name must not be null");
		}

		@JsonCreator
		static CallToolRequest fromJson(@JsonProperty("name") String name,
				@JsonProperty("arguments") Map<String, Object> arguments,
				@JsonProperty("_meta") Map<String, Object> meta) {
			if (name == null) {
				logger.warn("CallToolRequest: missing required field 'name' during deserialization, using default ''");
				name = "";
			}
			return new CallToolRequest(name, arguments, meta);
		}

		@Deprecated
		public CallToolRequest(McpJsonMapper jsonMapper, String name, String jsonArguments) {
			this(name, parseJsonArguments(jsonMapper, jsonArguments), null);
		}

		@Deprecated
		public CallToolRequest(String name, Map<String, Object> arguments) {
			this(name, arguments, null);
		}

		private static Map<String, Object> parseJsonArguments(McpJsonMapper jsonMapper, String jsonArguments) {
			try {
				return jsonMapper.readValue(jsonArguments, MAP_TYPE_REF);
			}
			catch (IOException e) {
				throw new IllegalArgumentException("Invalid arguments: " + jsonArguments, e);
			}
		}

		/**
		 * @deprecated Use {@link #builder(String)} instead.
		 */
		@Deprecated
		public static Builder builder() {
			return new Builder();
		}

		public static Builder builder(String name) {
			return new Builder(name);
		}

		public static class Builder {

			private String name;

			private Map<String, Object> arguments;

			private Map<String, Object> meta;

			/**
			 * @deprecated Use {@link CallToolRequest#builder(String)} instead.
			 */
			@Deprecated
			public Builder() {
			}

			private Builder(String name) {
				Assert.hasText(name, "name must not be empty");
				this.name = name;
			}

			public Builder name(String name) {
				this.name = name;
				return this;
			}

			public Builder arguments(Map<String, Object> arguments) {
				this.arguments = arguments;
				return this;
			}

			public Builder arguments(McpJsonMapper jsonMapper, String jsonArguments) {
				this.arguments = parseJsonArguments(jsonMapper, jsonArguments);
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public Builder progressToken(Object 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);
			}

		}
	}

	/**
	 * 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 structuredContent An optional JSON object that represents the structured
	 * result of the tool call.
	 * @param meta See specification for notes on _meta usage
	 * <p>
	 * Note: {@code content} is required by the MCP specification. Deserialization accepts
	 * a missing value and substitutes an empty list to avoid breaking existing
	 * integrations that may omit the field.
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record CallToolResult( // @formatter:off
		@JsonProperty("content") List<Content> content,
		@JsonProperty("isError") Boolean isError,
		@JsonProperty("structuredContent") Object structuredContent,
		@JsonProperty("_meta") Map<String, Object> meta) implements Result { // @formatter:on

		public CallToolResult {
			Assert.notNull(content, "content must not be null");
		}

		@JsonCreator
		static CallToolResult fromJson(@JsonProperty("content") List<Content> content,
				@JsonProperty("isError") Boolean isError, @JsonProperty("structuredContent") Object structuredContent,
				@JsonProperty("_meta") Map<String, Object> meta) {
			if (content == null) {
				logger.warn("CallToolResult: missing required fields during deserialization: content -> []");
				content = List.of();
			}
			return new CallToolResult(content, isError, structuredContent, meta);
		}

		/**
		 * Creates a builder for {@link CallToolResult} with the required content list.
		 * @param content the content list
		 * @return a new builder instance
		 */
		public static Builder builder(List<Content> content) {
			return new Builder(content);
		}

		/**
		 * Creates a builder for {@link CallToolResult}.
		 * @return a new builder instance
		 */
		public static Builder builder() {
			return new Builder(new ArrayList<>());
		}

		/**
		 * Builder for {@link CallToolResult}.
		 */
		public static class Builder {

			private List<Content> content = new ArrayList<>();

			private Boolean isError = false;

			/**
			 * @deprecated Use {@link CallToolResult#builder()} factory method instead of
			 * instantiating the builder directly.
			 */
			@Deprecated
			public Builder() {
			}

			private Builder(List<Content> content) {
				this.content.addAll(content);
			}

			private Object structuredContent;

			private Map<String, Object> meta;

			/**
			 * Sets the content list for the tool result.
			 * @param content the content list
			 * @return this builder
			 */
			public Builder content(List<Content> content) {
				Assert.notNull(content, "content must not be null");
				this.content = new ArrayList<>(content);
				return this;
			}

			public Builder structuredContent(Object structuredContent) {
				Assert.notNull(structuredContent, "structuredContent must not be null");
				this.structuredContent = structuredContent;
				return this;
			}

			public Builder structuredContent(McpJsonMapper jsonMapper, String structuredContent) {
				Assert.hasText(structuredContent, "structuredContent must not be empty");
				try {
					this.structuredContent = jsonMapper.readValue(structuredContent, MAP_TYPE_REF);
				}
				catch (IOException e) {
					throw new IllegalArgumentException("Invalid structured content: " + structuredContent, e);
				}
				return this;
			}

			/**
			 * Sets the text content for the tool result.
			 * @param textContent the text content
			 * @return this builder
			 */
			public Builder textContent(List<String> textContent) {
				Assert.notNull(textContent, "textContent must not be null");
				textContent.stream().map(t -> TextContent.builder(t).build()).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");
				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(TextContent.builder(text).build());
			}

			/**
			 * 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<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			/**
			 * Builds a new {@link CallToolResult} instance.
			 * @return a new CallToolResult instance
			 */
			public CallToolResult build() {
				Assert.notNull(content, "content must not be null");
				return new CallToolResult(content, isError, structuredContent, meta);
			}

		}

	}

	// ---------------------------
	// Sampling Interfaces
	// ---------------------------
	/**
	 * The server's preferences for model selection, requested of the client during
	 * sampling.
	 *
	 * @param hints Optional hints to use for model selection. If multiple hints are
	 * specified, the client MUST evaluate them in order (such that the first match is
	 * taken). The client SHOULD prioritize these hints over the numeric priorities, but
	 * MAY still use the priorities to select from ambiguous matches
	 * @param costPriority How much to prioritize cost when selecting a model. A value of
	 * 0 means cost is not important, while a value of 1 means cost is the most important
	 * factor
	 * @param speedPriority How much to prioritize sampling speed (latency) when selecting
	 * a model. A value of 0 means speed is not important, while a value of 1 means speed
	 * is the most important factor
	 * @param intelligencePriority How much to prioritize intelligence and capabilities
	 * when selecting a model. A value of 0 means intelligence is not important, while a
	 * value of 1 means intelligence is the most important factor
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record ModelPreferences( // @formatter:off
		@JsonProperty("hints") List<ModelHint> hints,
		@JsonProperty("costPriority") Double costPriority,
		@JsonProperty("speedPriority") Double speedPriority,
		@JsonProperty("intelligencePriority") Double intelligencePriority) { // @formatter:on

		public static Builder builder() {
			return new Builder();
		}

		public static class Builder {

			private List<ModelHint> hints;

			private Double costPriority;

			private Double speedPriority;

			private Double intelligencePriority;

			public Builder hints(List<ModelHint> 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);
			}

		}
	}

	/**
	 * Hints to use for model selection.
	 *
	 * @param name A hint for a model name. The client SHOULD treat this as a substring of
	 * a model name; for example: `claude-3-5-sonnet` should match
	 * `claude-3-5-sonnet-20241022`, `sonnet` should match `claude-3-5-sonnet-20241022`,
	 * `claude-3-sonnet-20240229`, etc., `claude` should match any Claude model. The
	 * client MAY also map the string to a different provider's model name or a different
	 * model family, as long as it fills a similar niche
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record ModelHint(@JsonProperty("name") String name) {

		/**
		 * @deprecated Use {@link #ModelHint(String)}
		 */
		@Deprecated
		public static ModelHint of(String name) {
			return new ModelHint(name);
		}
	}

	/**
	 * Describes a message issued to or received from an LLM API.
	 *
	 * @param role The sender or recipient of messages and data in a conversation
	 * @param content The content of the message
	 * <p>
	 * Note: {@code role} and {@code content} are required by the MCP specification.
	 * Deserialization accepts missing values and substitutes defaults to avoid breaking
	 * existing integrations that may omit these fields.
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record SamplingMessage( // @formatter:off
		@JsonProperty("role") Role role,
		@JsonProperty("content") Content content) { // @formatter:on

		public SamplingMessage {
			Assert.notNull(role, "role must not be null");
			Assert.notNull(content, "content must not be null");
		}

		@JsonCreator
		static SamplingMessage fromJson(@JsonProperty("role") Role role, @JsonProperty("content") Content content) {
			if (role == null || content == null) {
				List<String> missing = new ArrayList<>();
				if (role == null) {
					missing.add("role -> 'user'");
					role = Role.USER;
				}
				if (content == null) {
					missing.add("content -> ''");
					content = TextContent.builder("").build();
				}
				logger.warn("SamplingMessage: missing required fields during deserialization: {}",
						String.join(", ", missing));
			}
			return new SamplingMessage(role, content);
		}

		public static Builder builder(Role role, Content content) {
			return new Builder(role, content);
		}

		public static class Builder {

			private final Role role;

			private final Content content;

			private Builder(Role role, Content content) {
				Assert.notNull(role, "role must not be null");
				Assert.notNull(content, "content must not be null");
				this.role = role;
				this.content = content;
			}

			public SamplingMessage build() {
				return new SamplingMessage(role, content);
			}

		}
	}

	/**
	 * A request from the server to sample an LLM via the client. The client has full
	 * discretion over which model to select. The client should also inform the user
	 * before beginning sampling, to allow them to inspect the request (human in the loop)
	 * and decide whether to approve it.
	 *
	 * @param messages The conversation messages to send to the LLM
	 * @param modelPreferences The server's preferences for which model to select. The
	 * client MAY ignore these preferences
	 * @param systemPrompt An optional system prompt the server wants to use for sampling.
	 * The client MAY modify or omit this prompt
	 * @param includeContext A request to include context from one or more MCP servers
	 * (including the caller), to be attached to the prompt. The client MAY ignore this
	 * request
	 * @param temperature Optional temperature parameter for sampling
	 * @param maxTokens The maximum number of tokens to sample, as requested by the
	 * server. The client MAY choose to sample fewer tokens than requested
	 * @param stopSequences Optional stop sequences for sampling
	 * @param metadata Optional metadata to pass through to the LLM provider. The format
	 * of this metadata is provider-specific
	 * @param meta See specification for notes on _meta usage
	 * <p>
	 * Note: {@code messages} and {@code maxTokens} are required by the MCP specification.
	 * Deserialization accepts missing values and substitutes defaults to avoid breaking
	 * existing integrations that may omit these fields.
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record CreateMessageRequest( // @formatter:off
		@JsonProperty("messages") List<SamplingMessage> messages,
		@JsonProperty("modelPreferences") ModelPreferences modelPreferences,
		@JsonProperty("systemPrompt") String systemPrompt,
		@JsonProperty("includeContext") ContextInclusionStrategy includeContext,
		@JsonProperty("temperature") Double temperature,
		@JsonProperty("maxTokens") Integer maxTokens,
		@JsonProperty("stopSequences") List<String> stopSequences,
		@JsonProperty("metadata") Map<String, Object> metadata,
		@JsonProperty("_meta") Map<String, Object> meta) implements Request { // @formatter:on

		public CreateMessageRequest {
			Assert.notNull(messages, "messages must not be null");
			Assert.notNull(maxTokens, "maxTokens must not be null");
		}

		@JsonCreator
		static CreateMessageRequest fromJson(@JsonProperty("messages") List<SamplingMessage> messages,
				@JsonProperty("modelPreferences") ModelPreferences modelPreferences,
				@JsonProperty("systemPrompt") String systemPrompt,
				@JsonProperty("includeContext") ContextInclusionStrategy includeContext,
				@JsonProperty("temperature") Double temperature, @JsonProperty("maxTokens") Integer maxTokens,
				@JsonProperty("stopSequences") List<String> stopSequences,
				@JsonProperty("metadata") Map<String, Object> metadata,
				@JsonProperty("_meta") Map<String, Object> meta) {
			if (messages == null || maxTokens == null) {
				List<String> missing = new ArrayList<>();
				if (messages == null) {
					missing.add("messages -> []");
					messages = List.of();
				}
				if (maxTokens == null) {
					missing.add("maxTokens -> 0");
					maxTokens = 0;
				}
				logger.warn("CreateMessageRequest: missing required fields during deserialization: {}",
						String.join(", ", missing));
			}
			return new CreateMessageRequest(messages, modelPreferences, systemPrompt, includeContext, temperature,
					maxTokens, stopSequences, metadata, meta);
		}

		// backwards compatibility constructor
		public CreateMessageRequest(List<SamplingMessage> messages, ModelPreferences modelPreferences,
				String systemPrompt, ContextInclusionStrategy includeContext, Double temperature, Integer maxTokens,
				List<String> stopSequences, Map<String, Object> metadata) {
			this(messages, modelPreferences, systemPrompt, includeContext, temperature, maxTokens, stopSequences,
					metadata, null);
		}

		public enum ContextInclusionStrategy {

		// @formatter:off
			@JsonProperty("none") NONE,
			@JsonProperty("thisServer") THIS_SERVER,
			@JsonProperty("allServers") ALL_SERVERS
		} // @formatter:on

		/**
		 * @deprecated Use {@link #builder(List, int)} instead.
		 */
		@Deprecated
		public static Builder builder() {
			return new Builder();
		}

		public static Builder builder(List<SamplingMessage> messages, int maxTokens) {
			return new Builder(messages, maxTokens);
		}

		public static class Builder {

			private List<SamplingMessage> messages;

			private ModelPreferences modelPreferences;

			private String systemPrompt;

			private ContextInclusionStrategy includeContext;

			private Double temperature;

			private Integer maxTokens;

			private List<String> stopSequences;

			private Map<String, Object> metadata;

			private Map<String, Object> meta;

			/**
			 * @deprecated Use {@link CreateMessageRequest#builder(List, int)} factory
			 * method instead.
			 */
			@Deprecated
			public Builder() {
			}

			private Builder(List<SamplingMessage> messages, int maxTokens) {
				Assert.notNull(messages, "messages must not be null");
				this.messages = messages;
				this.maxTokens = maxTokens;
			}

			public Builder messages(List<SamplingMessage> messages) {
				Assert.notNull(messages, "messages must not be null");
				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<String> stopSequences) {
				this.stopSequences = stopSequences;
				return this;
			}

			public Builder metadata(Map<String, Object> metadata) {
				this.metadata = metadata;
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public Builder progressToken(Object progressToken) {
				if (this.meta == null) {
					this.meta = new HashMap<>();
				}
				this.meta.put("progressToken", progressToken);
				return this;
			}

			public CreateMessageRequest build() {
				Assert.notNull(messages, "messages must not be null");
				Assert.notNull(maxTokens, "maxTokens must not be null");
				return new CreateMessageRequest(messages, modelPreferences, systemPrompt, includeContext, temperature,
						maxTokens, stopSequences, metadata, meta);
			}

		}
	}

	// TODO: role, content and model are required
	/**
	 * The client's response to a sampling/create_message request from the server. The
	 * client should inform the user before returning the sampled message, to allow them
	 * to inspect the response (human in the loop) and decide whether to allow the server
	 * to see it.
	 *
	 * @param role The role of the message sender (typically assistant)
	 * @param content The content of the sampled message
	 * @param model The name of the model that generated the message
	 * @param stopReason The reason why sampling stopped, if known
	 * @param meta See specification for notes on _meta usage
	 */
	@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,
		@JsonProperty("_meta") Map<String, Object> meta) implements Result { // @formatter:on

		public CreateMessageResult {
			Assert.notNull(role, "role must not be null");
			Assert.notNull(content, "content must not be null");
			Assert.notNull(model, "model must not be null");
		}

		@JsonCreator
		static CreateMessageResult fromJson(@JsonProperty("role") Role role, @JsonProperty("content") Content content,
				@JsonProperty("model") String model, @JsonProperty("stopReason") StopReason stopReason,
				@JsonProperty("_meta") Map<String, Object> meta) {
			if (role == null || content == null || model == null) {
				List<String> missing = new ArrayList<>();
				if (role == null) {
					missing.add("role -> 'assistant'");
					role = Role.ASSISTANT;
				}
				if (content == null) {
					missing.add("content -> ''");
					content = TextContent.builder("").build();
				}
				if (model == null) {
					missing.add("model -> ''");
					model = "";
				}
				logger.warn("CreateMessageResult: missing required fields during deserialization: {}",
						String.join(", ", missing));
			}
			return new CreateMessageResult(role, content, model, stopReason, meta);
		}

		public enum StopReason {

		// @formatter:off

			@JsonProperty("endTurn") END_TURN("endTurn"),
			@JsonProperty("stopSequence") STOP_SEQUENCE("stopSequence"),
			@JsonProperty("maxTokens") MAX_TOKENS("maxTokens"),
			@JsonProperty("unknown") UNKNOWN("unknown"); // @formatter:on

			private final String value;

			private static final Map<String, StopReason> BY_VALUE;

			static {
				Map<String, StopReason> m = new HashMap<>();
				for (StopReason r : values()) {
					m.put(r.value, r);
				}
				BY_VALUE = Map.copyOf(m);
			}

			StopReason(String value) {
				this.value = value;
			}

			@JsonCreator
			public static StopReason of(String value) {
				return BY_VALUE.getOrDefault(value, UNKNOWN);
			}

		}

		// backwards compatibility constructor
		public CreateMessageResult(Role role, Content content, String model, StopReason stopReason) {
			this(role, content, model, stopReason, null);
		}

		@Deprecated
		public static Builder builder() {
			return new Builder(Role.ASSISTANT);
		}

		public static Builder builder(Role role, String textContent, String model) {
			return builder(role, TextContent.builder(textContent).build(), model);
		}

		public static Builder builder(Role role, Content content, String model) {
			return new Builder(role, content, model);
		}

		public static class Builder {

			private Role role;

			private Content content;

			private String model;

			private StopReason stopReason = StopReason.END_TURN;

			private Map<String, Object> meta;

			// temporary to keep deprecated use
			private Builder(Role role) {
				Assert.notNull(role, "role must not be null");
				this.role = role;
			}

			Builder(Role role, Content content, String model) {
				Assert.notNull(role, "role must not be null");
				Assert.notNull(content, "content must not be null");
				Assert.notNull(model, "model must not be null");
				this.role = role;
				this.content = content;
				this.model = model;
			}

			@Deprecated
			public Builder role(Role role) {
				this.role = role;
				return this;
			}

			@Deprecated
			public Builder content(Content content) {
				this.content = content;
				return this;
			}

			@Deprecated
			public Builder model(String model) {
				this.model = model;
				return this;
			}

			public Builder stopReason(StopReason stopReason) {
				this.stopReason = stopReason;
				return this;
			}

			@Deprecated
			public Builder message(String message) {
				this.content = TextContent.builder(message).build();
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public CreateMessageResult build() {
				return new CreateMessageResult(role, content, model, stopReason, meta);
			}

		}
	}

	// Elicitation

	/**
	 * An option in a titled enum schema, with a machine-readable value and a
	 * human-readable display label.
	 *
	 * @param constValue The machine-readable value of the option
	 * @param title The human-readable display label
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record EnumSchemaOption( // @formatter:off
		@JsonProperty("const") String constValue,
		@JsonProperty("title") String title) { // @formatter:on

		public EnumSchemaOption {
			Assert.notNull(constValue, "constValue must not be null");
			Assert.notNull(title, "title must not be null");
		}

		@JsonCreator
		static EnumSchemaOption fromJson(@JsonProperty("const") String constValue,
				@JsonProperty("title") String title) {
			if (constValue == null || title == null) {
				List<String> missing = new ArrayList<>();
				if (constValue == null) {
					missing.add("constValue -> ''");
					constValue = "";
				}
				if (title == null) {
					missing.add("title -> ''");
					title = "";
				}
				logger.warn("EnumSchemaOption: missing required fields during deserialization: {}",
						String.join(", ", missing));
			}
			return new EnumSchemaOption(constValue, title);
		}

	}

	/**
	 * Legacy enum schema with optional display names via the non-standard
	 * {@code enumNames} property. Use {@link TitledSingleSelectEnumSchema} instead.
	 *
	 * @param title Optional title for the enum field
	 * @param description Optional description for the enum field
	 * @param enumValues Array of enum values to choose from
	 * @param enumNames Optional display names for enum values (non-standard per JSON
	 * Schema 2020-12)
	 * @param defaultValue Optional default value
	 * @deprecated Use {@link TitledSingleSelectEnumSchema} instead
	 */
	@Deprecated
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record LegacyTitledEnumSchema( // @formatter:off
		@JsonProperty("title") String title,
		@JsonProperty("description") String description,
		@JsonProperty("enum") List<String> enumValues,
		@JsonProperty("enumNames") List<String> enumNames,
		@JsonProperty("default") String defaultValue) { // @formatter:on

		public LegacyTitledEnumSchema {
			Assert.notNull(enumValues, "enumValues must not be null");
		}

		@JsonProperty("type")
		public String type() {
			return "string";
		}

		public static Builder builder() {
			return new Builder();
		}

		public static class Builder {

			private String title;

			private String description;

			private List<String> enumValues;

			private List<String> enumNames;

			private String defaultValue;

			private Builder() {
			}

			public Builder title(String title) {
				this.title = title;
				return this;
			}

			public Builder description(String description) {
				this.description = description;
				return this;
			}

			public Builder enumValues(List<String> enumValues) {
				Assert.notNull(enumValues, "enumValues must not be null");
				this.enumValues = new ArrayList<>(enumValues);
				return this;
			}

			public Builder enumValues(String... enumValues) {
				Assert.notNull(enumValues, "enumValues must not be null");
				this.enumValues = Arrays.asList(enumValues);
				return this;
			}

			public Builder enumNames(List<String> enumNames) {
				Assert.notNull(enumNames, "enumNames must not be null");
				this.enumNames = new ArrayList<>(enumNames);
				return this;
			}

			public Builder enumNames(String... enumNames) {
				Assert.notNull(enumNames, "enumNames must not be null");
				this.enumNames = Arrays.asList(enumNames);
				return this;
			}

			public Builder defaultValue(String defaultValue) {
				this.defaultValue = defaultValue;
				return this;
			}

			public LegacyTitledEnumSchema build() {
				Assert.notEmpty(enumValues, "enumValues must not be empty");
				return new LegacyTitledEnumSchema(title, description, enumValues, enumNames, defaultValue);
			}

		}
	}

	/**
	 * Schema for single-selection enumeration without display titles for options.
	 *
	 * @param title Optional title for the enum field
	 * @param description Optional description for the enum field
	 * @param enumValues Array of enum values to choose from
	 * @param defaultValue Optional default value
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record UntitledSingleSelectEnumSchema( // @formatter:off
		@JsonProperty("title") String title,
		@JsonProperty("description") String description,
		@JsonProperty("enum") List<String> enumValues,
		@JsonProperty("default") String defaultValue) { // @formatter:on

		public UntitledSingleSelectEnumSchema {
			Assert.notNull(enumValues, "enumValues must not be null");
		}

		@JsonProperty("type")
		public String type() {
			return "string";
		}

		public static Builder builder() {
			return new Builder();
		}

		public static class Builder {

			private String title;

			private String description;

			private List<String> enumValues;

			private String defaultValue;

			private Builder() {
			}

			public Builder title(String title) {
				this.title = title;
				return this;
			}

			public Builder description(String description) {
				this.description = description;
				return this;
			}

			public Builder enumValues(List<String> enumValues) {
				Assert.notNull(enumValues, "enumValues must not be null");
				this.enumValues = new ArrayList<>(enumValues);
				return this;
			}

			public Builder enumValues(String... enumValues) {
				Assert.notNull(enumValues, "enumValues must not be null");
				this.enumValues = Arrays.asList(enumValues);
				return this;
			}

			public Builder defaultValue(String defaultValue) {
				this.defaultValue = defaultValue;
				return this;
			}

			public UntitledSingleSelectEnumSchema build() {
				Assert.notEmpty(enumValues, "enumValues must not be empty");
				return new UntitledSingleSelectEnumSchema(title, description, enumValues, defaultValue);
			}

		}
	}

	/**
	 * Schema for single-selection enumeration with display titles for each option.
	 *
	 * @param title Optional title for the enum field
	 * @param description Optional description for the enum field
	 * @param oneOf Array of enum options, each with a machine-readable value and a
	 * human-readable display label
	 * @param defaultValue Optional default value
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record TitledSingleSelectEnumSchema( // @formatter:off
		@JsonProperty("title") String title,
		@JsonProperty("description") String description,
		@JsonProperty("oneOf") List<EnumSchemaOption> oneOf,
		@JsonProperty("default") String defaultValue) { // @formatter:on

		public TitledSingleSelectEnumSchema {
			Assert.notEmpty(oneOf, "oneOf must not be empty");
		}

		@JsonProperty("type")
		public String type() {
			return "string";
		}

		public static Builder builder() {
			return new Builder();
		}

		public static class Builder {

			private String title;

			private String description;

			private List<EnumSchemaOption> oneOf;

			private String defaultValue;

			private Builder() {
			}

			public Builder title(String title) {
				this.title = title;
				return this;
			}

			public Builder description(String description) {
				this.description = description;
				return this;
			}

			public Builder oneOf(List<EnumSchemaOption> oneOf) {
				Assert.notNull(oneOf, "oneOf must not be null");
				this.oneOf = new ArrayList<>(oneOf);
				return this;
			}

			public Builder oneOf(EnumSchemaOption... oneOf) {
				Assert.notNull(oneOf, "oneOf must not be null");
				this.oneOf = Arrays.asList(oneOf);
				return this;
			}

			public Builder defaultValue(String defaultValue) {
				this.defaultValue = defaultValue;
				return this;
			}

			public TitledSingleSelectEnumSchema build() {
				Assert.notEmpty(oneOf, "oneOf must not be empty");
				return new TitledSingleSelectEnumSchema(title, description, oneOf, defaultValue);
			}

		}
	}

	/**
	 * The items schema for {@link UntitledMultiSelectEnumSchema}, describing the allowed
	 * enum values.
	 *
	 * @param enumValues Array of enum values to choose from
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record UntitledMultiSelectItems( // @formatter:off
		@JsonProperty("enum") List<String> enumValues) { // @formatter:on

		public UntitledMultiSelectItems {
			Assert.notNull(enumValues, "enumValues must not be null");
		}

		@JsonProperty("type")
		public String type() {
			return "string";
		}

		public static Builder builder() {
			return new Builder();
		}

		public static class Builder {

			private List<String> enumValues;

			private Builder() {
			}

			public Builder enumValues(List<String> enumValues) {
				Assert.notNull(enumValues, "enumValues must not be null");
				this.enumValues = new ArrayList<>(enumValues);
				return this;
			}

			public Builder enumValues(String... enumValues) {
				Assert.notNull(enumValues, "enumValues must not be null");
				this.enumValues = Arrays.asList(enumValues);
				return this;
			}

			public UntitledMultiSelectItems build() {
				Assert.notEmpty(enumValues, "enumValues must not be empty");
				return new UntitledMultiSelectItems(enumValues);
			}

		}
	}

	/**
	 * Schema for multiple-selection enumeration without display titles for options.
	 *
	 * @param title Optional title for the enum field
	 * @param description Optional description for the enum field
	 * @param items Schema for the array items, containing the list of enum values
	 * @param minItems Optional minimum number of items to select
	 * @param maxItems Optional maximum number of items to select
	 * @param defaultValue Optional default selected values
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record UntitledMultiSelectEnumSchema( // @formatter:off
		@JsonProperty("title") String title,
		@JsonProperty("description") String description,
		@JsonProperty("items") UntitledMultiSelectItems items,
		@JsonProperty("minItems") Integer minItems,
		@JsonProperty("maxItems") Integer maxItems,
		@JsonProperty("default") List<String> defaultValue) { // @formatter:on

		public UntitledMultiSelectEnumSchema {
			Assert.notNull(items, "items must not be null");
		}

		@JsonProperty("type")
		public String type() {
			return "array";
		}

		public static Builder builder(UntitledMultiSelectItems items) {
			return new Builder(items);
		}

		public static class Builder {

			private String title;

			private String description;

			private UntitledMultiSelectItems items;

			private Integer minItems;

			private Integer maxItems;

			private List<String> defaultValue;

			private Builder(UntitledMultiSelectItems items) {
				Assert.notNull(items, "items must not be null");
				this.items = items;
			}

			public Builder title(String title) {
				this.title = title;
				return this;
			}

			public Builder description(String description) {
				this.description = description;
				return this;
			}

			public Builder items(UntitledMultiSelectItems items) {
				Assert.notNull(items, "items must not be null");
				this.items = items;
				return this;
			}

			public Builder minItems(Integer minItems) {
				this.minItems = minItems;
				return this;
			}

			public Builder maxItems(Integer maxItems) {
				this.maxItems = maxItems;
				return this;
			}

			public Builder defaults(String... defaultValue) {
				Assert.notNull(defaultValue, "defaultValue must not be null");
				this.defaultValue = Arrays.asList(defaultValue);
				return this;
			}

			public Builder defaults(List<String> defaultValue) {
				Assert.notNull(defaultValue, "defaultValue must not be null");
				this.defaultValue = new ArrayList<>(defaultValue);
				return this;
			}

			public UntitledMultiSelectEnumSchema build() {
				return new UntitledMultiSelectEnumSchema(title, description, items, minItems, maxItems, defaultValue);
			}

		}
	}

	/**
	 * The items schema for {@link TitledMultiSelectEnumSchema}, describing the allowed
	 * enum options with display labels.
	 *
	 * @param anyOf Array of enum options, each with a machine-readable value and a
	 * human-readable display label
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record TitledMultiSelectItems( // @formatter:off
		@JsonProperty("anyOf") List<EnumSchemaOption> anyOf) { // @formatter:on

		public TitledMultiSelectItems {
			Assert.notNull(anyOf, "anyOf must not be null");
		}

		public static Builder builder() {
			return new Builder();
		}

		public static class Builder {

			private List<EnumSchemaOption> anyOf;

			private Builder() {
			}

			public Builder anyOf(List<EnumSchemaOption> anyOf) {
				Assert.notNull(anyOf, "anyOf must not be null");
				this.anyOf = new ArrayList<>(anyOf);
				return this;
			}

			public Builder anyOf(EnumSchemaOption... anyOf) {
				Assert.notNull(anyOf, "anyOf must not be null");
				this.anyOf = Arrays.asList(anyOf);
				return this;
			}

			public TitledMultiSelectItems build() {
				Assert.notEmpty(anyOf, "anyOf must not be empty");
				return new TitledMultiSelectItems(anyOf);
			}

		}
	}

	/**
	 * Schema for multiple-selection enumeration with display titles for each option.
	 *
	 * @param title Optional title for the enum field
	 * @param description Optional description for the enum field
	 * @param items Schema for the array items, containing the list of titled enum options
	 * @param minItems Optional minimum number of items to select
	 * @param maxItems Optional maximum number of items to select
	 * @param defaultValue Optional default selected values
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record TitledMultiSelectEnumSchema( // @formatter:off
		@JsonProperty("title") String title,
		@JsonProperty("description") String description,
		@JsonProperty("items") TitledMultiSelectItems items,
		@JsonProperty("minItems") Integer minItems,
		@JsonProperty("maxItems") Integer maxItems,
		@JsonProperty("default") List<String> defaultValue) { // @formatter:on

		public TitledMultiSelectEnumSchema {
			Assert.notNull(items, "items must not be null");
		}

		@JsonProperty("type")
		public String type() {
			return "array";
		}

		public static Builder builder(TitledMultiSelectItems items) {
			return new Builder(items);
		}

		public static class Builder {

			private String title;

			private String description;

			private TitledMultiSelectItems items;

			private Integer minItems;

			private Integer maxItems;

			private List<String> defaultValue;

			private Builder(TitledMultiSelectItems items) {
				Assert.notNull(items, "items must not be null");
				this.items = items;
			}

			public Builder title(String title) {
				this.title = title;
				return this;
			}

			public Builder description(String description) {
				this.description = description;
				return this;
			}

			public Builder items(TitledMultiSelectItems items) {
				Assert.notNull(items, "items must not be null");
				this.items = items;
				return this;
			}

			public Builder minItems(Integer minItems) {
				this.minItems = minItems;
				return this;
			}

			public Builder maxItems(Integer maxItems) {
				this.maxItems = maxItems;
				return this;
			}

			public Builder defaults(List<String> defaultValue) {
				Assert.notNull(defaultValue, "defaultValue must not be null");
				this.defaultValue = new ArrayList<>(defaultValue);
				return this;
			}

			public Builder defaults(String... defaultValue) {
				Assert.notNull(defaultValue, "defaultValue must not be null");
				this.defaultValue = Arrays.asList(defaultValue);
				return this;
			}

			public TitledMultiSelectEnumSchema build() {
				return new TitledMultiSelectEnumSchema(title, description, items, minItems, maxItems, defaultValue);
			}

		}
	}

	/**
	 * Schema for a boolean field in a form-based elicitation request.
	 *
	 * @param title Optional title for the boolean field
	 * @param description Optional description for the boolean field
	 * @param defaultValue Optional default value
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record BooleanSchema( // @formatter:off
		@JsonProperty("title") String title,
		@JsonProperty("description") String description,
		@JsonProperty("default") Boolean defaultValue) { // @formatter:on

		@JsonProperty("type")
		public String type() {
			return "boolean";
		}

		public static Builder builder() {
			return new Builder();
		}

		public static class Builder {

			private String title;

			private String description;

			private Boolean defaultValue;

			private Builder() {
			}

			public Builder title(String title) {
				this.title = title;
				return this;
			}

			public Builder description(String description) {
				this.description = description;
				return this;
			}

			public Builder defaultValue(Boolean defaultValue) {
				this.defaultValue = defaultValue;
				return this;
			}

			public BooleanSchema build() {
				return new BooleanSchema(title, description, defaultValue);
			}

		}
	}

	/**
	 * Schema for a numeric field in a form-based elicitation request, supporting both
	 * {@code "number"} (floating-point) and {@code "integer"} types.
	 *
	 * @param title Optional title for the numeric field
	 * @param description Optional description for the numeric field
	 * @param type The JSON Schema type, either {@code "number"} or {@code "integer"};
	 * defaults to {@code "number"} in the builder
	 * @param minimum Optional minimum value (inclusive)
	 * @param maximum Optional maximum value (inclusive)
	 * @param defaultValue Optional default value
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record NumberSchema( // @formatter:off
		@JsonProperty("title") String title,
		@JsonProperty("description") String description,
		@JsonProperty("type") String type,
		@JsonProperty("minimum") Number minimum,
		@JsonProperty("maximum") Number maximum,
		@JsonProperty("default") Number defaultValue) { // @formatter:on

		public NumberSchema {
			Assert.notNull(type, "type must not be null");
		}

		public static Builder builder() {
			return new Builder();
		}

		public static class Builder {

			private String title;

			private String description;

			private String type = "number";

			private Number minimum;

			private Number maximum;

			private Number defaultValue;

			private Builder() {
			}

			public Builder title(String title) {
				this.title = title;
				return this;
			}

			public Builder description(String description) {
				this.description = description;
				return this;
			}

			public Builder integer() {
				this.type = "integer";
				return this;
			}

			public Builder minimum(Number minimum) {
				this.minimum = minimum;
				return this;
			}

			public Builder maximum(Number maximum) {
				this.maximum = maximum;
				return this;
			}

			public Builder defaultValue(Number defaultValue) {
				this.defaultValue = defaultValue;
				return this;
			}

			public NumberSchema build() {
				return new NumberSchema(title, description, type, minimum, maximum, defaultValue);
			}

		}
	}

	/**
	 * Schema for a text input field in a form-based elicitation request.
	 *
	 * @param title Optional title for the text field
	 * @param description Optional description for the text field
	 * @param minLength Optional minimum string length
	 * @param maxLength Optional maximum string length
	 * @param format Optional format hint (e.g. {@code "email"}, {@code "uri"})
	 * @param defaultValue Optional default value
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record StringSchema( // @formatter:off
		@JsonProperty("title") String title,
		@JsonProperty("description") String description,
		@JsonProperty("minLength") Integer minLength,
		@JsonProperty("maxLength") Integer maxLength,
		@JsonProperty("format") String format,
		@JsonProperty("default") String defaultValue) { // @formatter:on

		@JsonProperty("type")
		public String type() {
			return "string";
		}

		public static Builder builder() {
			return new Builder();
		}

		public static class Builder {

			private String title;

			private String description;

			private Integer minLength;

			private Integer maxLength;

			private String format;

			private String defaultValue;

			private Builder() {
			}

			public Builder title(String title) {
				this.title = title;
				return this;
			}

			public Builder description(String description) {
				this.description = description;
				return this;
			}

			public Builder minLength(Integer minLength) {
				this.minLength = minLength;
				return this;
			}

			public Builder maxLength(Integer maxLength) {
				this.maxLength = maxLength;
				return this;
			}

			public Builder format(String format) {
				this.format = format;
				return this;
			}

			public Builder defaultValue(String defaultValue) {
				this.defaultValue = defaultValue;
				return this;
			}

			public StringSchema build() {
				Assert.isTrue(
						format == null || format.equals("uri") || format.equals("email") || format.equals("date")
								|| format.equals("date-time"),
						"format must be one of: null, \"uri\", \"email\", \"date\", \"date-time\"");
				return new StringSchema(title, description, minLength, maxLength, format, defaultValue);
			}

		}
	}

	/**
	 * A request from the server to elicit additional information from the user, either
	 * through the client or out-of-band.
	 *
	 * @see ElicitFormRequest
	 * @see ElicitUrlRequest
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "mode",
			defaultImpl = ElicitFormRequest.class)
	@JsonSubTypes({ @JsonSubTypes.Type(value = ElicitFormRequest.class, name = ElicitFormRequest.MODE),
			@JsonSubTypes.Type(value = ElicitUrlRequest.class, name = ElicitUrlRequest.MODE) })
	public interface ElicitRequest extends Request {

		String message();

		Map<String, Object> meta();

		String mode();

		/**
		 * @deprecated Use {@link ElicitFormRequest#builder(String, Map)} instead.
		 */
		@Deprecated
		static ElicitFormRequest.Builder builder() {
			return new ElicitFormRequest.Builder();
		}

		/**
		 * @deprecated Use {@link ElicitFormRequest#builder(String, Map)} instead.
		 */
		@Deprecated
		static ElicitFormRequest.Builder builder(String message, Map<String, Object> requestedSchema) {
			return new ElicitFormRequest.Builder(message, requestedSchema);
		}

	}

	/**
	 * A request from the server to elicit additional information from the user via the
	 * client, using {@code form} mode.
	 * <p>
	 * The requested schema is flexible, but for standard schemas, consider using one the
	 * following types:
	 * <ul>
	 * <li>{@link BooleanSchema}
	 * <li>{@link NumberSchema}
	 * <li>{@link StringSchema}
	 * <li>{@link LegacyTitledEnumSchema}
	 * <li>{@link TitledSingleSelectEnumSchema}
	 * <li>{@link TitledMultiSelectEnumSchema}
	 * <li>{@link UntitledSingleSelectEnumSchema}
	 * <li>{@link UntitledMultiSelectEnumSchema}
	 * </ul>
	 *
	 * These can be used with a JSON mapper:
	 *
	 * <pre>
	 * var mapper = McpJsonDefaults.getMapper();
	 * TypeRef&lt;Map&lt;String, Object&gt;&gt; mapType = new TypeRef<>() { };
	 * var first = UntitledSingleSelectEnumSchema.builder()
	 *           .enumValues("option1", "option2", "option3")
	 *           .build();
	 * var second = BooleanSchema
	 *           .builder()
	 *           .title("Say yes")
	 *           .description("By selecting this, you say yes to the thing")
	 *           .build();
	 * Map&lt;String, Object&gt; requestedSchema = Map.of(
	 *     "type", "object",
	 *     "properties", Map.of(
	 *         "first-thing", mapper.convertValue(first, mapType),
	 *         "second-thing", mapper.convertValue(second, mapType)),
	 *     "required", List.of("first-thing", "second-thing"));
	 * </pre>
	 *
	 * @param message The message to present to the user
	 * @param requestedSchema A restricted subset of JSON Schema. Only top-level
	 * properties are allowed, without nesting. Per SEP-1613, the dialect defaults to JSON
	 * Schema 2020-12 ({@link #JSON_SCHEMA_DIALECT_2020_12}) when no explicit
	 * {@code $schema} entry is present. To declare a different dialect, include a
	 * {@code "$schema"} key in the map. For type-safety in the schemas, use one of the
	 * supported schema types.
	 * @param meta See specification for notes on _meta usage
	 * <p>
	 * Note: {@code message} and {@code requestedSchema} are required by the MCP
	 * specification. Deserialization accepts missing values and substitutes defaults to
	 * avoid breaking existing integrations that may omit these fields.
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record ElicitFormRequest( // @formatter:off
		@JsonProperty("message") String message,
		@JsonProperty("requestedSchema") Map<String, Object> requestedSchema,
		@JsonProperty("_meta") Map<String, Object> meta) implements ElicitRequest { // @formatter:on

		public static final String MODE = "form";

		public ElicitFormRequest {
			Assert.notNull(message, "message must not be null");
			Assert.notNull(requestedSchema, "requestedSchema must not be null");
		}

		@Override
		@JsonProperty("mode")
		public String mode() {
			return MODE;
		}

		@JsonCreator
		static ElicitFormRequest fromJson(@JsonProperty("message") String message,
				@JsonProperty("requestedSchema") Map<String, Object> requestedSchema,
				@JsonProperty("_meta") Map<String, Object> meta) {
			if (message == null || requestedSchema == null) {
				List<String> missing = new ArrayList<>();
				if (message == null) {
					missing.add("message -> ''");
					message = "";
				}
				if (requestedSchema == null) {
					missing.add("requestedSchema -> {}");
					requestedSchema = Map.of();
				}
				logger.warn("ElicitFormRequest: missing required fields during deserialization: {}",
						String.join(", ", missing));
			}
			return new ElicitFormRequest(message, requestedSchema, meta);
		}

		public static Builder builder(String message, Map<String, Object> requestedSchema) {
			return new Builder(message, requestedSchema);
		}

		public static class Builder {

			private String message;

			private Map<String, Object> requestedSchema;

			private Map<String, Object> meta;

			/**
			 * @deprecated Use {@link ElicitFormRequest#builder(String, Map)} factory
			 * method instead.
			 */
			@Deprecated
			private Builder() {
			}

			private Builder(String message, Map<String, Object> requestedSchema) {
				Assert.notNull(message, "message must not be null");
				Assert.notNull(requestedSchema, "requestedSchema must not be null");
				this.message = message;
				this.requestedSchema = requestedSchema;
			}

			/**
			 * @deprecated Use {@link ElicitFormRequest#builder(String, Map)} factory
			 * method instead.
			 */
			@Deprecated
			public Builder message(String message) {
				Assert.notNull(message, "message must not be null");
				this.message = message;
				return this;
			}

			/**
			 * @deprecated Use {@link ElicitFormRequest#builder(String, Map)} factory
			 * method instead.
			 */
			@Deprecated
			public Builder requestedSchema(Map<String, Object> requestedSchema) {
				Assert.notNull(requestedSchema, "requestedSchema must not be null");
				this.requestedSchema = requestedSchema;
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public Builder progressToken(Object progressToken) {
				if (this.meta == null) {
					this.meta = new HashMap<>();
				}
				this.meta.put("progressToken", progressToken);
				return this;
			}

			public ElicitFormRequest build() {
				Assert.notNull(message, "message must not be null");
				Assert.notNull(requestedSchema, "requestedSchema must not be null");
				return new ElicitFormRequest(message, requestedSchema, meta);
			}

		}
	}

	/**
	 * A request from the server to elicit additional information from the user out of
	 * band, using {@code url} mode.
	 *
	 * @param message The message to present to the user
	 * @param url The URL the user must navigate to.
	 * @param elicitationId The elicitation ID of the elicitations reques.t
	 * @param meta See specification for notes on _meta usage
	 * <p>
	 * Note: {@code message}, {@code url} and {@code elicitationId} are required by the
	 * MCP specification. Deserialization accepts missing values and substitutes defaults
	 * to avoid breaking existing integrations that may omit these fields.
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record ElicitUrlRequest( // @formatter:off
		@JsonProperty("message") String message,
		@JsonProperty("url") String url,
		@JsonProperty("elicitationId") String elicitationId,
		@JsonProperty("_meta") Map<String, Object> meta) implements ElicitRequest { // @formatter:on

		public static final String MODE = "url";

		public ElicitUrlRequest {
			Assert.notNull(message, "message must not be null");
			Assert.notNull(url, "url must not be null");
			Assert.notNull(elicitationId, "elicitationId must not be null");
		}

		@Override
		@JsonProperty("mode")
		public String mode() {
			return MODE;
		}

		@JsonCreator
		static ElicitUrlRequest fromJson(@JsonProperty("message") String message, @JsonProperty("url") String url,
				@JsonProperty("elicitationId") String elicitationId, @JsonProperty("_meta") Map<String, Object> meta) {
			if (message == null || url == null || elicitationId == null) {
				List<String> missing = new ArrayList<>();
				if (message == null) {
					missing.add("message -> ''");
					message = "";
				}
				if (url == null) {
					missing.add("url -> ''");
					url = "";
				}
				if (elicitationId == null) {
					missing.add("elicitationId -> ''");
					elicitationId = "";
				}
				logger.warn("ElicitUrlRequest: missing required fields during deserialization: {}",
						String.join(", ", missing));
			}
			return new ElicitUrlRequest(message, url, elicitationId, meta);
		}

		public static Builder builder(String message, String url, String elicitationId) {
			return new Builder(message, url, elicitationId);
		}

		public static class Builder {

			private final String message;

			private final String url;

			private final String elicitationId;

			private Map<String, Object> meta;

			private Builder(String message, String url, String elicitationId) {
				Assert.notNull(message, "message must not be null");
				Assert.notNull(url, "url must not be null");
				Assert.notNull(elicitationId, "elicitationId must not be null");
				this.message = message;
				this.url = url;
				this.elicitationId = elicitationId;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public Builder progressToken(Object progressToken) {
				if (this.meta == null) {
					this.meta = new HashMap<>();
				}
				this.meta.put("progressToken", progressToken);
				return this;
			}

			public ElicitUrlRequest build() {
				Assert.notNull(message, "message must not be null");
				Assert.notNull(url, "url must not be null");
				Assert.notNull(elicitationId, "elicitationId must not be null");
				return new ElicitUrlRequest(message, url, elicitationId, meta);
			}

		}
	}

	/**
	 * The client's response to an elicitation request.
	 *
	 * @param action The user action in response to the elicitation. "accept": User
	 * submitted the form/confirmed the action, "decline": User explicitly declined the
	 * action, "cancel": User dismissed without making an explicit choice
	 * @param content The submitted form data, only present when action is "accept".
	 * Contains values matching the requested schema
	 * @param meta See specification for notes on _meta usage
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record ElicitResult( // @formatter:off
		@JsonProperty("action") Action action,
		@JsonProperty("content") Map<String, Object> content,
		@JsonProperty("_meta") Map<String, Object> meta) implements Result { // @formatter:on

		public ElicitResult {
			Assert.notNull(action, "action must not be null");
		}

		@JsonCreator
		static ElicitResult fromJson(@JsonProperty("action") Action action,
				@JsonProperty("content") Map<String, Object> content, @JsonProperty("_meta") Map<String, Object> meta) {
			if (action == null) {
				logger.warn(
						"ElicitResult: missing required field 'action' during deserialization, using default 'cancel'");
				action = Action.CANCEL;
			}
			return new ElicitResult(action, content, meta);
		}

		public enum Action {

		// @formatter:off

			@JsonProperty("accept") ACCEPT,
			@JsonProperty("decline") DECLINE,
			@JsonProperty("cancel") CANCEL

		} // @formatter:on

		// backwards compatibility constructor
		public ElicitResult(Action action, Map<String, Object> content) {
			this(action, content, null);
		}

		@Deprecated
		public static Builder builder() {
			return new Builder();
		}

		public static Builder builder(Action action) {
			return new Builder(action);
		}

		public static class Builder {

			private Action action;

			private Map<String, Object> content;

			private Map<String, Object> meta;

			// tepmorary to support deprecated builder
			private Builder() {

			}

			private Builder(Action action) {
				Assert.notNull(action, "action must not be null");
				this.action = action;
			}

			@Deprecated
			public Builder message(Action action) {
				this.action = action;
				return this;
			}

			public Builder content(Map<String, Object> content) {
				this.content = content;
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public ElicitResult build() {
				Assert.notNull(action, "action must not be null");
				return new ElicitResult(action, content, meta);
			}

		}
	}

	/**
	 * A notification from the server to the client indicating that an out-of-band URL
	 * elicitation interaction has completed.
	 *
	 * @param elicitationId The unique identifier of the completed elicitation
	 * @param meta See specification for notes on _meta usage
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record ElicitationCompleteNotification( // @formatter:off
	   @JsonProperty("elicitationId") String elicitationId,
	   @JsonProperty("_meta") Map<String, Object> meta) implements Notification { // @formatter:on

		public ElicitationCompleteNotification {
			Assert.notNull(elicitationId, "elicitationId must not be null");
		}

		@JsonCreator
		static ElicitationCompleteNotification fromJson(@JsonProperty("elicitationId") String elicitationId,
				@JsonProperty("_meta") Map<String, Object> meta) {
			if (elicitationId == null || elicitationId.isBlank()) {
				logger.warn(
						"ElicitationCompleteNotification: missing required field 'elicitationId' during deserialization, using default ''");
				elicitationId = "";
			}
			return new ElicitationCompleteNotification(elicitationId, meta);
		}

		public ElicitationCompleteNotification(String elicitationId) {
			this(elicitationId, null);
		}
	}

	// ---------------------------
	// Pagination Interfaces
	// ---------------------------
	/**
	 * A request that supports pagination using cursors.
	 *
	 * @param cursor An opaque token representing the current pagination position. If
	 * provided, the server should return results starting after this cursor
	 * @param meta See specification for notes on _meta usage
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record PaginatedRequest( // @formatter:off
		@JsonProperty("cursor") String cursor,
		@JsonProperty("_meta") Map<String, Object> 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);
		}
	}

	/**
	 * An opaque token representing the pagination position after the last returned
	 * result. If present, there may be more results available.
	 *
	 * @param nextCursor An opaque token representing the pagination position after the
	 * last returned result. If present, there may be more results available
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	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.
	 * @param meta See specification for notes on _meta usage
	 * <p>
	 * Note: {@code progressToken} and {@code progress} are required by the MCP
	 * specification. Deserialization accepts missing values and substitutes defaults to
	 * avoid breaking existing integrations that may omit these fields.
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record ProgressNotification( // @formatter:off
		@JsonProperty("progressToken") Object progressToken,
		@JsonProperty("progress") Double progress,
		@JsonProperty("total") Double total,
		@JsonProperty("message") String message,
		@JsonProperty("_meta") Map<String, Object> meta) implements Notification { // @formatter:on

		public ProgressNotification {
			Assert.notNull(progressToken, "progressToken must not be null");
			Assert.notNull(progress, "progress must not be null");
		}

		@JsonCreator
		static ProgressNotification fromJson(@JsonProperty("progressToken") Object progressToken,
				@JsonProperty("progress") Double progress, @JsonProperty("total") Double total,
				@JsonProperty("message") String message, @JsonProperty("_meta") Map<String, Object> meta) {
			if (progressToken == null || progress == null) {
				List<String> missing = new ArrayList<>();
				if (progressToken == null) {
					missing.add("progressToken -> ''");
					progressToken = "";
				}
				if (progress == null) {
					missing.add("progress -> 0.0");
					progress = 0.0;
				}
				logger.warn("ProgressNotification: missing required fields during deserialization: {}",
						String.join(", ", missing));
			}
			return new ProgressNotification(progressToken, progress, total, message, meta);
		}

		@Deprecated
		public ProgressNotification(Object progressToken, double progress, Double total, String message) {
			this(progressToken, progress, total, message, null);
		}

		public static Builder builder(Object progressToken, double progress) {
			return new Builder(progressToken, progress);
		}

		public static class Builder {

			private final Object progressToken;

			private final Double progress;

			private Double total;

			private String message;

			private Map<String, Object> meta;

			private Builder(Object progressToken, double progress) {
				Assert.notNull(progressToken, "progressToken must not be null");
				this.progressToken = progressToken;
				this.progress = progress;
			}

			public Builder total(Double total) {
				this.total = total;
				return this;
			}

			public Builder message(String message) {
				this.message = message;
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public ProgressNotification build() {
				return new ProgressNotification(progressToken, progress, total, message, meta);
			}

		}

	}

	/**
	 * 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 See specification for notes on _meta usage
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record ResourcesUpdatedNotification(// @formatter:off
		@JsonProperty("uri") String uri,
		@JsonProperty("_meta") Map<String, Object> meta) implements Notification { // @formatter:on

		public ResourcesUpdatedNotification {
			Assert.notNull(uri, "uri must not be null");
		}

		public ResourcesUpdatedNotification(String uri) {
			this(uri, null);
		}

		@JsonCreator
		static ResourcesUpdatedNotification fromJson(@JsonProperty("uri") String uri,
				@JsonProperty("_meta") Map<String, Object> meta) {
			if (uri == null) {
				logger.warn(
						"ResourcesUpdatedNotification: missing required field 'uri' during deserialization, using default ''");
				uri = "";
			}
			return new ResourcesUpdatedNotification(uri, meta);
		}
	}

	/**
	 * The Model Context Protocol (MCP) provides a standardized way for servers to send
	 * structured log messages to clients. Clients can control logging verbosity by
	 * setting minimum log levels, with servers sending notifications containing severity
	 * levels, optional logger names, and arbitrary JSON-serializable data.
	 *
	 * @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 See specification for notes on _meta usage
	 * <p>
	 * Note: {@code level} and {@code data} are required by the MCP specification.
	 * Deserialization accepts missing values and substitutes defaults to avoid breaking
	 * existing integrations that may omit these fields.
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record LoggingMessageNotification( // @formatter:off
		@JsonProperty("level") LoggingLevel level,
		@JsonProperty("logger") String logger,
		@JsonProperty("data") String data,
		@JsonProperty("_meta") Map<String, Object> meta) implements Notification { // @formatter:on

		public LoggingMessageNotification {
			Assert.notNull(level, "level must not be null");
			Assert.notNull(data, "data must not be null");
		}

		@JsonCreator
		static LoggingMessageNotification fromJson(@JsonProperty("level") LoggingLevel level,
				@JsonProperty("logger") String loggerName, @JsonProperty("data") String data,
				@JsonProperty("_meta") Map<String, Object> meta) {
			if (level == null || data == null) {
				List<String> missing = new ArrayList<>();
				if (level == null) {
					missing.add("level -> INFO");
					level = LoggingLevel.INFO;
				}
				if (data == null) {
					missing.add("data -> ''");
					data = "";
				}
				McpSchema.logger.warn("LoggingMessageNotification: missing required fields during deserialization: {}",
						String.join(", ", missing));
			}
			return new LoggingMessageNotification(level, loggerName, data, meta);
		}

		// backwards compatibility constructor
		public LoggingMessageNotification(LoggingLevel level, String logger, String data) {
			this(level, logger, data, null);
		}

		/**
		 * @deprecated Use {@link #builder(LoggingLevel, String)} instead.
		 */
		@Deprecated
		public static Builder builder() {
			return new Builder().level(LoggingLevel.INFO);
		}

		public static Builder builder(LoggingLevel level, String data) {
			return new Builder(level, data);
		}

		public static class Builder {

			private LoggingLevel level;

			private String logger = "server";

			private String data;

			private Map<String, Object> meta;

			/**
			 * @deprecated Use
			 * {@link LoggingMessageNotification#builder(LoggingLevel, String)} factory
			 * method instead.
			 */
			@Deprecated
			public Builder() {
			}

			private Builder(LoggingLevel level, String data) {
				Assert.notNull(level, "level must not be null");
				Assert.notNull(data, "data must not be null");
				this.level = level;
				this.data = data;
			}

			@Deprecated
			public Builder level(LoggingLevel level) {
				Assert.notNull(level, "level must not be null");
				this.level = level;
				return this;
			}

			public Builder logger(String logger) {
				this.logger = logger;
				return this;
			}

			@Deprecated
			public Builder data(String data) {
				Assert.notNull(data, "data must not be null");
				this.data = data;
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public LoggingMessageNotification build() {
				Assert.notNull(level, "level must not be null");
				Assert.notNull(data, "data must not be null");
				return new LoggingMessageNotification(level, logger, data, meta);
			}

		}
	}

	/**
	 * Severity levels for MCP log messages, ordered from least to most severe. The
	 * numeric {@link #level()} can be used to compare severities. Deserialization is
	 * case-insensitive and returns {@code null} for unrecognized values.
	 */
	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);
		// @formatter:on

		private final int level;

		private static final Map<String, LoggingLevel> BY_NAME;

		static {
			Map<String, LoggingLevel> m = new HashMap<>();
			for (LoggingLevel l : values()) {
				m.put(l.name().toLowerCase(), l);
			}
			BY_NAME = Map.copyOf(m);
		}

		LoggingLevel(int level) {
			this.level = level;
		}

		public int level() {
			return level;
		}

		@JsonCreator
		public static LoggingLevel fromValue(String value) {
			return value == null ? null : BY_NAME.get(value.toLowerCase());
		}

	}

	/**
	 * A request from the client to the server, to enable or adjust logging.
	 *
	 * @param level The level of logging that the client wants to receive from the server.
	 * The server should send all logs at this level and higher (i.e., more severe) to the
	 * client as notifications/message
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record SetLevelRequest(@JsonProperty("level") LoggingLevel level) {

		public SetLevelRequest {
			Assert.notNull(level, "level must not be null");
		}

		@JsonCreator
		static SetLevelRequest fromJson(@JsonProperty("level") LoggingLevel level) {
			if (level == null) {
				logger.warn(
						"SetLevelRequest: missing required field 'level' during deserialization, using default 'info'");
				level = LoggingLevel.INFO;
			}
			return new SetLevelRequest(level);
		}
	}

	// ---------------------------
	// Autocomplete
	// ---------------------------

	/**
	 * A reference to a prompt or resource that can be used as input for completion
	 * requests. Implementations are identified by a {@code "type"} discriminator field
	 * whose value maps to a concrete subtype via {@code @JsonSubTypes}.
	 */
	@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type",
			visible = true)
	@JsonSubTypes({ @JsonSubTypes.Type(value = PromptReference.class, name = PromptReference.TYPE),
			@JsonSubTypes.Type(value = ResourceReference.class, name = ResourceReference.TYPE) })
	public interface CompleteReference {

		default String type() {
			if (this instanceof PromptReference) {
				return PromptReference.TYPE;
			}
			else if (this instanceof ResourceReference) {
				return ResourceReference.TYPE;
			}
			throw new IllegalArgumentException("Unknown CompleteReference type: " + this);
		}

		@Deprecated
		default String identifier() {
			return null;
		}

	}

	/**
	 * Identifies a prompt for completion requests.
	 *
	 * @param type Always {@value #TYPE}; present as the polymorphic discriminator. Any
	 * non-null value other than {@value #TYPE} is replaced with {@value #TYPE} and a WARN
	 * is logged.
	 * @param name The name of the prompt
	 * @param title An optional title for the prompt
	 */
	@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, Identifier { // @formatter:on

		public static final String TYPE = "ref/prompt";

		public PromptReference {
			Assert.hasText(name, "name must not be null or empty");
			if (type != null && !TYPE.equals(type)) {
				logger.warn("PromptReference: 'type' argument '{}' is ignored, type is always '{}'", type, TYPE);
			}
			type = TYPE;
		}

		@JsonCreator
		static PromptReference fromJson(@JsonProperty("type") String type, @JsonProperty("name") String name,
				@JsonProperty("title") String title) {
			return new PromptReference(type, name, title);
		}

		/**
		 * @deprecated The {@code type} argument is ignored — the type discriminator is
		 * always {@value #TYPE}. Use {@link #PromptReference(String)} or the
		 * {@link #builder(String)} instead.
		 */
		@Deprecated
		public PromptReference(String type, String name) {
			this(type, name, null);
		}

		public PromptReference(String name) {
			this(TYPE, name, null);
		}

		@Override
		public String identifier() {
			return name();
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null || getClass() != obj.getClass())
				return false;
			PromptReference that = (PromptReference) obj;
			return java.util.Objects.equals(name, that.name);
		}

		@Override
		public int hashCode() {
			return java.util.Objects.hash(name);
		}

		public static Builder builder(String name) {
			return new Builder(name);
		}

		public static final class Builder {

			private final String name;

			private String title;

			private Builder(String name) {
				this.name = name;
			}

			public Builder title(String title) {
				this.title = title;
				return this;
			}

			public PromptReference build() {
				return new PromptReference(TYPE, name, title);
			}

		}

	}

	// TODO: this should actually be a ResourceTemplateReference
	/**
	 * A reference to a resource or resource template definition for completion requests.
	 *
	 * @param uri The URI or URI template of the resource
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record ResourceReference( // @formatter:off
		@JsonProperty("uri") String uri) implements McpSchema.CompleteReference { // @formatter:on

		public static final String TYPE = "ref/resource";

		public ResourceReference {
			Assert.notNull(uri, "uri must not be null");
		}

		@JsonProperty("type")
		@Override
		public String type() {
			return CompleteReference.super.type();
		}

		@JsonCreator
		static ResourceReference fromJson(@JsonProperty("uri") String uri, @JsonProperty("type") String type) {
			return new ResourceReference(uri);
		}

		@Deprecated
		public ResourceReference(String type, String uri) {
			this(uri);
			logger.warn("ResourceReference: type argument '{}' is ignored, type is always '{}'", type, TYPE);
		}

		@Override
		public String identifier() {
			return uri();
		}
	}

	/**
	 * A request from the client to the server, to ask for completion options.
	 *
	 * @param ref A reference to a prompt or resource template definition
	 * @param argument The argument's information for completion requests
	 * @param meta See specification for notes on _meta usage
	 * @param context Additional, optional context for completions
	 */
	@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<String, Object> meta,
		@JsonProperty("context") CompleteContext context) implements Request { // @formatter:on

		public CompleteRequest {
			Assert.notNull(ref, "ref must not be null");
			Assert.notNull(argument, "argument must not be null");
		}

		@JsonCreator
		static CompleteRequest fromJson(@JsonProperty("ref") McpSchema.CompleteReference ref,
				@JsonProperty("argument") CompleteArgument argument, @JsonProperty("_meta") Map<String, Object> meta,
				@JsonProperty("context") CompleteContext context) {
			return new CompleteRequest(ref, argument, meta, context);
		}

		@Deprecated
		public CompleteRequest(McpSchema.CompleteReference ref, CompleteArgument argument, Map<String, Object> meta) {
			this(ref, argument, meta, null);
		}

		@Deprecated
		public CompleteRequest(McpSchema.CompleteReference ref, CompleteArgument argument, CompleteContext context) {
			this(ref, argument, null, context);
		}

		@Deprecated
		public CompleteRequest(McpSchema.CompleteReference ref, CompleteArgument argument) {
			this(ref, argument, null, null);
		}

		public static Builder builder(McpSchema.CompleteReference ref, CompleteArgument argument) {
			return new Builder(ref, argument);
		}

		public static class Builder {

			private final McpSchema.CompleteReference ref;

			private final CompleteArgument argument;

			private Map<String, Object> meta;

			private CompleteContext context;

			private Builder(McpSchema.CompleteReference ref, CompleteArgument argument) {
				Assert.notNull(ref, "ref must not be null");
				Assert.notNull(argument, "argument must not be null");
				this.ref = ref;
				this.argument = argument;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public Builder context(CompleteContext context) {
				this.context = context;
				return this;
			}

			public CompleteRequest build() {
				return new CompleteRequest(ref, argument, meta, context);
			}

		}

		/**
		 * The argument's information for completion requests.
		 *
		 * @param name The name of the argument
		 * @param value The value of the argument to use for completion matching
		 */
		@JsonInclude(JsonInclude.Include.NON_ABSENT)
		@JsonIgnoreProperties(ignoreUnknown = true)
		public record CompleteArgument(@JsonProperty("name") String name, @JsonProperty("value") String value) {
			public CompleteArgument {
				Assert.hasText(name, "name must not be empty");
				Assert.notNull(value, "value must not be null");
			}
		}

		/**
		 * Additional, optional context for completions.
		 *
		 * @param arguments Previously-resolved variables in a URI template or prompt
		 */
		@JsonInclude(JsonInclude.Include.NON_ABSENT)
		@JsonIgnoreProperties(ignoreUnknown = true)
		public record CompleteContext(@JsonProperty("arguments") Map<String, String> arguments) {

			public static Builder builder() {
				return new Builder();
			}

			public static class Builder {

				private Map<String, String> arguments;

				public Builder arguments(Map<String, String> arguments) {
					this.arguments = arguments;
					return this;
				}

				public CompleteContext build() {
					return new CompleteContext(arguments);
				}

			}
		}
	}

	/**
	 * The server's response to a completion/complete request.
	 *
	 * @param completion The completion information containing values and metadata
	 * @param meta See specification for notes on _meta usage
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record CompleteResult(// @formatter:off
			@JsonProperty("completion") CompleteCompletion completion,
			@JsonProperty("_meta") Map<String, Object> meta) implements Result { // @formatter:on

		public CompleteResult {
			Assert.notNull(completion, "completion must not be null");
		}

		@JsonCreator
		static CompleteResult fromJson(@JsonProperty("completion") CompleteCompletion completion,
				@JsonProperty("_meta") Map<String, Object> meta) {
			if (completion == null) {
				logger.warn(
						"CompleteResult: missing required field 'completion' during deserialization, using default {values=[]}");
				completion = new CompleteCompletion(List.of(), null, null);
			}
			return new CompleteResult(completion, meta);
		}

		public CompleteResult(CompleteCompletion completion) {
			this(completion, null);
		}

		/**
		 * The server's response to a completion/complete request
		 *
		 * @param values An array of completion values. Must not exceed 100 items
		 * @param total The total number of completion options available. This can exceed
		 * the number of values actually sent in the response
		 * @param hasMore Indicates whether there are additional completion options beyond
		 * those provided in the current response, even if the exact total is unknown
		 */
		@JsonInclude(JsonInclude.Include.NON_ABSENT)
		@JsonIgnoreProperties(ignoreUnknown = true)
		public record CompleteCompletion( // @formatter:off
				@JsonProperty("values") List<String> values,
				@JsonProperty("total") Integer total,
				@JsonProperty("hasMore") Boolean hasMore) { // @formatter:on

			public CompleteCompletion {
				Assert.notNull(values, "values must not be null");
			}

			public CompleteCompletion(List<String> values) {
				this(values, null, null);
			}
		}
	}

	// ---------------------------
	// Content Types
	// ---------------------------

	/**
	 * A polymorphic content value that can appear in messages and tool results. The
	 * concrete type is determined by the {@code "type"} JSON property.
	 */
	@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
	@JsonSubTypes({ @JsonSubTypes.Type(value = TextContent.class, name = "text"),
			@JsonSubTypes.Type(value = ImageContent.class, name = "image"),
			@JsonSubTypes.Type(value = AudioContent.class, name = "audio"),
			@JsonSubTypes.Type(value = EmbeddedResource.class, name = "resource"),
			@JsonSubTypes.Type(value = ResourceLink.class, name = "resource_link") })
	public interface Content extends Meta {

		@JsonIgnore
		default String type() {
			if (this instanceof TextContent) {
				return "text";
			}
			else if (this instanceof ImageContent) {
				return "image";
			}
			else if (this instanceof AudioContent) {
				return "audio";
			}
			else if (this instanceof EmbeddedResource) {
				return "resource";
			}
			else if (this instanceof ResourceLink) {
				return "resource_link";
			}
			throw new IllegalArgumentException("Unknown content type: " + this);
		}

	}

	/**
	 * Text provided to or from an LLM.
	 *
	 * @param annotations Optional annotations for the client
	 * @param text The text content of the message
	 * @param meta See specification for notes on _meta usage
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record TextContent( // @formatter:off
		@JsonProperty("annotations") Annotations annotations,
		@JsonProperty("text") String text,
		@JsonProperty("_meta") Map<String, Object> meta) implements Annotated, Content { // @formatter:on

		public TextContent {
			Assert.notNull(text, "text must not be null");
		}

		@JsonCreator
		static TextContent fromJson(@JsonProperty("annotations") Annotations annotations,
				@JsonProperty("text") String text, @JsonProperty("_meta") Map<String, Object> meta) {
			if (text == null) {
				logger.warn("TextContent: missing required field 'text' during deserialization, using default ''");
				text = "";
			}
			return new TextContent(annotations, text, meta);
		}

		@Deprecated
		public TextContent(Annotations annotations, String text) {
			this(annotations, text, null);
		}

		@Deprecated
		public TextContent(String content) {
			this(null, content, null);
		}

		public static Builder builder(String text) {
			return new Builder(text);
		}

		public static class Builder {

			private Annotations annotations;

			private final String text;

			private Map<String, Object> meta;

			private Builder(String text) {
				Assert.notNull(text, "text must not be null");
				this.text = text;
			}

			public Builder annotations(Annotations annotations) {
				this.annotations = annotations;
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public TextContent build() {
				return new TextContent(annotations, text, meta);
			}

		}
	}

	/**
	 * An image provided to or from an LLM.
	 *
	 * @param annotations Optional annotations for the client
	 * @param data The base64-encoded image data
	 * @param mimeType The MIME type of the image. Different providers may support
	 * different image types
	 * @param meta See specification for notes on _meta usage
	 */
	@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,
		@JsonProperty("_meta") Map<String, Object> meta) implements Annotated, Content { // @formatter:on

		public ImageContent {
			Assert.notNull(data, "data must not be null");
			Assert.notNull(mimeType, "mimeType must not be null");
		}

		@JsonCreator
		static ImageContent fromJson(@JsonProperty("annotations") Annotations annotations,
				@JsonProperty("data") String data, @JsonProperty("mimeType") String mimeType,
				@JsonProperty("_meta") Map<String, Object> meta) {
			if (data == null || mimeType == null) {
				List<String> missing = new ArrayList<>();
				if (data == null) {
					missing.add("data -> ''");
					data = "";
				}
				if (mimeType == null) {
					missing.add("mimeType -> ''");
					mimeType = "";
				}
				logger.warn("ImageContent: missing required fields during deserialization: {}",
						String.join(", ", missing));
			}
			return new ImageContent(annotations, data, mimeType, meta);
		}

		@Deprecated
		public ImageContent(Annotations annotations, String data, String mimeType) {
			this(annotations, data, mimeType, null);
		}

		public static Builder builder(String data, String mimeType) {
			return new Builder(data, mimeType);
		}

		public static class Builder {

			private Annotations annotations;

			private final String data;

			private final String mimeType;

			private Map<String, Object> meta;

			private Builder(String data, String mimeType) {
				Assert.notNull(data, "data must not be null");
				Assert.notNull(mimeType, "mimeType must not be null");
				this.data = data;
				this.mimeType = mimeType;
			}

			public Builder annotations(Annotations annotations) {
				this.annotations = annotations;
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public ImageContent build() {
				return new ImageContent(annotations, data, mimeType, meta);
			}

		}
	}

	/**
	 * Audio provided to or from an LLM.
	 *
	 * @param annotations Optional annotations for the client
	 * @param data The base64-encoded audio data
	 * @param mimeType The MIME type of the audio. Different providers may support
	 * different audio types
	 * @param meta See specification for notes on _meta usage
	 */
	@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,
		@JsonProperty("_meta") Map<String, Object> meta) implements Annotated, Content { // @formatter:on

		public AudioContent {
			Assert.notNull(data, "data must not be null");
			Assert.notNull(mimeType, "mimeType must not be null");
		}

		@JsonCreator
		static AudioContent fromJson(@JsonProperty("annotations") Annotations annotations,
				@JsonProperty("data") String data, @JsonProperty("mimeType") String mimeType,
				@JsonProperty("_meta") Map<String, Object> meta) {
			if (data == null || mimeType == null) {
				List<String> missing = new ArrayList<>();
				if (data == null) {
					missing.add("data -> ''");
					data = "";
				}
				if (mimeType == null) {
					missing.add("mimeType -> ''");
					mimeType = "";
				}
				logger.warn("AudioContent: missing required fields during deserialization: {}",
						String.join(", ", missing));
			}
			return new AudioContent(annotations, data, mimeType, meta);
		}

		// backwards compatibility constructor
		@Deprecated
		public AudioContent(Annotations annotations, String data, String mimeType) {
			this(annotations, data, mimeType, null);
		}

		public static Builder builder(String data, String mimeType) {
			return new Builder(data, mimeType);
		}

		public static class Builder {

			private Annotations annotations;

			private final String data;

			private final String mimeType;

			private Map<String, Object> meta;

			private Builder(String data, String mimeType) {
				Assert.notNull(data, "data must not be null");
				Assert.notNull(mimeType, "mimeType must not be null");
				this.data = data;
				this.mimeType = mimeType;
			}

			public Builder annotations(Annotations annotations) {
				this.annotations = annotations;
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public AudioContent build() {
				return new AudioContent(annotations, data, mimeType, meta);
			}

		}
	}

	/**
	 * The contents of a resource, embedded into a prompt or tool call result.
	 *
	 * It is up to the client how best to render embedded resources for the benefit of the
	 * LLM and/or the user.
	 *
	 * @param annotations Optional annotations for the client
	 * @param resource The resource contents that are embedded
	 * @param meta See specification for notes on _meta usage
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record EmbeddedResource( // @formatter:off
		@JsonProperty("annotations") Annotations annotations,
		@JsonProperty("resource") ResourceContents resource,
		@JsonProperty("_meta") Map<String, Object> meta) implements Annotated, Content { // @formatter:on

		public EmbeddedResource {
			Assert.notNull(resource, "resource must not be null");
		}

		@JsonCreator
		static EmbeddedResource fromJson(@JsonProperty("annotations") Annotations annotations,
				@JsonProperty("resource") ResourceContents resource, @JsonProperty("_meta") Map<String, Object> meta) {
			if (resource == null) {
				logger.warn(
						"EmbeddedResource: missing required field 'resource' during deserialization, using empty text resource");
				resource = new TextResourceContents("", null, "", null);
			}
			return new EmbeddedResource(annotations, resource, meta);
		}

		// backwards compatibility constructor
		@Deprecated
		public EmbeddedResource(Annotations annotations, ResourceContents resource) {
			this(annotations, resource, null);
		}

		public static Builder builder(ResourceContents resource) {
			return new Builder(resource);
		}

		public static class Builder {

			private Annotations annotations;

			private final ResourceContents resource;

			private Map<String, Object> meta;

			private Builder(ResourceContents resource) {
				Assert.notNull(resource, "resource must not be null");
				this.resource = resource;
			}

			public Builder annotations(Annotations annotations) {
				this.annotations = annotations;
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public EmbeddedResource build() {
				return new EmbeddedResource(annotations, resource, meta);
			}

		}
	}

	/**
	 * A known resource that the server is capable of reading.
	 *
	 * @param uri the URI of the resource.
	 * @param name A human-readable name for this resource. This can be used by clients to
	 * populate UI elements.
	 * @param title A human-readable title for this resource.
	 * @param description A description of what this resource represents. This can be used
	 * by clients to improve the LLM's understanding of available resources. It can be
	 * thought of like a "hint" to the model.
	 * @param mimeType The MIME type of this resource, if known.
	 * @param size The size of the raw resource content, in bytes (i.e., before base64
	 * encoding or any tokenization), if known. This can be used by Hosts to display file
	 * 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 See specification for notes on _meta usage
	 */
	@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,
		@JsonProperty("_meta") Map<String, Object> meta) implements Content, ResourceContent { // @formatter:on

		public static Builder builder() {
			return new Builder();
		}

		public static class Builder {

			private String name;

			private String title;

			private String uri;

			private String description;

			private String mimeType;

			private Annotations annotations;

			private Long size;

			private Map<String, Object> meta;

			public Builder name(String name) {
				this.name = name;
				return this;
			}

			public Builder title(String title) {
				this.title = title;
				return this;
			}

			public Builder uri(String uri) {
				this.uri = uri;
				return this;
			}

			public Builder description(String description) {
				this.description = description;
				return this;
			}

			public Builder mimeType(String mimeType) {
				this.mimeType = mimeType;
				return this;
			}

			public Builder annotations(Annotations annotations) {
				this.annotations = annotations;
				return this;
			}

			public Builder size(Long size) {
				this.size = size;
				return this;
			}

			public Builder meta(Map<String, Object> 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, meta);
			}

		}
	}

	// ---------------------------
	// Roots
	// ---------------------------
	/**
	 * Represents a root directory or file that the server can operate on.
	 *
	 * @param uri The URI identifying the root. This *must* start with file:// for now.
	 * This restriction may be relaxed in future versions of the protocol to allow other
	 * URI schemes.
	 * @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 See specification for notes on _meta usage
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record Root( // @formatter:off
		@JsonProperty("uri") String uri,
		@JsonProperty("name") String name,
		@JsonProperty("_meta") Map<String, Object> meta) { // @formatter:on

		public Root {
			Assert.notNull(uri, "uri must not be null");
		}

		@JsonCreator
		static Root fromJson(@JsonProperty("uri") String uri, @JsonProperty("name") String name,
				@JsonProperty("_meta") Map<String, Object> meta) {
			if (uri == null) {
				logger.warn("Root: missing required field 'uri' during deserialization, using default ''");
				uri = "";
			}
			return new Root(uri, name, meta);
		}

		public Root(String uri, String name) {
			this(uri, name, null);
		}

		public static Builder builder(String uri) {
			return new Builder(uri);
		}

		public static class Builder {

			private final String uri;

			private String name;

			private Map<String, Object> meta;

			private Builder(String uri) {
				Assert.hasText(uri, "uri must not be empty");
				this.uri = uri;
			}

			public Builder name(String name) {
				this.name = name;
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public Root build() {
				return new Root(uri, name, meta);
			}

		}
	}

	/**
	 * The client's response to a roots/list request from the server. This result contains
	 * an array of Root objects, each representing a root directory or file that the
	 * server can operate on.
	 *
	 * @param roots An array of Root objects, each representing a root directory or file
	 * that the server can operate on.
	 * @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 See specification for notes on _meta usage
	 */
	@JsonInclude(JsonInclude.Include.NON_ABSENT)
	@JsonIgnoreProperties(ignoreUnknown = true)
	public record ListRootsResult( // @formatter:off
		@JsonProperty("roots") List<Root> roots,
		@JsonProperty("nextCursor") String nextCursor,
		@JsonProperty("_meta") Map<String, Object> meta) implements Result { // @formatter:on

		public ListRootsResult {
			Assert.notNull(roots, "roots must not be null");
		}

		@JsonCreator
		static ListRootsResult fromJson(@JsonProperty("roots") List<Root> roots,
				@JsonProperty("nextCursor") String nextCursor, @JsonProperty("_meta") Map<String, Object> meta) {
			if (roots == null) {
				logger.warn("ListRootsResult: missing required field 'roots' during deserialization, using default []");
				roots = List.of();
			}
			return new ListRootsResult(roots, nextCursor, meta);
		}

		@Deprecated
		public ListRootsResult(List<Root> roots) {
			this(roots, null, null);
		}

		@Deprecated
		public ListRootsResult(List<Root> roots, String nextCursor) {
			this(roots, nextCursor, null);
		}

		public static Builder builder(List<Root> roots) {
			return new Builder(roots);
		}

		public static class Builder {

			private final List<Root> roots;

			private String nextCursor;

			private Map<String, Object> meta;

			private Builder(List<Root> roots) {
				Assert.notNull(roots, "roots must not be null");
				this.roots = roots;
			}

			public Builder nextCursor(String nextCursor) {
				this.nextCursor = nextCursor;
				return this;
			}

			public Builder meta(Map<String, Object> meta) {
				this.meta = meta;
				return this;
			}

			public ListRootsResult build() {
				return new ListRootsResult(roots, nextCursor, meta);
			}

		}
	}

}
