Skip to content

Commit e3b999a

Browse files
committed
feat: add ignorable JSON-RPC methods support for both client and server
- Add ignorableJsonRpcMethods support to McpClientSession and McpServerSession - Implement request and notification filtering for specified methods - Add default ignorable methods: notifications/cancelled, notifications/stderr - Extend both client and server builders with ignorableJsonRpcMethods() methods - Support both List<String> and varargs configurations - Add comprehensive test coverage for client and server-side functionality - Maintain full backward compatibility with deprecated constructors - Log ignored methods as DEBUG for debugging visibility This feature allows both MCP clients and servers to gracefully handle unknown or unwanted JSON-RPC methods without generating errors, improving compatibility with various implementations and reducing log noise. Resolves #416, #377 Related to #130 It provides a workaround until the `notification/cancelled` is supposed. Signed-off-by: Christian Tzolov <[email protected]>
1 parent 1e93776 commit e3b999a

File tree

13 files changed

+592
-23
lines changed

13 files changed

+592
-23
lines changed

mcp/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ public class McpAsyncClient {
159159
* @param features the MCP Client supported features.
160160
*/
161161
McpAsyncClient(McpClientTransport transport, Duration requestTimeout, Duration initializationTimeout,
162-
McpClientFeatures.Async features) {
162+
McpClientFeatures.Async features, List<String> ignorableJsonRpcMethods) {
163163

164164
Assert.notNull(transport, "Transport must not be null");
165165
Assert.notNull(requestTimeout, "Request timeout must not be null");
@@ -269,7 +269,7 @@ public class McpAsyncClient {
269269
this.initializer = new LifecycleInitializer(clientCapabilities, clientInfo,
270270
List.of(McpSchema.LATEST_PROTOCOL_VERSION), initializationTimeout,
271271
ctx -> new McpClientSession(requestTimeout, transport, requestHandlers, notificationHandlers,
272-
con -> con.contextWrite(ctx)));
272+
con -> con.contextWrite(ctx), ignorableJsonRpcMethods));
273273
this.transport.setExceptionHandler(this.initializer::handleException);
274274
}
275275

mcp/src/main/java/io/modelcontextprotocol/client/McpClient.java

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import io.modelcontextprotocol.spec.McpSchema.Implementation;
2424
import io.modelcontextprotocol.spec.McpSchema.Root;
2525
import io.modelcontextprotocol.util.Assert;
26+
import io.modelcontextprotocol.util.Utils;
2627
import reactor.core.publisher.Mono;
2728

2829
/**
@@ -161,6 +162,12 @@ class SyncSpec {
161162

162163
private Duration initializationTimeout = Duration.ofSeconds(20);
163164

165+
/**
166+
* List of JSON-RPC methods that can be ignored. These methods will not be
167+
* processed and will not generate errors if received
168+
*/
169+
private final List<String> ignorableJsonRpcMethods = new ArrayList<>(Utils.DEFAULT_IGNORABLE_JSON_RPC_METHODS);
170+
164171
private ClientCapabilities capabilities;
165172

166173
private Implementation clientInfo = new Implementation("Java SDK MCP Client", "1.0.0");
@@ -215,6 +222,36 @@ public SyncSpec initializationTimeout(Duration initializationTimeout) {
215222
return this;
216223
}
217224

