Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_RESOURCE_UNSUBSCRIBER_INTERFACE;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_ROLE;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_ROLE_ENUM;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_SAMPLING;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_SERVER;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_SERVER_CONFIG;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_TOOL;
Expand Down Expand Up @@ -448,6 +449,10 @@ private void addResourceMethod(Method.Builder builder, String uri, ClassModel.Bu
parameters.add("request.features().cancellation()");
continue;
}
if (MCP_SAMPLING.equals(parameter.typeName())) {
parameters.add("request.features().sampling()");
continue;
}
if (isResourceTemplate(uri)) {
if (MCP_PARAMETERS.equals(parameter.typeName())) {
parameters.add("request.parameters()");
Expand Down Expand Up @@ -590,6 +595,16 @@ private void addPromptMethod(Method.Builder builder, ClassModel.Builder classMod
builder.addContentLine("var cancellation = features.cancellation();");
continue;
}
if (MCP_SAMPLING.equals(param.typeName())) {
if (!featuresLocalVar) {
addFeaturesLocalVar(builder, classModel);
featuresLocalVar = true;
}
parameters.add("sampling");
classModel.addImport(MCP_SAMPLING);
builder.addContentLine("var sampling = features.sampling();");
continue;
}
if (!parametersLocalVar) {
addParametersLocalVar(builder, classModel);
parametersLocalVar = true;
Expand Down Expand Up @@ -750,83 +765,62 @@ private void addToolMethod(Method.Builder builder, ClassModel.Builder classModel
.addAnnotation(Annotations.OVERRIDE);
builder.addContentLine("return request -> {");

boolean featuresLocalVar = false;
boolean parametersLocalVar = false;
for (TypedElementInfo param : element.parameterArguments()) {
if (MCP_REQUEST.equals(param.typeName())) {
parameters.add("request");
continue;
}
if (MCP_FEATURES.equals(param.typeName()) && !featuresLocalVar) {
if (MCP_FEATURES.equals(param.typeName())) {
addFeaturesLocalVar(builder, classModel);
parameters.add("features");
featuresLocalVar = true;
parameters.add("request.features()");
continue;
}
if (MCP_LOGGER.equals(param.typeName())) {
if (!featuresLocalVar) {
addFeaturesLocalVar(builder, classModel);
featuresLocalVar = true;
}
parameters.add("logger");
builder.addContentLine("var logger = features.logger();");
builder.addContentLine("var logger = request.features().logger();");
continue;
}
if (MCP_PROGRESS.equals(param.typeName())) {
if (!featuresLocalVar) {
addFeaturesLocalVar(builder, classModel);
featuresLocalVar = true;
}
parameters.add("progress");
classModel.addImport(MCP_PROGRESS);
builder.addContentLine("var progress = features.progress();");
builder.addContentLine("var progress = request.features().progress();");
continue;
}
if (MCP_CANCELLATION.equals(param.typeName())) {
if (!featuresLocalVar) {
addFeaturesLocalVar(builder, classModel);
featuresLocalVar = true;
}
parameters.add("cancellation");
classModel.addImport(MCP_CANCELLATION);
builder.addContentLine("var cancellation = features.cancellation();");
builder.addContentLine("var cancellation = request.features().cancellation();");
continue;
}
if (MCP_SAMPLING.equals(param.typeName())) {
parameters.add("sampling");
classModel.addImport(MCP_SAMPLING);
builder.addContentLine("var sampling = request.features().sampling();");
continue;
}
if (TypeNames.STRING.equals(param.typeName())) {
if (!parametersLocalVar) {
addParametersLocalVar(builder, classModel);
parametersLocalVar = true;
}
parameters.add(param.elementName());
builder.addContent("var ")
.addContent(param.elementName())
.addContent(" = parameters.get(\"")
.addContent(" = request.parameters().get(\"")
.addContent(param.elementName())
.addContentLine("\").asString().orElse(\"\");");
continue;
}
if (isBoolean(param.typeName())) {
if (!parametersLocalVar) {
addParametersLocalVar(builder, classModel);
parametersLocalVar = true;
}
parameters.add(param.elementName());
builder.addContent("boolean ")
.addContent(param.elementName())
.addContent(" = parameters.get(\"")
.addContent(" = request.parameters().get(\"")
.addContent(param.elementName())
.addContentLine("\").asBoolean().orElse(false);");
continue;
}
if (isNumber(param.typeName())) {
if (!parametersLocalVar) {
addParametersLocalVar(builder, classModel);
parametersLocalVar = true;
}
parameters.add(param.elementName());
builder.addContent("var ")
.addContent(param.elementName())
.addContent(" = parameters.get(\"")
.addContent(" = request.parameters().get(\"")
.addContent(param.elementName())
.addContent("\").as")
.addContent(param.typeName().className())
Expand All @@ -836,28 +830,19 @@ private void addToolMethod(Method.Builder builder, ClassModel.Builder classModel
if (isList(param.typeName())) {
TypeName typeArg = param.typeName().typeArguments().getFirst();
addToListMethod(classModel, typeArg);

if (!parametersLocalVar) {
addParametersLocalVar(builder, classModel);
parametersLocalVar = true;
}
parameters.add(param.elementName());
builder.addContent("var ")
.addContent(param.elementName())
.addContent(" = toList(parameters.get(\"")
.addContent(" = toList(request.parameters().get(\"")
.addContent(param.elementName())
.addContentLine("\").asList().orElse(null));");
continue;
}
if (!parametersLocalVar) {
addParametersLocalVar(builder, classModel);
parametersLocalVar = true;
}
parameters.add(param.elementName());
builder.addContent(param.typeName().classNameWithEnclosingNames())
.addContent(" ")
.addContent(param.elementName())
.addContent(" = parameters.get(\"")
.addContent(" = request.parameters().get(\"")
.addContent(param.elementName())
.addContent("\").as(")
.addContent(param.typeName())
Expand Down Expand Up @@ -905,7 +890,9 @@ private void addToolDescriptionMethod(Method.Builder builder, String description
builder.name("description")
.addAnnotation(Annotations.OVERRIDE)
.returnType(TypeNames.STRING)
.addContentLine("return \"" + description + "\";");
.addContent("return \"")
.addContent(description)
.addContentLine("\";");
}

private void addToolAnnotationsMethod(Method.Builder builder, Annotation toolAnnotation) {
Expand Down Expand Up @@ -1077,9 +1064,10 @@ private TypeName generatedTypeName(TypeName factoryTypeName, String suffix) {

private boolean isIgnoredSchemaElement(TypeName typeName) {
return MCP_REQUEST.equals(typeName)
|| MCP_FEATURES.equals(typeName)
|| MCP_LOGGER.equals(typeName)
|| MCP_FEATURES.equals(typeName)
|| MCP_PROGRESS.equals(typeName)
|| MCP_SAMPLING.equals(typeName)
|| MCP_CANCELLATION.equals(typeName);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_SERVER;

/**
* Mcp code generator provider.
* MCP code generator provider.
*/
public class McpCodegenProvider implements CodegenExtensionProvider {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ private McpTypes() {
static final TypeName MCP_REQUEST = TypeName.create("io.helidon.extensions.mcp.server.McpRequest");
static final TypeName MCP_FEATURES = TypeName.create("io.helidon.extensions.mcp.server.McpFeatures");
static final TypeName MCP_PROGRESS = TypeName.create("io.helidon.extensions.mcp.server.McpProgress");
static final TypeName MCP_SAMPLING = TypeName.create("io.helidon.extensions.mcp.server.McpSampling");
static final TypeName MCP_TOOL_INTERFACE = TypeName.create("io.helidon.extensions.mcp.server.McpTool");
static final TypeName MCP_PARAMETERS = TypeName.create("io.helidon.extensions.mcp.server.McpParameters");
static final TypeName MCP_PROMPT_INTERFACE = TypeName.create("io.helidon.extensions.mcp.server.McpPrompt");
Expand Down
14 changes: 14 additions & 0 deletions docs/mcp-declarative/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,20 @@ List<McpToolContent> cancellationTool(McpCancellation cancellation) {
}
```

### Sampling

See the full [sampling documentation details](../mcp/README.md#sampling)

#### Example

Below is an example of a tool that uses the Sampling feature. `McpSampling` object can be used as method parameter.

```java
@Mcp.Tool("Uses MCP Sampling to ask the connected client model.")
List<McpToolContent> samplingTool(McpSampling sampling) {
}
```

## References

- [MCP Specification](https://modelcontextprotocol.io/introduction)
Expand Down
97 changes: 97 additions & 0 deletions docs/mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,103 @@ private class CancellationTool implements McpTool {
}
```

### Sampling

The MCP Sampling feature provides a standardized mechanism that allows servers to request LLM sampling operations from language
models through connected clients. It enables servers to seamlessly integrate AI capabilities into their workflows without
requiring API keys. Like other MCP features, sampling can be accessed via the MCP request features.
Sampling support is optional for clients, and servers can verify its availability using the `enabled` method:

```java
var sampling = request.features().sampling();
if (!sampling.enabled()) {
}
```

If the client supports sampling, you can send a sampling request using the request method. A builder is provided to configure
and customize the sampling request as needed:

```java
McpSamplingRequest request = McpSamplingRequest.builder()
.maxTokens(1)
.temperature(0.1)
.costPriority(0.1)
.speedPriority(0.1)
.hints(List.of("hint1"))
.metadata(JsonValue.TRUE)
.intelligencePriority(0.1)
.systemPrompt("system prompt")
.timeout(Duration.ofSeconds(10))
.stopSequences(List.of("stop1"))
.includeContext(McpIncludeContext.NONE)
.addMessage(McpSamplingMessages.textContent("text", McpRole.USER))
.build();
```

Once your request is built, send it using the sampling feature. The request method may throw an `McpSamplingException` if an
error occurs during processing. On success, it returns an McpSamplingResponse containing the response message, the model used,
and optionally a stop reason.

```java
try {
McpSamplingResponse response = sampling.request(req -> req.addMessage(message));
} catch(McpSamplingException exception) {
// Manage error
}
```

The messages you send are prompts to the language model, and they follow the same structure as MCP prompts. You can use the
`McpSamplingMessages` utility class to create different types of messages for the client model:

```java
var text = McpSamplingMessages.textContent("Explain Helidon MCP in one paragraph.", McpRole.USER);
var image = McpSamplingMessages.imageContent(pngBytes, MediaTypes.create("image/png"), McpRole.USER);
var audio = McpSamplingMessages.audioContent(wavBytes, MediaTypes.create("audio/wav"), McpRole.USER);
```

#### Example

Below is an example of a tool that uses the Sampling feature. If the connected client does not support sampling, the tool
throws a `McpToolErrorException`.

```java
class SamplingTool implements McpTool {
@Override
public String name() {
return "sampling-tool";
}

@Override
public String description() {
return "Uses MCP Sampling to ask the connected client model.";
}

@Override
public String schema() {
return "";
}

@Override
public List<McpToolContent> process(McpRequest request) {
var sampling = request.features().sampling();

if (!sampling.enabled()) {
throw new McpToolErrorException("This tool requires sampling feature");
}

try {
McpSamplingResponse response = sampling.request(req -> req
.timeout(Duration.ofSeconds(10))
.systemPrompt("You are a concise, helpful assistant.")
.addMessage(McpSamplingMessages.textContent("Write a 3-line summary of Helidon MCP Sampling.", McpRole.USER)));
return List.of(McpToolContents.textContent(response.asTextMessage()));
} catch (McpSamplingException e) {
throw new McpToolErrorException(e.getMessage());
}
}
}
```

## Configuration

MCP server configuration can be defined using Helidon configuration files. Example in YAML:
Expand Down
13 changes: 7 additions & 6 deletions server/src/main/java/io/helidon/extensions/mcp/server/Mcp.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ public final class Mcp {
/**
* Annotation to define an MCP server. An MCP Server aggregates several MCP
* components like tools, prompts, resources and completions.
*
* <p>The primary components include:</p>
* <p>
* The primary components include:
* <ul>
* <li>
* {@link io.helidon.extensions.mcp.server.Mcp.Tool} -
* Tool is a function that computes a set of inputs and return a result. Mcp server uses tools to
* Tool is a function that computes a set of inputs and return a result. MCP server uses tools to
* interact with the outside world to reach real time data through API calls, access to databases
* or performing any kind of computation.
* </li>
Expand All @@ -61,7 +61,8 @@ public final class Mcp {
* This way, the server can suggest where are resources located and which arguments can be used.
* </li>
* </ul>
* <p>The MCP server can be configured using the following annotations:</p>
* <p>
* The MCP server can be configured using the following annotations:
* <ul>
* <li>
* {@link io.helidon.extensions.mcp.server.Mcp.Version} -
Expand Down Expand Up @@ -208,8 +209,8 @@ public final class Mcp {
* Annotation to define an MCP resource.
* A resource is a none static method and must be located in a class annotated with
* {@link io.helidon.extensions.mcp.server.Mcp.Server}. This way, the resource is automatically registered to the server.
*
* <p>This annotation supports two kinds of Resource:</p>
* <p>
* This annotation supports two kinds of Resource:
* <ul>
* <li>
* {@code Regular Resource} where the resource {@link java.net.URI} points to an MCP resource such as
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ enum McpCapability {
COMPLETION,
PAGINATION,
SAMPLING,
ROOT,
ROOTS,
PROGRESS;

String text() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
/**
* General content type for all MCP component contents.
*/
public sealed interface McpContent permits McpEmbeddedResource,
public sealed interface McpContent permits McpTextContent,
McpMediaContent,
McpResourceContent,
McpTextContent,
McpMediaContent {
McpEmbeddedResource {
/**
* Content type.
*
Expand Down
Loading