Skip to content

Commit 78b62c1

Browse files
committed
Reject listRoots when client lacks roots capability
McpAsyncServerExchange#createMessage and #createElicitation both fail fast when the client is not initialized or has not declared the matching capability. listRoots sent the request regardless. Add the same two guards to listRoots(String cursor). The no-arg listRoots() delegates to it, so both overloads are covered by the single check.
1 parent c7fef64 commit 78b62c1

2 files changed

Lines changed: 41 additions & 0 deletions

File tree

‎mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServerExchange.java‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,13 @@ public Mono<McpSchema.ListRootsResult> listRoots() {
225225
* @return A Mono that emits the list of roots result containing
226226
*/
227227
public Mono<McpSchema.ListRootsResult> listRoots(String cursor) {
228+
if (this.clientCapabilities == null) {
229+
return Mono
230+
.error(new IllegalStateException("Client must be initialized. Call the initialize method first!"));
231+
}
232+
if (this.clientCapabilities.roots() == null) {
233+
return Mono.error(new IllegalStateException("Client must be configured with roots capabilities"));
234+
}
228235
return this.session.sendRequest(McpSchema.METHOD_ROOTS_LIST, new McpSchema.PaginatedRequest(cursor),
229236
LIST_ROOTS_RESULT_TYPE_REF);
230237
}

‎mcp-core/src/test/java/io/modelcontextprotocol/server/McpAsyncServerExchangeTests.java‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,40 @@ void testListRootsUnmodifiabilityAfterAccumulation() {
207207
}).verifyComplete();
208208
}
209209

210+
@Test
211+
void testListRootsWithNullCapabilities() {
212+
// Given - Create exchange with null capabilities
213+
McpAsyncServerExchange exchangeWithNullCapabilities = new McpAsyncServerExchange("testSessionId", mockSession,
214+
null, clientInfo, McpTransportContext.EMPTY);
215+
216+
StepVerifier.create(exchangeWithNullCapabilities.listRoots()).verifyErrorSatisfies(error -> {
217+
assertThat(error).isInstanceOf(IllegalStateException.class)
218+
.hasMessage("Client must be initialized. Call the initialize method first!");
219+
});
220+
221+
// Verify that sendRequest was never called due to null capabilities
222+
verify(mockSession, never()).sendRequest(eq(McpSchema.METHOD_ROOTS_LIST), any(), any(TypeRef.class));
223+
}
224+
225+
@Test
226+
void testListRootsWithoutRootsCapabilities() {
227+
// Given - Create exchange without roots capabilities
228+
McpSchema.ClientCapabilities capabilitiesWithoutRoots = McpSchema.ClientCapabilities.builder()
229+
.sampling()
230+
.build();
231+
232+
McpAsyncServerExchange exchangeWithoutRoots = new McpAsyncServerExchange("testSessionId", mockSession,
233+
capabilitiesWithoutRoots, clientInfo, McpTransportContext.EMPTY);
234+
235+
StepVerifier.create(exchangeWithoutRoots.listRoots()).verifyErrorSatisfies(error -> {
236+
assertThat(error).isInstanceOf(IllegalStateException.class)
237+
.hasMessage("Client must be configured with roots capabilities");
238+
});
239+
240+
// Verify that sendRequest was never called due to missing roots capabilities
241+
verify(mockSession, never()).sendRequest(eq(McpSchema.METHOD_ROOTS_LIST), any(), any(TypeRef.class));
242+
}
243+
210244
@Test
211245
void testGetClientCapabilities() {
212246
assertThat(exchange.getClientCapabilities()).isEqualTo(clientCapabilities);

0 commit comments

Comments
 (0)