Implementing Prompts
Prompts are predefined templates for LLM interactions that can be filled with variable values and returned to clients. MCP provides a standardized way for servers to expose prompt templates to clients. This guide shows you how to implement prompts using both declarative annotations and programmatic APIs.
Basic Prompt Implementation
import io.quarkiverse.mcp.server.Prompt;
import io.quarkiverse.mcp.server.PromptArg;
import io.quarkiverse.mcp.server.PromptMessage;
import io.quarkiverse.mcp.server.TextContent;
import jakarta.inject.Inject;
// @Singleton (1)
public class MyPrompts {
@Inject (2)
FooService fooService;
@Prompt(description = "Put your description here.") (3)
PromptMessage foo(@PromptArg(description = "The name", defaultValue = "Max") String name) { (4)
return PromptMessage.withUserRole(new TextContent(fooService.ping(name)));
}
}
| 1 | The @Singleton scope is added automatically, if needed. |
| 2 | MyPrompts is an ordinary CDI bean. It can inject other beans, use interceptors, etc. |
| 3 | @Prompt annotates a business method of a CDI bean that should be exposed as a prompt template. By default, the name of the prompt is derived from the method name. |
| 4 | The @PromptArg annotation can be used to customize the description of an argument and set the default value that is used when a client does not provide an argument value. |
Default values are specified as strings and automatically converted to the argument type. For complex types, you can implement custom DefaultValueConverter instances. See Default Value Converters for more information.
|
Prompt Arguments
Prompt methods accept string parameters representing the prompt arguments. Each argument can be customized with the @PromptArg annotation:
import io.quarkiverse.mcp.server.Prompt;
import io.quarkiverse.mcp.server.PromptArg;
import io.quarkiverse.mcp.server.PromptMessage;
import io.quarkiverse.mcp.server.TextContent;
public class CodePrompts {
@Prompt(description = "Generate code based on requirements")
PromptMessage generateCode(
@PromptArg(description = "Programming language", defaultValue = "Java") String language,
@PromptArg(description = "Code requirements") String requirements,
@PromptArg(description = "Code style", defaultValue = "clean") String style) {
String promptText = String.format(
"Generate %s code with %s style for: %s",
language, style, requirements
);
return PromptMessage.withUserRole(new TextContent(promptText));
}
}
Arguments without a defaultValue are required and must be provided by the client.
Return Types
The result of a "prompt get" operation is always represented as a PromptResponse.
However, @Prompt methods can return other types that are automatically converted.
For details on supported return types and conversion rules, see Prompt Return Type Conversion.
Common Return Type Examples
Return a single message:
@Prompt(description = "Simple prompt")
PromptMessage simplePrompt(String input) {
return PromptMessage.withUserRole(new TextContent("Process: " + input));
}
Return multiple messages for conversation context:
import java.util.List;
@Prompt(description = "Multi-message prompt")
List<PromptMessage> conversationPrompt(String topic) {
return List.of(
PromptMessage.withSystemRole(new TextContent("You are a helpful assistant.")),
PromptMessage.withUserRole(new TextContent("Tell me about " + topic))
);
}
Return async results with Uni:
import io.smallrye.mutiny.Uni;
@Prompt(description = "Async prompt")
Uni<PromptMessage> asyncPrompt(String query) {
return fetchDataAsync(query)
.map(data -> PromptMessage.withUserRole(new TextContent(data)));
}
Method Parameters
A @Prompt method must only accept String parameters that represent prompt arguments.
However, it may also accept the following special parameters:
io.quarkiverse.mcp.server.McpConnection-
The connection from an MCP client.
io.quarkiverse.mcp.server.McpLog-
Used to send log message notifications to the MCP client.
io.quarkiverse.mcp.server.RequestId-
The identifier of the current MCP request.
io.quarkiverse.mcp.server.Progress-
Used to send progress notification messages back to the client.
io.quarkiverse.mcp.server.Roots-
Used to obtain the list of root objects from the MCP client.
io.quarkiverse.mcp.server.Sampling-
Used to request LLM sampling from models.
io.quarkiverse.mcp.server.Elicitation-
Used to request additional information from the client.
io.quarkiverse.mcp.server.Cancellation-
Used to determine if an MCP client requested a cancellation of an in-progress request.
io.quarkiverse.mcp.server.RawMessage-
Represents an unprocessed MCP request or notification from an MCP client.
io.quarkiverse.mcp.server.Meta-
Additional metadata sent from the client to the server, i.e. the
_metapart of the message.
See Parameter Types Reference for complete details on supported parameter types.
Example with logging:
import io.quarkiverse.mcp.server.McpLog;
@Prompt(description = "Prompt with logging")
PromptMessage loggedPrompt(String input, McpLog log) {
log.info("Processing prompt with input: {}", input);
return PromptMessage.withUserRole(new TextContent("Processing: " + input));
}
Completion API
Arguments of a @Prompt method may be auto-completed through the completion API.
This allows clients to provide suggestions as users type argument values.
import io.quarkiverse.mcp.server.CompleteArg;
import io.quarkiverse.mcp.server.CompletePrompt;
import io.quarkiverse.mcp.server.Prompt;
import io.quarkiverse.mcp.server.PromptArg;
import io.quarkiverse.mcp.server.PromptMessage;
import io.quarkiverse.mcp.server.TextContent;
import jakarta.inject.Inject;
import java.util.List;
public class MyPrompts {
@Inject
FooService fooService;
@Prompt(description = "Put your description here.")
PromptMessage foo(@PromptArg(description = "The name") String name) {
return PromptMessage.withUserRole(new TextContent(fooService.ping(name)));
}
@CompletePrompt("foo") (1)
List<String> completeName(@CompleteArg(name = "name") String val) { (2) (3)
return fooService.getNames().stream()
.filter(n -> n.startsWith(val))
.toList();
}
}
| 1 | "foo" is the name reference to a prompt. If no such prompt exists, the build fails. |
| 2 | The method returns a list of matching values. |
| 3 | The @CompleteArg annotation can be used to customize the name of an argument. |
Completion Context
When implementing completion methods, you may need access to previously completed arguments to provide context-aware suggestions.
The CompleteContext interface provides this capability:
import io.quarkiverse.mcp.server.CompleteContext;
import io.quarkiverse.mcp.server.CompletePrompt;
import io.quarkiverse.mcp.server.CompleteArg;
import io.quarkiverse.mcp.server.Prompt;
import io.quarkiverse.mcp.server.PromptArg;
import io.quarkiverse.mcp.server.PromptMessage;
import io.quarkiverse.mcp.server.TextContent;
import java.util.List;
public class SearchPrompts {
@Prompt(description = "Search for files in a directory")
PromptMessage searchFiles(
@PromptArg(description = "Directory path") String directory,
@PromptArg(description = "File name") String fileName) {
return PromptMessage.withUserRole(
new TextContent("Searching in " + directory + " for " + fileName));
}
@CompletePrompt("searchFiles")
List<String> completeFileName(
CompleteContext context, (1)
@CompleteArg(name = "fileName") String fileName) {
// Access the previously completed directory argument
String directory = context.arguments().get("directory"); (2)
if (directory == null) {
// No directory specified yet, provide generic suggestions
return List.of("README.md", "pom.xml", "package.json");
}
// Provide context-aware suggestions based on the directory
return getFilesInDirectory(directory).stream()
.filter(f -> f.startsWith(fileName))
.toList();
}
private List<String> getFilesInDirectory(String dir) {
// Implementation to list files in directory
return List.of("file1.txt", "file2.txt", "config.json");
}
}
| 1 | Inject CompleteContext to access previously completed arguments. |
| 2 | Retrieve the value of a previously completed argument by name. |
The CompleteContext.arguments() method returns a Map<String, String> containing all previously completed argument values.
Programmatic API
It’s also possible to register a prompt programmatically with the io.quarkiverse.mcp.server.PromptManager API.
For example, if a prompt is only known at application startup time, it can be added as follows:
import io.quarkiverse.mcp.server.PromptManager;
import io.quarkiverse.mcp.server.PromptMessage;
import io.quarkiverse.mcp.server.PromptResponse;
import io.quarkiverse.mcp.server.TextContent;
import io.quarkus.runtime.Startup;
import jakarta.inject.Inject;
import java.util.List;
public class MyPrompts {
@Inject
PromptManager promptManager; (1)
@Inject
CodeService codeService;
@Startup (2)
void addPrompt() {
promptManager.newPrompt("code_assist") (3)
.setDescription("Prompt for code assistance")
.addArgument("lang", "Programming language", true)
.setHandler(
a -> PromptResponse.withMessages(
List.of(PromptMessage.withUserRole(
new TextContent(codeService.assist(a.args().get("lang")))))))
.register(); (4)
}
}
| 1 | The injected manager can be used to obtain metadata and register a new prompt programmatically. |
| 2 | Ensure that addPrompt is executed when the application starts. |
| 3 | The PromptManager#newPrompt(String) method returns PromptDefinition, a builder-like API. |
| 4 | Registers the prompt definition and sends the notifications/prompts/list_changed notification to all connected clients. |
The programmatic API also allows you to remove existing prompts at runtime (using removePrompt(String name)), which is not possible with the annotation-based approach.