Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -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
11 changes: 6 additions & 5 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,8 +34,8 @@ 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} -
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 @@ -19,5 +19,6 @@
/**
* Audio content.
*/
sealed interface McpAudioContent extends McpMediaContent permits McpAudioContentImpl {
sealed interface McpAudioContent extends McpMediaContent permits McpAudioContentImpl,
McpSamplingAudioContent {
}
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,11 @@
/**
* General content type for all MCP component contents.
*/
public sealed interface McpContent permits McpEmbeddedResource,
public sealed interface McpContent permits McpTextContent,
McpMediaContent,
McpResourceContent,
McpTextContent,
McpMediaContent {
McpSamplingMessage,
McpEmbeddedResource {
/**
* Content type.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2025 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.helidon.extensions.mcp.server;

import java.util.Optional;

import io.helidon.builder.api.Prototype;

import static io.helidon.extensions.mcp.server.McpPagination.DEFAULT_PAGE_SIZE;

/**
* Placeholder for the MCP configuration decorators.
*/
final class McpDecorators {
private McpDecorators() {
}

/**
* Enforce positive page size.
* <p>
* See {@link io.helidon.extensions.mcp.server.McpPagination}.
*/
static class PageSizeDecorator implements Prototype.OptionDecorator<McpServerConfig.BuilderBase<?, ?>, Integer> {
@Override
public void decorate(McpServerConfig.BuilderBase<?, ?> builder, Integer pageSize) {
if (pageSize < DEFAULT_PAGE_SIZE) {
throw new IllegalArgumentException("Page size must be greater than zero");
}
}
}

/**
* Enforce intelligence priority value between 0 and 1.
* <p>
* See {@link io.helidon.extensions.mcp.server.McpSamplingRequest}.
*/
static class IntelligencePriorityDecorator implements Prototype.OptionDecorator<McpSamplingRequest.BuilderBase<?, ?>, Optional<Double>> {
@Override
public void decorate(McpSamplingRequest.BuilderBase<?, ?> builder, Optional<Double> value) {
value.filter(McpDecorators::isPositiveAndInferiorToOne)
.orElseThrow(() -> new IllegalArgumentException("Intelligence priority must be in range [0, 1]"));
}
}

/**
* Enforce speed priority value between 0 and 1.
* <p>
* See {@link io.helidon.extensions.mcp.server.McpSamplingRequest}.
*/
static class SpeedPriorityDecorator implements Prototype.OptionDecorator<McpSamplingRequest.BuilderBase<?, ?>, Optional<Double>> {
@Override
public void decorate(McpSamplingRequest.BuilderBase<?, ?> builder, Optional<Double> value) {
value.filter(McpDecorators::isPositiveAndInferiorToOne)
.orElseThrow(() -> new IllegalArgumentException("Speed priority must be in range [0, 1]"));
}
}

/**
* Enforce cost priority value between 0 and 1.
* <p>
* See {@link io.helidon.extensions.mcp.server.McpSamplingRequest}.
*/
static class CostPriorityDecorator implements Prototype.OptionDecorator<McpSamplingRequest.BuilderBase<?, ?>, Optional<Double>> {
@Override
public void decorate(McpSamplingRequest.BuilderBase<?, ?> builder, Optional<Double> value) {
value.filter(McpDecorators::isPositiveAndInferiorToOne)
.orElseThrow(() -> new IllegalArgumentException("Cost priority must be in range [0, 1]"));
}
}

static boolean isPositiveAndInferiorToOne(Double value) {
return 0 <= value && value <= 1.0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
* {@link io.helidon.extensions.mcp.server.McpSubscriptions} - MCP subscription feature.
* Sends notifications to the subscribed clients.
* </li>
* <li>
* {@link io.helidon.extensions.mcp.server.McpSampling} - MCP Sampling feature.
* Send sampling messages to client.
* </li>
* </ul>
*/
public final class McpFeatures {
Expand All @@ -51,8 +55,9 @@ public final class McpFeatures {
private final McpSession session;

private SseSink sseSink;
private McpProgress progress;
private McpLogger logger;
private McpSampling sampling;
private McpProgress progress;
private McpSubscriptions subscriptions;

McpFeatures(McpSession session) {
Expand Down Expand Up @@ -111,6 +116,23 @@ public McpLogger logger() {
return logger;
}

/**
* Get a {@link io.helidon.extensions.mcp.server.McpSampling} feature.
*
* @return the MCP sampling
*/
public McpSampling sampling() {
if (sampling == null) {
if (response != null) {
sseSink = getOrCreateSseSink();
sampling = new McpSampling(session, sseSink);
} else {
sampling = new McpSampling(session);
}
}
return sampling;
}

/**
* Get a {@link io.helidon.extensions.mcp.server.McpSubscriptions} feature.
*
Expand Down
Loading
Loading