225+
/**
226+
* Sets the list of JSON-RPC methods that can be ignored by the client. These
227+
* methods will not be processed and will not generate errors if received.
228+
* @param ignorableJsonRpcMethods A list of JSON-RPC method names to ignore. Must
229+
* not be null.
230+
* @return This builder instance for method chaining
231+
* @throws IllegalArgumentException if ignorableJsonRpcMethods is null
232+
*/
233+
public SyncSpec ignorableJsonRpcMethods(List<String> ignorableJsonRpcMethods) {
234+
Assert.notNull(ignorableJsonRpcMethods, "Ignorable JSON-RPC methods must not be null");
235+
this.ignorableJsonRpcMethods.addAll(ignorableJsonRpcMethods);
236+
return this;
237+
}
238+
239+
/**
240+
* Sets the list of JSON-RPC methods that can be ignored by the client. These
241+
* methods will not be processed and will not generate errors if received.
242+
* @param ignorableJsonRpcMethods An array of JSON-RPC method names to ignore.
243+
* Must not be null.
244+
* @return This builder instance for method chaining
245+
* @throws IllegalArgumentException if ignorableJsonRpcMethods is null
246+
*/
247+
public SyncSpec ignorableJsonRpcMethods(String... ignorableJsonRpcMethods) {
248+
Assert.notNull(ignorableJsonRpcMethods, "Ignorable JSON-RPC methods must not be null");
249+
for (String method : ignorableJsonRpcMethods) {
250+
this.ignorableJsonRpcMethods.add(method);
251+
}
252+
return this;
253+
}
254+
218255
/**
219256
* Sets the client capabilities that will be advertised to the server during
220257
* connection initialization. Capabilities define what features the client
@@ -422,8 +459,8 @@ public McpSyncClient build() {
422459

423460
McpClientFeatures.Async asyncFeatures = McpClientFeatures.Async.fromSync(syncFeatures);
424461

425-
return new McpSyncClient(
426-
new McpAsyncClient(transport, this.requestTimeout, this.initializationTimeout, asyncFeatures));
462+
return new McpSyncClient(new McpAsyncClient(transport, this.requestTimeout, this.initializationTimeout,
463+
asyncFeatures, this.ignorableJsonRpcMethods));
427464
}
428465

429466
}
@@ -452,6 +489,12 @@ class AsyncSpec {
452489

453490
private Duration initializationTimeout = Duration.ofSeconds(20);
454491

492+
/**
493+
* List of JSON-RPC methods that can be ignored. These methods will not be
494+
* processed and will not generate errors if received
495+
*/
496+
private final List<String> ignorableJsonRpcMethods = new ArrayList<>(Utils.DEFAULT_IGNORABLE_JSON_RPC_METHODS);
497+
455498
private ClientCapabilities capabilities;
456499

457500
private Implementation clientInfo = new Implementation("Spring AI MCP Client", "0.3.1");
@@ -506,6 +549,36 @@ public AsyncSpec initializationTimeout(Duration initializationTimeout) {
506549
return this;
507550
}
508551

