From a1e0d41d5ac38025dde27d301d4eee1c8230e290 Mon Sep 17 00:00:00 2001 From: Christian Tzolov Date: Mon, 10 Mar 2025 11:01:52 +0100 Subject: [PATCH 1/2] feat(mcp): relax MCP Schema JSON deserialization constraints - Add @JsonIgnoreProperties(ignoreUnknown = true) annotation to all record classes in McpSchema to make JSON deserialization more robust by ignoring unknown properties. This improves compatibility with third-party implementations like Cursor that are not MCP Schema compliant. Signed-off-by: Christian Tzolov --- .../modelcontextprotocol/spec/McpSchema.java | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java b/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java index 3ce2068b..dbf35004 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java +++ b/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java @@ -176,6 +176,7 @@ public sealed interface JSONRPCMessage permits JSONRPCRequest, JSONRPCNotificati } @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) public record JSONRPCRequest( // @formatter:off @JsonProperty("jsonrpc") String jsonrpc, @JsonProperty("method") String method, @@ -184,6 +185,7 @@ public record JSONRPCRequest( // @formatter:off } // @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) public record JSONRPCNotification( // @formatter:off @JsonProperty("jsonrpc") String jsonrpc, @JsonProperty("method") String method, @@ -191,6 +193,7 @@ public record JSONRPCNotification( // @formatter:off } // @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) public record JSONRPCResponse( // @formatter:off @JsonProperty("jsonrpc") String jsonrpc, @JsonProperty("id") Object id, @@ -198,6 +201,7 @@ public record JSONRPCResponse( // @formatter:off @JsonProperty("error") JSONRPCError error) implements JSONRPCMessage { @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) public record JSONRPCError( @JsonProperty("code") int code, @JsonProperty("message") String message, @@ -209,6 +213,7 @@ public record JSONRPCError( // Initialization // --------------------------- @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) public record InitializeRequest( // @formatter:off @JsonProperty("protocolVersion") String protocolVersion, @JsonProperty("capabilities") ClientCapabilities capabilities, @@ -239,6 +244,7 @@ public record InitializeResult( // @formatter:off * */ @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) public record ClientCapabilities( // @formatter:off @JsonProperty("experimental") Map experimental, @JsonProperty("roots") RootCapabilities roots, @@ -253,7 +259,8 @@ public record ClientCapabilities( // @formatter:off * @param listChanged Whether the client would send notification about roots * has changed since the last time the server checked. */ - @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) public record RootCapabilities( @JsonProperty("listChanged") Boolean listChanged) { } @@ -303,6 +310,7 @@ public ClientCapabilities build() { }// @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) public record ServerCapabilities( // @formatter:off @JsonProperty("experimental") Map experimental, @JsonProperty("logging") LoggingCapabilities logging, @@ -375,6 +383,7 @@ public ServerCapabilities build() { } // @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) public record Implementation(// @formatter:off @JsonProperty("name") String name, @JsonProperty("version") String version) { @@ -413,6 +422,7 @@ public interface Annotated { * 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 audience, @JsonProperty("priority") Double priority) { @@ -458,6 +468,7 @@ public record Resource( // @formatter:off * @see RFC 6570 */ @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) public record ResourceTemplate( // @formatter:off @JsonProperty("uriTemplate") String uriTemplate, @JsonProperty("name") String name, @@ -481,6 +492,7 @@ public record ListResourceTemplatesResult( // @formatter:off } // @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) public record ReadResourceRequest( // @formatter:off @JsonProperty("uri") String uri){ } // @formatter:on @@ -499,11 +511,13 @@ public record ReadResourceResult( // @formatter:off * it is up to the server how to interpret it. */ @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) public record SubscribeRequest( // @formatter:off @JsonProperty("uri") String uri){ } // @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) public record UnsubscribeRequest( // @formatter:off @JsonProperty("uri") String uri){ } // @formatter:on @@ -574,6 +588,7 @@ public record BlobResourceContents( // @formatter:off * @param arguments A list of arguments to use for templating the prompt. */ @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) public record Prompt( // @formatter:off @JsonProperty("name") String name, @JsonProperty("description") String description, @@ -588,6 +603,7 @@ public record Prompt( // @formatter:off * @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("description") String description, @@ -604,6 +620,7 @@ public record PromptArgument( // @formatter:off * @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) { @@ -630,6 +647,7 @@ public record ListPromptsResult( // @formatter:off * @param arguments Arguments to use for templating the prompt. */ @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) public record GetPromptRequest(// @formatter:off @JsonProperty("name") String name, @JsonProperty("arguments") Map arguments) implements Request { @@ -688,6 +706,7 @@ record JsonSchema( // @formatter:off * arguments before sending them to the server. */ @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) public record Tool( // @formatter:off @JsonProperty("name") String name, @JsonProperty("description") String description, @@ -742,6 +761,7 @@ public record CallToolResult( // @formatter:off // Sampling Interfaces // --------------------------- @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) public record ModelPreferences(// @formatter:off @JsonProperty("hints") List hints, @JsonProperty("costPriority") Double costPriority, @@ -750,10 +770,12 @@ public record ModelPreferences(// @formatter:off } // @formatter:on @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) public record ModelHint(@JsonProperty("name") String name) { } @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) public record SamplingMessage(// @formatter:off @JsonProperty("role") Role role, @JsonProperty("content") Content content) { @@ -761,6 +783,7 @@ public record SamplingMessage(// @formatter:off // Sampling and Message Creation @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) public record CreateMessageRequest(// @formatter:off @JsonProperty("messages") List messages, @JsonProperty("modelPreferences") ModelPreferences modelPreferences, @@ -837,6 +860,7 @@ public CreateMessageResult build() { // Pagination Interfaces // --------------------------- @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) public record PaginatedRequest(@JsonProperty("cursor") String cursor) { } @@ -848,6 +872,7 @@ public record PaginatedResult(@JsonProperty("nextCursor") String nextCursor) { // --------------------------- // Progress and Logging // --------------------------- + @JsonIgnoreProperties(ignoreUnknown = true) public record ProgressNotification(// @formatter:off @JsonProperty("progressToken") String progressToken, @JsonProperty("progress") double progress, @@ -864,6 +889,7 @@ public record ProgressNotification(// @formatter:off * @param logger The logger that generated the message. * @param data JSON-serializable logging data. */ + @JsonIgnoreProperties(ignoreUnknown = true) public record LoggingMessageNotification(// @formatter:off @JsonProperty("level") LoggingLevel level, @JsonProperty("logger") String logger, @@ -980,6 +1006,7 @@ else if (this instanceof EmbeddedResource) { } @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) public record TextContent( // @formatter:off @JsonProperty("audience") List audience, @JsonProperty("priority") Double priority, @@ -991,6 +1018,7 @@ public TextContent(String content) { } @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) public record ImageContent( // @formatter:off @JsonProperty("audience") List audience, @JsonProperty("priority") Double priority, @@ -999,6 +1027,7 @@ public record ImageContent( // @formatter:off } @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) public record EmbeddedResource( // @formatter:off @JsonProperty("audience") List audience, @JsonProperty("priority") Double priority, @@ -1019,6 +1048,7 @@ public record EmbeddedResource( // @formatter:off * for referencing the root in other parts of the application. */ @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) public record Root( // @formatter:off @JsonProperty("uri") String uri, @JsonProperty("name") String name) { From ec103c02430a58e9858bf8439806ce8c4cb27edd Mon Sep 17 00:00:00 2001 From: Christian Tzolov Date: Mon, 10 Mar 2025 12:25:38 +0100 Subject: [PATCH 2/2] Make the JsonSchema record public Resolves #36 Signed-off-by: Christian Tzolov --- mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java b/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java index dbf35004..2f551196 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java +++ b/mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java @@ -685,7 +685,7 @@ public record ListToolsResult( // @formatter:off @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) - record JsonSchema( // @formatter:off + public record JsonSchema( // @formatter:off @JsonProperty("type") String type, @JsonProperty("properties") Map properties, @JsonProperty("required") List required,