Skip to content

Commit d096916

Browse files
committed
Review changes
1 parent 70d150c commit d096916

19 files changed

+280
-183
lines changed

docs/mcp-declarative/README.md

Lines changed: 2 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -496,88 +496,18 @@ List<McpToolContent> cancellationTool(McpCancellation cancellation) {
496496

497497
### Sampling
498498

499-
The MCP Sampling feature provides a standardized mechanism that allows servers to request LLM sampling operations from language
500-
models through connected clients. It enables servers to seamlessly integrate AI capabilities into their workflows without
501-
requiring API keys. Like other MCP features, sampling can be accessed via the MCP request features.
502-
Sampling support is optional for clients, and servers can verify its availability using the `enabled` method:
503-
504-
```java
505-
var sampling = request.features().sampling();
506-
if (!sampling.enabled()) {
507-
}
508-
```
509-
510-
If the client supports sampling, you can send a sampling request using the request method. A builder is provided to configure
511-
and customize the sampling request as needed:
512-
513-
```java
514-
McpSamplingRequest request = McpSamplingRequest.builder()
515-
.maxTokens(1)
516-
.temperature(0.1)
517-
.costPriority(0.1)
518-
.speedPriority(0.1)
519-
.hints(List.of("hint1"))
520-
.metadata(JsonValue.TRUE)
521-
.intelligencePriority(0.1)
522-
.systemPrompt("system prompt")
523-
.timeout(Duration.ofSeconds(10))
524-
.stopSequences(List.of("stop1"))
525-
.includeContext(McpIncludeContext.NONE)
526-
.addMessage(McpSamplingMessages.textContent("text", McpRole.USER))
527-
.build();
528-
```
529-
530-
Once your request is built, send it using the sampling feature. The request method may throw an `McpSamplingException` if an
531-
error occurs during processing. On success, it returns an McpSamplingResponse containing the response message, the model used,
532-
and optionally a stop reason.
533-
534-
```java
535-
try {
536-
McpSamplingResponse response = sampling.request(req -> req.addMessage(message));
537-
} catch(McpSamplingException exception) {
538-
// Manage error
539-
}
540-
```
541-
542-
The messages you send are prompts to the language model, and they follow the same structure as MCP prompts. You can use the
543-
`McpSamplingMessages` utility class to create different types of messages for the client model:
544-
545-
```java
546-
var text = McpSamplingMessages.textContent("Explain Helidon MCP in one paragraph.", McpRole.USER);
547-
var image = McpSamplingMessages.imageContent(pngBytes, MediaTypes.create("image/png"), McpRole.USER);
548-
var audio = McpSamplingMessages.audioContent(wavBytes, MediaTypes.create("audio/wav"), McpRole.USER);
549-
```
499+
See the full [sampling documentation details](../mcp/README.md#sampling)
550500

551501
#### Example
552502

553-
Below is an example of a tool that uses the Sampling feature. If the connected client does not support sampling, the tool
554-
throws a `McpToolErrorException`.
503+
Below is an example of a tool that uses the Sampling feature. `McpSampling` object can be used as method parameter.
555504

556505
```java
557506
@Mcp.Tool("Uses MCP Sampling to ask the connected client model.")
558507
List<McpToolContent> samplingTool(McpSampling sampling) {
559-
if (!sampling.enabled()) {
560-
throw new McpToolErrorException("This tool requires sampling feature");
561-
}
562-
563-
try {
564-
McpSamplingResponse response = sampling.request(req -> req
565-
.timeout(Duration.ofSeconds(10))
566-
.systemPrompt("You are a concise, helpful assistant.")
567-
.addMessage(McpSamplingMessages.textContent("Write a 3-line summary of Helidon MCP Sampling.", McpRole.USER)));
568-
569-
McpSamplingMessage message = response.message();
570-
if (message instanceof McpSamplingTextContent text) {
571-
return List.of(McpToolContents.textContent(text.text()));
572-
}
573-
throw new McpToolErrorException("Received non-text sampling content.");
574-
} catch (McpSamplingException e) {
575-
throw new McpToolErrorException(e.getMessage());
576-
}
577508
}
578509
```
579510

580-
581511
## References
582512

583513
- [MCP Specification](https://modelcontextprotocol.io/introduction)

docs/mcp/README.md

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -719,8 +719,6 @@ Below is an example of a tool that uses the Sampling feature. If the connected c
719719
throws a `McpToolErrorException`.
720720

721721
```java
722-
import java.time.Duration;
723-
724722
class SamplingTool implements McpTool {
725723
@Override
726724
public String name() {
@@ -750,12 +748,7 @@ class SamplingTool implements McpTool {
750748
.timeout(Duration.ofSeconds(10))
751749
.systemPrompt("You are a concise, helpful assistant.")
752750
.addMessage(McpSamplingMessages.textContent("Write a 3-line summary of Helidon MCP Sampling.", McpRole.USER)));
753-
754-
McpSamplingMessage message = response.message();
755-
if (message instanceof McpSamplingTextContent text) {
756-
return List.of(McpToolContents.textContent(text.text()));
757-
}
758-
throw new McpToolErrorException("Received non-text sampling content.");
751+
return List.of(McpToolContents.textContent(response.asTextMessage()));
759752
} catch (McpSamplingException e) {
760753
throw new McpToolErrorException(e.getMessage());
761754
}

server/src/main/java/io/helidon/extensions/mcp/server/McpContent.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
public sealed interface McpContent permits McpTextContent,
2323
McpMediaContent,
2424
McpResourceContent,
25-
McpSamplingMessage,
2625
McpEmbeddedResource {
2726
/**
2827
* Content type.

server/src/main/java/io/helidon/extensions/mcp/server/McpJsonRpc.java

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -378,13 +378,13 @@ static JsonObjectBuilder toJson(McpContent content) {
378378
}
379379

380380
static JsonObjectBuilder toJson(McpSamplingMessage message) {
381-
if (message instanceof McpSamplingTextContentImpl text) {
381+
if (message instanceof McpSamplingTextMessageImpl text) {
382382
return toJson(text);
383383
}
384-
if (message instanceof McpSamplingImageContentImpl image) {
384+
if (message instanceof McpSamplingImageMessageImpl image) {
385385
return toJson(image);
386386
}
387-
if (message instanceof McpSamplingAudioContentImpl resource) {
387+
if (message instanceof McpSamplingAudioMessageImpl resource) {
388388
return toJson(resource);
389389
}
390390
throw new IllegalArgumentException("Unsupported content type: " + message.getClass().getName());
@@ -427,7 +427,7 @@ static JsonObjectBuilder toJson(McpPromptAudioContent audio) {
427427
.add("content", toJson(audio.content()));
428428
}
429429

430-
static JsonObjectBuilder toJson(McpSamplingImageContent image) {
430+
static JsonObjectBuilder toJson(McpSamplingImageMessage image) {
431431
return JSON_BUILDER_FACTORY.createObjectBuilder()
432432
.add("role", image.role().text())
433433
.add("content", JSON_BUILDER_FACTORY.createObjectBuilder()
@@ -436,15 +436,15 @@ static JsonObjectBuilder toJson(McpSamplingImageContent image) {
436436
.add("mimeType", image.mediaType().text()));
437437
}
438438

439-
static JsonObjectBuilder toJson(McpSamplingTextContent text) {
439+
static JsonObjectBuilder toJson(McpSamplingTextMessage text) {
440440
return JSON_BUILDER_FACTORY.createObjectBuilder()
441441
.add("role", text.role().text())
442442
.add("content", JSON_BUILDER_FACTORY.createObjectBuilder()
443443
.add("type", text.type().text())
444444
.add("text", text.text()));
445445
}
446446

447-
static JsonObjectBuilder toJson(McpSamplingAudioContent audio) {
447+
static JsonObjectBuilder toJson(McpSamplingAudioMessage audio) {
448448
return JSON_BUILDER_FACTORY.createObjectBuilder()
449449
.add("role", audio.role().text())
450450
.add("content", JSON_BUILDER_FACTORY.createObjectBuilder()
@@ -577,8 +577,6 @@ static McpSamplingResponse createSamplingResponse(JsonObject object) throws McpS
577577
throw new McpSamplingException(error.message());
578578
});
579579
try {
580-
var builder = McpSamplingResponse.builder();
581-
582580
var result = find(object, "result")
583581
.filter(McpJsonRpc::isJsonObject)
584582
.map(JsonValue::asJsonObject)
@@ -587,15 +585,13 @@ static McpSamplingResponse createSamplingResponse(JsonObject object) throws McpS
587585
String model = result.getString("model");
588586
McpRole role = McpRole.valueOf(result.getString("role").toUpperCase());
589587
McpSamplingMessage message = parseMessage(role, result.getJsonObject("content"));
590-
Optional<McpStopReason> stopReason = find(result, "stopReason")
588+
McpStopReason stopReason = find(result, "stopReason")
591589
.filter(McpJsonRpc::isJsonString)
592590
.map(JsonString.class::cast)
593591
.map(JsonString::getString)
594-
.map(McpStopReason::map);
595-
builder.model(model);
596-
builder.message(message);
597-
builder.stopReason(stopReason);
598-
return builder.build();
592+
.map(McpStopReason::map)
593+
.orElse(null);
594+
return new McpSamplingResponseImpl(message, model, stopReason);
599595
} catch (Exception e) {
600596
throw new McpSamplingException("Wrong sampling response format", e);
601597
}
@@ -651,20 +647,19 @@ static JsonObject timeoutResponse(long requestId) {
651647

652648
private static McpSamplingMessage parseMessage(McpRole role, JsonObject object) {
653649
String type = object.getString("type").toUpperCase();
654-
McpContent.ContentType contentType = McpContent.ContentType.valueOf(type);
655-
return switch (contentType) {
656-
case TEXT -> new McpSamplingTextContentImpl(object.getString("text"), role);
650+
McpSamplingMessageType messageType = McpSamplingMessageType.valueOf(type);
651+
return switch (messageType) {
652+
case TEXT -> new McpSamplingTextMessageImpl(object.getString("text"), role);
657653
case IMAGE -> {
658654
byte[] data = object.getString("data").getBytes(StandardCharsets.UTF_8);
659655
MediaType mediaType = MediaTypes.create(object.getString("mimeType"));
660-
yield new McpSamplingImageContentImpl(data, mediaType, role);
656+
yield new McpSamplingImageMessageImpl(data, mediaType, role);
661657
}
662658
case AUDIO -> {
663659
byte[] data = object.getString("data").getBytes(StandardCharsets.UTF_8);
664660
MediaType mediaType = MediaTypes.create(object.getString("mimeType"));
665-
yield new McpSamplingAudioContentImpl(data, mediaType, role);
661+
yield new McpSamplingAudioMessageImpl(data, mediaType, role);
666662
}
667-
default -> throw new IllegalArgumentException("Unknown content type: " + type);
668663
};
669664
}
670665

server/src/main/java/io/helidon/extensions/mcp/server/McpSamplingAudioContent.java renamed to server/src/main/java/io/helidon/extensions/mcp/server/McpSamplingAudioMessage.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@
1818
/**
1919
* MCP sampling audio content.
2020
*/
21-
public sealed interface McpSamplingAudioContent extends McpSamplingMessage,
22-
McpSamplingMediaContent permits McpSamplingAudioContentImpl {
21+
public sealed interface McpSamplingAudioMessage extends McpSamplingMessage,
22+
McpSamplingMediaMessage permits McpSamplingAudioMessageImpl {
2323
}

server/src/main/java/io/helidon/extensions/mcp/server/McpSamplingAudioContentImpl.java renamed to server/src/main/java/io/helidon/extensions/mcp/server/McpSamplingAudioMessageImpl.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,20 @@
2222
/**
2323
* MCP sampling audio content.
2424
*/
25-
final class McpSamplingAudioContentImpl implements McpSamplingAudioContent {
25+
final class McpSamplingAudioMessageImpl implements McpSamplingAudioMessage {
2626
private final byte[] data;
2727
private final McpRole role;
2828
private final MediaType type;
2929

30-
McpSamplingAudioContentImpl(byte[] data, MediaType type, McpRole role) {
30+
McpSamplingAudioMessageImpl(byte[] data, MediaType type, McpRole role) {
3131
this.data = data;
3232
this.role = role;
3333
this.type = type;
3434
}
3535

3636
@Override
37-
public ContentType type() {
38-
return ContentType.AUDIO;
37+
public McpSamplingMessageType type() {
38+
return McpSamplingMessageType.AUDIO;
3939
}
4040

4141
@Override
@@ -55,11 +55,11 @@ public byte[] data() {
5555

5656
@Override
5757
public byte[] decodeBase64Data() {
58-
return Base64.getMimeDecoder().decode(data);
58+
return Base64.getDecoder().decode(data);
5959
}
6060

6161
@Override
6262
public String encodeBase64Data() {
63-
return Base64.getMimeEncoder().encodeToString(data);
63+
return Base64.getEncoder().encodeToString(data);
6464
}
6565
}

server/src/main/java/io/helidon/extensions/mcp/server/McpSamplingImageContent.java renamed to server/src/main/java/io/helidon/extensions/mcp/server/McpSamplingImageMessage.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
/**
1919
* MCP sampling image content.
2020
*/
21-
public sealed interface McpSamplingImageContent extends McpSamplingMessage,
22-
McpSamplingMediaContent permits McpSamplingImageContentImpl {
21+
public sealed interface McpSamplingImageMessage extends McpSamplingMessage,
22+
McpSamplingMediaMessage permits McpSamplingImageMessageImpl {
2323

2424
}

server/src/main/java/io/helidon/extensions/mcp/server/McpSamplingImageContentImpl.java renamed to server/src/main/java/io/helidon/extensions/mcp/server/McpSamplingImageMessageImpl.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,20 @@
2222
/**
2323
* MCP sampling image content.
2424
*/
25-
final class McpSamplingImageContentImpl implements McpSamplingImageContent {
25+
final class McpSamplingImageMessageImpl implements McpSamplingImageMessage {
2626
private final byte[] data;
2727
private final McpRole role;
2828
private final MediaType type;
2929

30-
McpSamplingImageContentImpl(byte[] data, MediaType mediaType, McpRole role) {
30+
McpSamplingImageMessageImpl(byte[] data, MediaType mediaType, McpRole role) {
3131
this.data = data;
3232
this.role = role;
3333
this.type = mediaType;
3434
}
3535

3636
@Override
37-
public ContentType type() {
38-
return ContentType.IMAGE;
37+
public McpSamplingMessageType type() {
38+
return McpSamplingMessageType.IMAGE;
3939
}
4040

4141
@Override
@@ -55,11 +55,11 @@ public byte[] data() {
5555

5656
@Override
5757
public byte[] decodeBase64Data() {
58-
return Base64.getMimeDecoder().decode(data);
58+
return Base64.getDecoder().decode(data);
5959
}
6060

6161
@Override
6262
public String encodeBase64Data() {
63-
return Base64.getMimeEncoder().encodeToString(data);
63+
return Base64.getEncoder().encodeToString(data);
6464
}
6565
}

server/src/main/java/io/helidon/extensions/mcp/server/McpSamplingMediaContent.java renamed to server/src/main/java/io/helidon/extensions/mcp/server/McpSamplingMediaMessage.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
/**
2121
* MCP sampling media content.
2222
*/
23-
public sealed interface McpSamplingMediaContent permits McpSamplingAudioContent, McpSamplingImageContent {
23+
public sealed interface McpSamplingMediaMessage permits McpSamplingAudioMessage, McpSamplingImageMessage {
2424
/**
2525
* Image content raw data.
2626
*
@@ -29,14 +29,14 @@ public sealed interface McpSamplingMediaContent permits McpSamplingAudioContent,
2929
byte[] data();
3030

3131
/**
32-
* Media content decoded data.
32+
* Returns the decoded image data using base64 decoder.
3333
*
3434
* @return decoded content.
3535
*/
3636
byte[] decodeBase64Data();
3737

3838
/**
39-
* Media content data encoded in base64.
39+
* Returns the encoded image data using base64 encoder.
4040
*
4141
* @return content in base64.
4242
*/

server/src/main/java/io/helidon/extensions/mcp/server/McpSamplingMessage.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,18 @@
1919
/**
2020
* MCP sampling message.
2121
*/
22-
public sealed interface McpSamplingMessage extends McpContent permits McpSamplingTextContent,
23-
McpSamplingImageContent,
24-
McpSamplingAudioContent {
22+
public sealed interface McpSamplingMessage permits McpSamplingTextMessage, McpSamplingImageMessage, McpSamplingAudioMessage {
2523
/**
2624
* Sampling message role.
2725
*
2826
* @return role
2927
*/
3028
McpRole role();
29+
30+
/**
31+
* Sampling message type.
32+
*
33+
* @return type
34+
*/
35+
McpSamplingMessageType type();
3136
}

0 commit comments

Comments
 (0)