552+
/**
553+
* Sets the list of JSON-RPC methods that can be ignored by the client. These
554+
* methods will not be processed and will not generate errors if received.
555+
* @param ignorableJsonRpcMethods A list of JSON-RPC method names to ignore. Must
556+
* not be null.
557+
* @return This builder instance for method chaining
558+
* @throws IllegalArgumentException if ignorableJsonRpcMethods is null
559+
*/
560+
public AsyncSpec ignorableJsonRpcMethods(List<String> ignorableJsonRpcMethods) {
561+
Assert.notNull(ignorableJsonRpcMethods, "Ignorable JSON-RPC methods must not be null");
562+
this.ignorableJsonRpcMethods.addAll(ignorableJsonRpcMethods);
563+
return this;
564+
}
565+
566+
/**
567+
* Sets the list of JSON-RPC methods that can be ignored by the client. These
568+
* methods will not be processed and will not generate errors if received.
569+
* @param ignorableJsonRpcMethods An array of JSON-RPC method names to ignore.
570+
* Must not be null.
571+
* @return This builder instance for method chaining
572+
* @throws IllegalArgumentException if ignorableJsonRpcMethods is null
573+
*/
574+
public AsyncSpec ignorableJsonRpcMethods(String... ignorableJsonRpcMethods) {
575+
Assert.notNull(ignorableJsonRpcMethods, "Ignorable JSON-RPC methods must not be null");
576+
for (String method : ignorableJsonRpcMethods) {
577+
this.ignorableJsonRpcMethods.add(method);
578+
}
579+
return this;
580+
}
581+
509582
/**
510583
* Sets the client capabilities that will be advertised to the server during
511584
* connection initialization. Capabilities define what features the client
@@ -730,7 +803,8 @@ public McpAsyncClient build() {
730803
new McpClientFeatures.Async(this.clientInfo, this.capabilities, this.roots,
731804
this.toolsChangeConsumers, this.resourcesChangeConsumers, this.resourcesUpdateConsumers,
732805
this.promptsChangeConsumers, this.loggingConsumers, this.progressConsumers,
733-
this.samplingHandler, this.elicitationHandler));
806+
this.samplingHandler, this.elicitationHandler),
807+
this.ignorableJsonRpcMethods);
734808
}
735809

736810
}

mcp/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ public class McpAsyncServer {
116116

117117
private McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory();
118118

119+
private final List<String> ignorableJsonRpcMethods;
120+
119121
/**
120122
* Create a new McpAsyncServer with the given transport provider and capabilities.
121123
* @param mcpTransportProvider The transport layer implementation for MCP
@@ -126,6 +128,22 @@ public class McpAsyncServer {
126128
McpAsyncServer(McpServerTransportProvider mcpTransportProvider, ObjectMapper objectMapper,
127129
McpServerFeatures.Async features, Duration requestTimeout,
128130
McpUriTemplateManagerFactory uriTemplateManagerFactory, JsonSchemaValidator jsonSchemaValidator) {
131+
this(mcpTransportProvider, objectMapper, features, requestTimeout, uriTemplateManagerFactory,
132+
jsonSchemaValidator, Utils.DEFAULT_IGNORABLE_JSON_RPC_METHODS);
133+
}
134+
135+
/**
136+
* Create a new McpAsyncServer with the given transport provider and capabilities.
137+
* @param mcpTransportProvider The transport layer implementation for MCP
138+
* communication.
139+
* @param features The MCP server supported features.
140+
* @param objectMapper The ObjectMapper to use for JSON serialization/deserialization
141+
* @param ignorableJsonRpcMethods List of JSON-RPC method names that should be ignored
142+
*/
143+
McpAsyncServer(McpServerTransportProvider mcpTransportProvider, ObjectMapper objectMapper,
144+
McpServerFeatures.Async features, Duration requestTimeout,
145+
McpUriTemplateManagerFactory uriTemplateManagerFactory, JsonSchemaValidator jsonSchemaValidator,
146+
List<String> ignorableJsonRpcMethods) {
129147
this.mcpTransportProvider = mcpTransportProvider;
130148
this.objectMapper = objectMapper;
131149
this.serverInfo = features.serverInfo();
@@ -138,6 +156,8 @@ public class McpAsyncServer {
138156
this.completions.putAll(features.completions());
139157
this.uriTemplateManagerFactory = uriTemplateManagerFactory;
140158
this.jsonSchemaValidator = jsonSchemaValidator;
159+
this.ignorableJsonRpcMethods = ignorableJsonRpcMethods != null ? List.copyOf(ignorableJsonRpcMethods)
160+
: List.of();
141161

142162
Map<String, McpServerSession.RequestHandler<?>> requestHandlers = new HashMap<>();
143163

@@ -190,9 +210,9 @@ public class McpAsyncServer {
190210
notificationHandlers.put(McpSchema.METHOD_NOTIFICATION_ROOTS_LIST_CHANGED,
191211
asyncRootsListChangedNotificationHandler(rootsChangeConsumers));
192212

193-
mcpTransportProvider.setSessionFactory(
194-
transport -> new McpServerSession(UUID.randomUUID().toString(), requestTimeout, transport,
195-
this::asyncInitializeRequestHandler, Mono::empty, requestHandlers, notificationHandlers));
213+
mcpTransportProvider.setSessionFactory(transport -> new McpServerSession(UUID.randomUUID().toString(),
214+
requestTimeout, transport, this::asyncInitializeRequestHandler, Mono::empty, requestHandlers,
215+
notificationHandlers, this.ignorableJsonRpcMethods));
196216
}
197217

198218
// ---------------------------------------

mcp/src/main/java/io/modelcontextprotocol/server/McpServer.java

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import io.modelcontextprotocol.util.Assert;
2525
import io.modelcontextprotocol.util.DeafaultMcpUriTemplateManagerFactory;
2626
import io.modelcontextprotocol.util.McpUriTemplateManagerFactory;
27+
import io.modelcontextprotocol.util.Utils;
2728
import reactor.core.publisher.Mono;
2829

2930
/**
@@ -212,6 +213,8 @@ class AsyncSpecification {
212213

213214
private Duration requestTimeout = Duration.ofSeconds(10); // Default timeout
214215

216+
private final List<String> ignorableJsonRpcMethods = new ArrayList<>(Utils.DEFAULT_IGNORABLE_JSON_RPC_METHODS);
217+
215218
private AsyncSpecification(McpServerTransportProvider transportProvider) {
216219
Assert.notNull(transportProvider, "Transport provider must not be null");
217220
this.transportProvider = transportProvider;
@@ -689,6 +692,34 @@ public AsyncSpecification jsonSchemaValidator(JsonSchemaValidator jsonSchemaVali
689692
return this;
690693
}
691694

695+
/**
696+
* Adds JSON-RPC method names that should be ignored by the server. Ignored
697+
* methods will not generate error responses when received from clients, allowing
698+
* for graceful handling of unknown or unwanted methods.
699+
* @param ignorableJsonRpcMethods List of method names to ignore. Must not be
700+
* null.
701+
* @return This builder instance for method chaining
702+
* @throws IllegalArgumentException if ignorableJsonRpcMethods is null
703+
*/
704+
public AsyncSpecification ignorableJsonRpcMethods(List<String> ignorableJsonRpcMethods) {
705+
Assert.notNull(ignorableJsonRpcMethods, "Ignorable JSON-RPC methods must not be null");
706+
this.ignorableJsonRpcMethods.addAll(ignorableJsonRpcMethods);
707+
return this;
708+
}
709+
710+
/**
711+
* Adds JSON-RPC method names that should be ignored by the server using varargs.
712+
* This is a convenience method for {@link #ignorableJsonRpcMethods(List)}.
713+
* @param ignorableJsonRpcMethods Method names to ignore. Must not be null.
714+
* @return This builder instance for method chaining
715+
* @throws IllegalArgumentException if ignorableJsonRpcMethods is null
716+
*/
717+
public AsyncSpecification ignorableJsonRpcMethods(String... ignorableJsonRpcMethods) {
718+
Assert.notNull(ignorableJsonRpcMethods, "Ignorable JSON-RPC methods must not be null");
719+
this.ignorableJsonRpcMethods.addAll(List.of(ignorableJsonRpcMethods));
720+
return this;
721+
}
722+
692723
/**
693724
* Builds an asynchronous MCP server that provides non-blocking operations.
694725
* @return A new instance of {@link McpAsyncServer} configured with this builder's
@@ -701,8 +732,9 @@ public McpAsyncServer build() {
701732
var mapper = this.objectMapper != null ? this.objectMapper : new ObjectMapper();
702733
var jsonSchemaValidator = this.jsonSchemaValidator != null ? this.jsonSchemaValidator
703734
: new DefaultJsonSchemaValidator(mapper);
735+
704736
return new McpAsyncServer(this.transportProvider, mapper, features, this.requestTimeout,
705-
this.uriTemplateManagerFactory, jsonSchemaValidator);
737+
this.uriTemplateManagerFactory, jsonSchemaValidator, this.ignorableJsonRpcMethods);
706738
}
707739

708740
}
@@ -766,6 +798,8 @@ class SyncSpecification {
766798

767799
private boolean immediateExecution = false;
768800

801+
private final List<String> ignorableJsonRpcMethods = new ArrayList<>(Utils.DEFAULT_IGNORABLE_JSON_RPC_METHODS);
802+
769803
private SyncSpecification(McpServerTransportProvider transportProvider) {
770804
Assert.notNull(transportProvider, "Transport provider must not be null");
771805
this.transportProvider = transportProvider;
@@ -1251,6 +1285,34 @@ public SyncSpecification immediateExecution(boolean immediateExecution) {
12511285
return this;
12521286
}
12531287

1288+
/**
1289+
* Adds JSON-RPC method names that should be ignored by the server. Ignored
1290+
* methods will not generate error responses when received from clients, allowing
1291+
* for graceful handling of unknown or unwanted methods.
1292+
* @param ignorableJsonRpcMethods List of method names to ignore. Must not be
1293+
* null.
1294+
* @return This builder instance for method chaining
1295+
* @throws IllegalArgumentException if ignorableJsonRpcMethods is null
1296+
*/
1297+
public SyncSpecification ignorableJsonRpcMethods(List<String> ignorableJsonRpcMethods) {
1298+
Assert.notNull(ignorableJsonRpcMethods, "Ignorable JSON-RPC methods must not be null");
1299+
this.ignorableJsonRpcMethods.addAll(ignorableJsonRpcMethods);
1300+
return this;
1301+
}
1302+
1303+
/**
1304+
* Adds JSON-RPC method names that should be ignored by the server using varargs.
1305+
* This is a convenience method for {@link #ignorableJsonRpcMethods(List)}.
1306+
* @param ignorableJsonRpcMethods Method names to ignore. Must not be null.
1307+
* @return This builder instance for method chaining
1308+
* @throws IllegalArgumentException if ignorableJsonRpcMethods is null
1309+
*/
1310+
public SyncSpecification ignorableJsonRpcMethods(String... ignorableJsonRpcMethods) {
1311+
Assert.notNull(ignorableJsonRpcMethods, "Ignorable JSON-RPC methods must not be null");
1312+
this.ignorableJsonRpcMethods.addAll(List.of(ignorableJsonRpcMethods));
1313+
return this;
1314+
}
1315+
12541316
/**
12551317
* Builds a synchronous MCP server that provides blocking operations.
12561318
* @return A new instance of {@link McpSyncServer} configured with this builder's
@@ -1267,7 +1329,7 @@ public McpSyncServer build() {
12671329
: new DefaultJsonSchemaValidator(mapper);
12681330

12691331
var asyncServer = new McpAsyncServer(this.transportProvider, mapper, asyncFeatures, this.requestTimeout,
1270-
this.uriTemplateManagerFactory, jsonSchemaValidator);
1332+
this.uriTemplateManagerFactory, jsonSchemaValidator, this.ignorableJsonRpcMethods);
12711333

12721334
return new McpSyncServer(asyncServer, this.immediateExecution);
12731335
}

0 commit comments

Comments
 (0)