Skip to content

Commit 1e93776

Browse files
committed
feat: enforce MCP request ID validation requirements
Signed-off-by: Christian Tzolov <[email protected]>
1 parent 53f7b77 commit 1e93776

File tree

3 files changed

+111
-0
lines changed

3 files changed

+111
-0
lines changed

mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,17 @@ public record JSONRPCRequest( // @formatter:off
236236
@JsonProperty("method") String method,
237237
@JsonProperty("id") Object id,
238238
@JsonProperty("params") Object params) implements JSONRPCMessage { // @formatter:on
239+
240+
/**
241+
* Constructor that validates MCP-specific ID requirements. Unlike base JSON-RPC,
242+
* MCP requires that: (1) Requests MUST include a string or integer ID; (2) The ID
243+
* MUST NOT be null
244+
*/
245+
public JSONRPCRequest {
246+
Assert.notNull(id, "MCP requests MUST include an ID - null IDs are not allowed");
247+
Assert.isTrue(id instanceof String || id instanceof Integer || id instanceof Long,
248+
"MCP requests MUST have an ID that is either a string or integer");
249+
}
239250
}
240251

241252
/**

mcp/src/main/java/io/modelcontextprotocol/util/Assert.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,17 @@ public static boolean hasText(@Nullable String str) {
7676
return (str != null && !str.isBlank());
7777
}
7878

79+
/**
80+
* Assert a boolean expression, throwing an {@code IllegalArgumentException} if the
81+
* expression evaluates to {@code false}.
82+
* @param expression a boolean expression
83+
* @param message the exception message to use if the assertion fails
84+
* @throws IllegalArgumentException if {@code expression} is {@code false}
85+
*/
86+
public static void isTrue(boolean expression, String message) {
87+
if (!expression) {
88+
throw new IllegalArgumentException(message);
89+
}
90+
}
91+
7992
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2024-2024 the original author or authors.
3+
*/
4+
5+
package io.modelcontextprotocol.spec;
6+
7+
import org.junit.jupiter.api.Test;
8+
import static org.junit.jupiter.api.Assertions.*;
9+
10+
/**
11+
* Tests for MCP-specific validation of JSONRPCRequest ID requirements.
12+
*
13+
* @author Christian Tzolov
14+
*/
15+
public class JSONRPCRequestMcpValidationTest {
16+
17+
@Test
18+
public void testValidStringId() {
19+
assertDoesNotThrow(() -> {
20+
var request = new McpSchema.JSONRPCRequest("2.0", "test/method", "string-id", null);
21+
assertEquals("string-id", request.id());
22+
});
23+
}
24+
25+
@Test
26+
public void testValidIntegerId() {
27+
assertDoesNotThrow(() -> {
28+
var request = new McpSchema.JSONRPCRequest("2.0", "test/method", 123, null);
29+
assertEquals(123, request.id());
30+
});
31+
}
32+
33+
@Test
34+
public void testValidLongId() {
35+
assertDoesNotThrow(() -> {
36+
var request = new McpSchema.JSONRPCRequest("2.0", "test/method", 123L, null);
37+
assertEquals(123L, request.id());
38+
});
39+
}
40+
41+
@Test
42+
public void testNullIdThrowsException() {
43+
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
44+
new McpSchema.JSONRPCRequest("2.0", "test/method", null, null);
45+
});
46+
47+
assertTrue(exception.getMessage().contains("MCP requests MUST include an ID"));
48+
assertTrue(exception.getMessage().contains("null IDs are not allowed"));
49+
}
50+
51+
@Test
52+
public void testDoubleIdTypeThrowsException() {
53+
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
54+
new McpSchema.JSONRPCRequest("2.0", "test/method", 123.45, null);
55+
});
56+
57+
assertTrue(exception.getMessage().contains("MCP requests MUST have an ID that is either a string or integer"));
58+
}
59+
60+
@Test
61+
public void testBooleanIdThrowsException() {
62+
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
63+
new McpSchema.JSONRPCRequest("2.0", "test/method", true, null);
64+
});
65+
66+
assertTrue(exception.getMessage().contains("MCP requests MUST have an ID that is either a string or integer"));
67+
}
68+
69+
@Test
70+
public void testArrayIdThrowsException() {
71+
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
72+
new McpSchema.JSONRPCRequest("2.0", "test/method", new String[] { "array" }, null);
73+
});
74+
75+
assertTrue(exception.getMessage().contains("MCP requests MUST have an ID that is either a string or integer"));
76+
}
77+
78+
@Test
79+
public void testObjectIdThrowsException() {
80+
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
81+
new McpSchema.JSONRPCRequest("2.0", "test/method", new Object(), null);
82+
});
83+
84+
assertTrue(exception.getMessage().contains("MCP requests MUST have an ID that is either a string or integer"));
85+
}
86+
87+
}

0 commit comments

Comments
 (0)