Skip to content

Commit ab8b677

Browse files
authored
Merge branch 'main' into bug/protocol_versioning
2 parents 7c85d2e + 6e4ce1c commit ab8b677

File tree

4 files changed

+100
-24
lines changed

4 files changed

+100
-24
lines changed

docs/client.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,49 @@ Resources represent server-side data sources that clients can access using URI t
408408
.subscribe();
409409
```
410410

411+
### Resource Subscriptions
412+
413+
When the server advertises `resources.subscribe` support, clients can subscribe to individual resources and receive a callback whenever the server pushes a `notifications/resources/updated` notification for that URI. The SDK automatically re-reads the resource on notification and delivers the updated contents to the registered consumer.
414+
415+
Register a consumer on the client builder, then subscribe/unsubscribe at any time:
416+
417+
=== "Sync API"
418+
419+
```java
420+
McpSyncClient client = McpClient.sync(transport)
421+
.resourcesUpdateConsumer(contents -> {
422+
// called with the updated resource contents after each notification
423+
System.out.println("Resource updated: " + contents);
424+
})
425+
.build();
426+
427+
client.initialize();
428+
429+
// Subscribe to a specific resource URI
430+
client.subscribeResource(new McpSchema.SubscribeRequest("custom://resource"));
431+
432+
// ... later, stop receiving updates
433+
client.unsubscribeResource(new McpSchema.UnsubscribeRequest("custom://resource"));
434+
```
435+
436+
=== "Async API"
437+
438+
```java
439+
McpAsyncClient client = McpClient.async(transport)
440+
.resourcesUpdateConsumer(contents -> Mono.fromRunnable(() -> {
441+
System.out.println("Resource updated: " + contents);
442+
}))
443+
.build();
444+
445+
client.initialize()
446+
.then(client.subscribeResource(new McpSchema.SubscribeRequest("custom://resource")))
447+
.subscribe();
448+
449+
// ... later, stop receiving updates
450+
client.unsubscribeResource(new McpSchema.UnsubscribeRequest("custom://resource"))
451+
.subscribe();
452+
```
453+
411454
### Prompt System
412455

413456
The prompt system enables interaction with server-side prompt templates. These templates can be discovered and executed with custom parameters, allowing for dynamic text generation based on predefined patterns.

docs/server.md

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ The server supports both synchronous and asynchronous APIs, allowing for flexibl
3333
McpSyncServer syncServer = McpServer.sync(transportProvider)
3434
.serverInfo("my-server", "1.0.0")
3535
.capabilities(ServerCapabilities.builder()
36-
.resources(false, true) // Enable resource support with list changes
36+
.resources(false, true) // Resource support: subscribe=false, listChanged=true
3737
.tools(true) // Enable tool support with list changes
3838
.prompts(true) // Enable prompt support with list changes
3939
.completions() // Enable completions support
@@ -57,7 +57,7 @@ The server supports both synchronous and asynchronous APIs, allowing for flexibl
5757
McpAsyncServer asyncServer = McpServer.async(transportProvider)
5858
.serverInfo("my-server", "1.0.0")
5959
.capabilities(ServerCapabilities.builder()
60-
.resources(false, true) // Enable resource support with list changes
60+
.resources(false, true) // Resource support: subscribe=false, listChanged=true
6161
.tools(true) // Enable tool support with list changes
6262
.prompts(true) // Enable prompt support with list changes
6363
.completions() // Enable completions support
@@ -319,7 +319,7 @@ The server can be configured with various capabilities:
319319

320320
```java
321321
var capabilities = ServerCapabilities.builder()
322-
.resources(false, true) // Resource support (subscribe, listChanged)
322+
.resources(true, true) // Resource support: subscribe=true, listChanged=true
323323
.tools(true) // Tool support with list changes notifications
324324
.prompts(true) // Prompt support with list changes notifications
325325
.completions() // Enable completions support
@@ -438,6 +438,42 @@ Resources provide context to AI models by exposing data such as: File contents,
438438
);
439439
```
440440

441+
### Resource Subscriptions
442+
443+
When the `subscribe` capability is enabled, clients can subscribe to specific resources and receive targeted `notifications/resources/updated` notifications when those resources change. Only sessions that have explicitly subscribed to a given URI receive the notification — not every connected client.
444+
445+
Enable subscription support in the server capabilities:
446+
447+
```java
448+
McpSyncServer server = McpServer.sync(transportProvider)
449+
.serverInfo("my-server", "1.0.0")
450+
.capabilities(ServerCapabilities.builder()
451+
.resources(true, false) // subscribe=true, listChanged=false
452+
.build())
453+
.resources(myResourceSpec)
454+
.build();
455+
```
456+
457+
When a subscribed resource changes, notify only the interested sessions:
458+
459+
=== "Sync"
460+
461+
```java
462+
server.notifyResourcesUpdated(
463+
new McpSchema.ResourcesUpdatedNotification("custom://resource")
464+
);
465+
```
466+
467+
=== "Async"
468+
469+
```java
470+
server.notifyResourcesUpdated(
471+
new McpSchema.ResourcesUpdatedNotification("custom://resource")
472+
).subscribe();
473+
```
474+
475+
If no sessions are subscribed to the given URI the call completes immediately without sending any messages. Subscription state is automatically cleaned up when a client session closes.
476+
441477
### Resource Template Specification
442478

443479
Resource templates allow servers to expose parameterized resources using URI templates:

mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@
5353
import org.junit.jupiter.params.provider.MethodSource;
5454
import org.junit.jupiter.params.provider.ValueSource;
5555
import reactor.core.publisher.Mono;
56-
import reactor.test.StepVerifier;
5756

5857
import static io.modelcontextprotocol.util.ToolsUtils.EMPTY_JSON_SCHEMA;
5958
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
@@ -404,6 +403,8 @@ void testCreateElicitationSuccess(String clientType) {
404403
.addContent(new McpSchema.TextContent("CALL RESPONSE"))
405404
.build();
406405

406+
AtomicReference<McpSchema.ElicitResult> elicitResultRef = new AtomicReference<>();
407+
407408
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
408409
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build())
409410
.callHandler((exchange, request) -> {
@@ -414,13 +415,9 @@ void testCreateElicitationSuccess(String clientType) {
414415
Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string"))))
415416
.build();
416417

417-
StepVerifier.create(exchange.createElicitation(elicitationRequest)).consumeNextWith(result -> {
418-
assertThat(result).isNotNull();
419-
assertThat(result.action()).isEqualTo(McpSchema.ElicitResult.Action.ACCEPT);
420-
assertThat(result.content().get("message")).isEqualTo("Test message");
421-
}).verifyComplete();
422-
423-
return Mono.just(callResponse);
418+
return exchange.createElicitation(elicitationRequest)
419+
.doOnNext(elicitResultRef::set)
420+
.thenReturn(callResponse);
424421
})
425422
.build();
426423

@@ -438,6 +435,11 @@ void testCreateElicitationSuccess(String clientType) {
438435

439436
assertThat(response).isNotNull();
440437
assertThat(response).isEqualTo(callResponse);
438+
assertWith(elicitResultRef.get(), result -> {
439+
assertThat(result).isNotNull();
440+
assertThat(result.action()).isEqualTo(McpSchema.ElicitResult.Action.ACCEPT);
441+
assertThat(result.content().get("message")).isEqualTo("Test message");
442+
});
441443
}
442444
finally {
443445
mcpServer.closeGracefully().block();

mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -610,22 +610,17 @@ void testListAllResourceTemplatesReturnsImmutableList() {
610610
});
611611
}
612612

613-
// @Test
613+
@Test
614614
void testResourceSubscription() {
615615
withClient(createMcpTransport(), mcpAsyncClient -> {
616-
StepVerifier.create(mcpAsyncClient.listResources()).consumeNextWith(resources -> {
617-
if (!resources.resources().isEmpty()) {
618-
Resource firstResource = resources.resources().get(0);
619-
620-
// Test subscribe
621-
StepVerifier.create(mcpAsyncClient.subscribeResource(new SubscribeRequest(firstResource.uri())))
622-
.verifyComplete();
623-
624-
// Test unsubscribe
625-
StepVerifier.create(mcpAsyncClient.unsubscribeResource(new UnsubscribeRequest(firstResource.uri())))
626-
.verifyComplete();
616+
StepVerifier.create(mcpAsyncClient.listResources().flatMap(resources -> {
617+
if (resources.resources().isEmpty()) {
618+
return Mono.empty();
627619
}
628-
}).verifyComplete();
620+
Resource firstResource = resources.resources().get(0);
621+
return mcpAsyncClient.subscribeResource(new SubscribeRequest(firstResource.uri()))
622+
.then(mcpAsyncClient.unsubscribeResource(new UnsubscribeRequest(firstResource.uri())));
623+
})).verifyComplete();
629624
});
630625
}
631626

0 commit comments

Comments
 (0)