feat: enforce MCP request ID validation requirements

Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
This commit is contained in:
Christian Tzolov 2025-07-16 11:06:20 +02:00
commit 1e93776354
3 changed files with 111 additions and 0 deletions

View file

@ -236,6 +236,17 @@ public final class McpSchema {
@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.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");
}
}
/**

View file

@ -76,4 +76,17 @@ public final class Assert {
return (str != null && !str.isBlank());
}
/**
* Assert a boolean expression, throwing an {@code IllegalArgumentException} if the
* expression evaluates to {@code false}.
* @param expression a boolean expression
* @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if {@code expression} is {@code false}
*/
public static void isTrue(boolean expression, String message) {
if (!expression) {
throw new IllegalArgumentException(message);
}
}
}

View file

@ -0,0 +1,87 @@
/*
* Copyright 2024-2024 the original author or authors.
*/
package io.modelcontextprotocol.spec;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/**
* Tests for MCP-specific validation of JSONRPCRequest ID requirements.
*
* @author Christian Tzolov
*/
public class JSONRPCRequestMcpValidationTest {
@Test
public void testValidStringId() {
assertDoesNotThrow(() -> {
var request = new McpSchema.JSONRPCRequest("2.0", "test/method", "string-id", null);
assertEquals("string-id", request.id());
});
}
@Test
public void testValidIntegerId() {
assertDoesNotThrow(() -> {
var request = new McpSchema.JSONRPCRequest("2.0", "test/method", 123, null);
assertEquals(123, request.id());
});
}
@Test
public void testValidLongId() {
assertDoesNotThrow(() -> {
var request = new McpSchema.JSONRPCRequest("2.0", "test/method", 123L, null);
assertEquals(123L, request.id());
});
}
@Test
public void testNullIdThrowsException() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
new McpSchema.JSONRPCRequest("2.0", "test/method", null, null);
});
assertTrue(exception.getMessage().contains("MCP requests MUST include an ID"));
assertTrue(exception.getMessage().contains("null IDs are not allowed"));
}
@Test
public void testDoubleIdTypeThrowsException() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
new McpSchema.JSONRPCRequest("2.0", "test/method", 123.45, null);
});
assertTrue(exception.getMessage().contains("MCP requests MUST have an ID that is either a string or integer"));
}
@Test
public void testBooleanIdThrowsException() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
new McpSchema.JSONRPCRequest("2.0", "test/method", true, null);
});
assertTrue(exception.getMessage().contains("MCP requests MUST have an ID that is either a string or integer"));
}
@Test
public void testArrayIdThrowsException() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
new McpSchema.JSONRPCRequest("2.0", "test/method", new String[] { "array" }, null);
});
assertTrue(exception.getMessage().contains("MCP requests MUST have an ID that is either a string or integer"));
}
@Test
public void testObjectIdThrowsException() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
new McpSchema.JSONRPCRequest("2.0", "test/method", new Object(), null);
});
assertTrue(exception.getMessage().contains("MCP requests MUST have an ID that is either a string or integer"));
}
}