Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Oracle and/or its affiliates.
* Copyright (c) 2025, 2026 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.
Expand Down Expand Up @@ -61,7 +61,7 @@ final class McpCodegen implements CodegenExtension {
McpCodegen(CodegenContext context) {
logger = context.logger();
recorder = new McpRecorder();
toolCodegen = new McpToolCodegen(recorder);
toolCodegen = new McpToolCodegen(recorder, context);
promptCodegen = new McpPromptCodegen(recorder);
resourceCodegen = new McpResourceCodegen(recorder);
completionCodegen = new McpCompletionCodegen(recorder);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Oracle and/or its affiliates.
* Copyright (c) 2025, 2026 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.
Expand All @@ -21,6 +21,7 @@
import java.util.stream.Collectors;

import io.helidon.codegen.classmodel.ClassModel;
import io.helidon.codegen.classmodel.Executable;
import io.helidon.codegen.classmodel.Method;
import io.helidon.codegen.classmodel.Parameter;
import io.helidon.common.types.AccessModifier;
Expand All @@ -34,19 +35,28 @@

import static io.helidon.common.types.TypeNames.LIST;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_CANCELLATION;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_COMPLETION_REQUEST;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_DESCRIPTION;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_FEATURES;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_LOGGER;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_PARAMETERS;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_PROGRESS;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_PROMPT_REQUEST;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_REQUEST;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_RESOURCE_REQUEST;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_ROOTS;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_SAMPLING;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_SUBSCRIBE_REQUEST;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_TOOL_REQUEST;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_UNSUBSCRIBE_REQUEST;

/**
* Utility class for methods used by several MCP code generator.
*/
class McpCodegenUtil {
/**
* Pattern to match the first character of a string.
*/
private static final Pattern PATTERN = Pattern.compile("^.");

static final List<String> MCP_TYPES = List.of(MCP_REQUEST.classNameWithEnclosingNames(),
Expand Down Expand Up @@ -119,7 +129,12 @@ static boolean isIgnoredSchemaElement(TypeName typeName) {
|| MCP_FEATURES.equals(typeName)
|| MCP_PROGRESS.equals(typeName)
|| MCP_SAMPLING.equals(typeName)
|| MCP_CANCELLATION.equals(typeName);
|| MCP_PARAMETERS.equals(typeName)
|| MCP_CANCELLATION.equals(typeName)
|| MCP_TOOL_REQUEST.equals(typeName)
|| MCP_PROMPT_REQUEST.equals(typeName)
|| MCP_RESOURCE_REQUEST.equals(typeName)
|| MCP_COMPLETION_REQUEST.equals(typeName);
}

static boolean isResourceTemplate(String uri) {
Expand Down Expand Up @@ -154,6 +169,23 @@ static void addToListMethod(ClassModel.Builder classModel, TypeName type) {
classModel.addMethod(method.build());
}

/**
* Add text content to the builder as literal. The text can be multi line.
*
* @param builder executable builder
* @param text text content
*/
static void generateSafeMultiLine(Executable.Builder<?, ?> builder, String text) {
if (text.contains("\n")) {
builder.addContentLine("\"\"\"")
.increaseContentPadding()
.addContent(text.replace("\\\"", "\""))
.addContent("\"\"\"");
} else {
builder.addContentLiteral(text);
}
}

/**
* Returns {@code true} if the provided type is an MCP type and create request getter for that type,
* otherwise nothing is created and return {@code false}.
Expand Down Expand Up @@ -195,6 +227,14 @@ static boolean isMcpType(List<String> parameters, TypedElementInfo type) {
parameters.add("request.parameters()");
return true;
}
if (MCP_UNSUBSCRIBE_REQUEST.equals(type.typeName())) {
parameters.add("request");
return true;
}
if (MCP_SUBSCRIBE_REQUEST.equals(type.typeName())) {
parameters.add("request");
return true;
}
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,11 @@
import static io.helidon.extensions.mcp.codegen.McpCodegenUtil.createClassName;
import static io.helidon.extensions.mcp.codegen.McpCodegenUtil.getElementsWithAnnotation;
import static io.helidon.extensions.mcp.codegen.McpCodegenUtil.isMcpType;
import static io.helidon.extensions.mcp.codegen.McpTypes.FUNCTION_COMPLETION_REQUEST_COMPLETION_CONTENT;
import static io.helidon.extensions.mcp.codegen.McpTypes.LIST_STRING;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_COMPLETION;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_COMPLETION_CONTENT;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_COMPLETION_CONTENTS;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_COMPLETION_INTERFACE;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_COMPLETION_REQUEST;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_COMPLETION_RESULT;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_COMPLETION_TYPE;

class McpCompletionCodegen {
Expand Down Expand Up @@ -92,14 +91,18 @@ private void addCompletionMethod(Method.Builder builder, ClassModel.Builder clas
List<String> parameters = new ArrayList<>();

builder.name("completion")
.returnType(returned -> returned.type(FUNCTION_COMPLETION_REQUEST_COMPLETION_CONTENT))
.returnType(returned -> returned.type(MCP_COMPLETION_RESULT))
.addParameter(parameter -> parameter.type(MCP_COMPLETION_REQUEST).name("request"))
.addAnnotation(Annotations.OVERRIDE);
builder.addContentLine("return request -> {");

for (TypedElementInfo parameter : element.parameterArguments()) {
if (isMcpType(parameters, parameter)) {
continue;
}
if (parameter.typeName().equals(MCP_COMPLETION_REQUEST)) {
parameters.add("request");
continue;
}
if (parameter.typeName().equals(TypeNames.STRING)) {
parameters.add(parameter.elementName());
builder.addContent("var ")
Expand All @@ -114,31 +117,30 @@ private void addCompletionMethod(Method.Builder builder, ClassModel.Builder clas

String params = String.join(", ", parameters);
if (element.typeName().equals(LIST_STRING)) {
builder.addContent("return ")
.addContent(MCP_COMPLETION_CONTENTS)
.addContent(".completion(delegate.")
// Create local variable for delegate result called "list".
builder.addContent(LIST_STRING)
.addContent(" list = delegate.")
.addContent(element.elementName())
.addContent("(")
.addContent(params)
.addContentLine("));")
.decreaseContentPadding()
.addContentLine("};");
.addContentLine(");");
builder.addContent("return ")
.addContent(MCP_COMPLETION_RESULT)
.addContentLine(".create(list);");
return;
}
if (element.typeName().equals(MCP_COMPLETION_CONTENT)) {
if (element.typeName().equals(MCP_COMPLETION_RESULT)) {
builder.addContent("return delegate.")
.addContent(element.elementName())
.addContent("(")
.addContent(params)
.addContentLine(");")
.decreaseContentPadding()
.addContentLine("};");
.addContentLine(");");
return;
}
throw new CodegenException(String.format("Wrong return type for method: %s. Supported types are: %s, or %s.",
element.elementName(),
LIST_STRING,
MCP_COMPLETION_CONTENT.classNameWithTypes()));
MCP_COMPLETION_RESULT.classNameWithTypes()));

}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Oracle and/or its affiliates.
* Copyright (c) 2025, 2026 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.
Expand All @@ -17,7 +17,6 @@

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import io.helidon.codegen.CodegenException;
import io.helidon.codegen.classmodel.ClassModel;
Expand All @@ -35,15 +34,14 @@
import static io.helidon.extensions.mcp.codegen.McpCodegenUtil.getElementsWithAnnotation;
import static io.helidon.extensions.mcp.codegen.McpCodegenUtil.isIgnoredSchemaElement;
import static io.helidon.extensions.mcp.codegen.McpCodegenUtil.isMcpType;
import static io.helidon.extensions.mcp.codegen.McpTypes.FUNCTION_REQUEST_LIST_PROMPT_CONTENT;
import static io.helidon.extensions.mcp.codegen.McpTypes.LIST_MCP_PROMPT_ARGUMENT;
import static io.helidon.extensions.mcp.codegen.McpTypes.LIST_MCP_PROMPT_CONTENT;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_DESCRIPTION;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_NAME;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_PROMPT;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_PROMPT_ARGUMENT;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_PROMPT_CONTENTS;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_PROMPT_INTERFACE;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_PROMPT_REQUEST;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_PROMPT_RESULT;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_ROLE;
import static io.helidon.extensions.mcp.codegen.McpTypes.MCP_ROLE_ENUM;

Expand Down Expand Up @@ -95,24 +93,29 @@ private void addPromptDescriptionMethod(Method.Builder builder, String descripti
private void addPromptMethod(Method.Builder builder, ClassModel.Builder classModel, TypedElementInfo element) {
List<String> parameters = new ArrayList<>();
TypeName returnType = element.signature().type();
Optional<String> role = element.findAnnotation(MCP_ROLE)
.flatMap(annotation -> annotation.value());
String role = element.findAnnotation(MCP_ROLE)
.flatMap(annotation -> annotation.value())
.orElse("ASSISTANT");

builder.name("prompt")
.returnType(returned -> returned.type(FUNCTION_REQUEST_LIST_PROMPT_CONTENT))
.returnType(returned -> returned.type(MCP_PROMPT_RESULT))
.addParameter(parameter -> parameter.type(MCP_PROMPT_REQUEST).name("request"))
.addAnnotation(Annotations.OVERRIDE);
builder.addContentLine("return request -> {");

for (TypedElementInfo param : element.parameterArguments()) {
if (isMcpType(parameters, param)) {
continue;
}
if (param.typeName().equals(MCP_PROMPT_REQUEST)) {
parameters.add("request");
continue;
}
if (param.typeName().equals(TypeNames.STRING)) {
parameters.add(param.elementName());
builder.addContent(param.typeName().classNameWithEnclosingNames())
.addContent(" ")
.addContent(param.elementName())
.addContent(" = request.parameters().get(\"")
.addContent(" = request.arguments().get(\"")
.addContent(param.elementName())
.addContentLine("\").asString().orElse(\"\");");
continue;
Expand All @@ -123,37 +126,35 @@ private void addPromptMethod(Method.Builder builder, ClassModel.Builder classMod
}

String params = String.join(", ", parameters);
if (returnType.equals(TypeNames.STRING)) {
builder.addContent("return ")
.addContent(List.class)
.addContent(".of(")
.addContent(MCP_PROMPT_CONTENTS)
.addContent(".textContent(delegate.")
if (returnType.equals(MCP_PROMPT_RESULT)) {
builder.addContent("return delegate.")
.addContent(element.elementName())
.addContent("(")
.addContent(params)
.addContent("), ")
.addContent(MCP_ROLE_ENUM)
.addContent(".")
.addContent(role.orElse("ASSISTANT"))
.addContentLine("));")
.decreaseContentPadding()
.addContentLine("};");
.addContentLine(");");
return;
}
if (returnType.equals(LIST_MCP_PROMPT_CONTENT)) {
builder.addContent("return delegate.")
if (returnType.equals(TypeNames.STRING)) {
builder.addContent("return ")
.addContent(MCP_PROMPT_RESULT)
.addContentLine(".builder()")
.increaseContentPadding()
.addContent(".addTextContent(t -> t.text(")
.addContent("delegate.")
.addContent(element.elementName())
.addContent("(")
.addContent(params)
.addContentLine(");")
.decreaseContentPadding()
.addContentLine("};");
.addContent(")).role(")
.addContent(MCP_ROLE_ENUM)
.addContent(".")
.addContent(role)
.addContentLine("))")
.addContentLine(".build();");
return;
}
throw new CodegenException(String.format("Wrong return type for method: %s. Supported types are: %s, or String.",
element.elementName(),
LIST_MCP_PROMPT_CONTENT.classNameWithTypes()));
MCP_PROMPT_RESULT.classNameWithTypes()));
}

private void addPromptArgumentsMethod(Method.Builder builder, TypedElementInfo element) {
Expand Down
Loading