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
@@ -0,0 +1,18 @@
package io.quarkus.runtime.annotations;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

/**
* Enable a JsonRPC Method for MCP by default
*/
@Retention(RUNTIME)
@Target({ METHOD })
@Documented
public @interface DevMCPEnableByDefault {

}
18 changes: 17 additions & 1 deletion docs/src/main/asciidoc/dev-mcp.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,17 @@ footerPageBuildItem.addBuildTimeData(
);
----

The extra `description` argument is new: without it the data only appears in the Dev UI. Once supplied, the `jokes` data becomes an MCP resource.
The extra `description` argument is new: without it the data only appears in the Dev UI. Once supplied, the `jokes` data becomes an MCP resource, however, by default this resource will be disabled. You can change the default by passing in `true` as another parameter after description, that will change this default to be enabled. Users can change this in Dev UI anyway, so this is just a sensible default.

[source,java]
----
footerPageBuildItem.addBuildTimeData(
"jokes",
jokesBuildItem.getJokes(),
"Some funny jokes that the user might enjoy",
true
);
----

==== MCP Resources against a recorded value

Expand All @@ -76,6 +86,8 @@ BuildTimeActionBuildItem createBuildTimeActions() {
<3> Set a human‑readable description.
<4> Provide the runtime value returned by your recorder.

By default this resource will be disabled, and the user has to enable it in Dev UI. You can change this default by adding `.enableMcpFuctionByDefault()` in the builder method.

=== MCP tools

A tool corresponds to a method that a client can call. Any JSON‑RPC method can be exposed as a tool by supplying descriptions on the method and its parameters.
Expand All @@ -100,6 +112,8 @@ public class MyExtensionRPCService {
<1> Description of the method.
<2> Description of each parameter.

By default this tool will be disabled, and the user has to enable it in Dev UI. You can change this default by adding the `@DevMCPEnableByDefault` annotation on the method.

You must register the JSON‑RPC service in the deployment module:

[source,java]
Expand Down Expand Up @@ -145,6 +159,8 @@ BuildTimeActionBuildItem createBuildTimeActions() {

The code in the `function` runs on the deployment classpath. The function can return a plain value, a `CompletionStage` or `CompletableFuture` for asynchronous work.

By default this tool will be disabled, and the user has to enable it in Dev UI. You can change this default by adding `.enableMcpFuctionByDefault()` in the builder method.

=== JSON‑RPC usage

By default all JSON‑RPC methods are visible in the Dev UI. Only methods with descriptions are exposed via Dev MCP.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import io.quarkus.assistant.runtime.dev.Assistant;
import io.quarkus.datasource.common.runtime.DataSourceUtil;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.annotations.DevMCPEnableByDefault;
import io.quarkus.runtime.annotations.JsonRpcDescription;

public final class DatabaseInspector {
Expand Down Expand Up @@ -96,6 +97,7 @@ protected void init() {
}

@JsonRpcDescription("Get all available datasources for the Database")
@DevMCPEnableByDefault
public List<Datasource> getDataSources() {
if (isDev) {
List<Datasource> datasources = new ArrayList<>();
Expand All @@ -110,6 +112,7 @@ public List<Datasource> getDataSources() {
}

@JsonRpcDescription("Get a spesific datasource for the Database by name")
@DevMCPEnableByDefault
private Datasource getDatasource(@JsonRpcDescription("Datasource name") String datasource) {
if (isDev) {
AgroalDataSource ads = checkedDataSources.get(datasource);
Expand All @@ -126,6 +129,7 @@ private Datasource getDatasource(@JsonRpcDescription("Datasource name") String d
}

@JsonRpcDescription("Get all the tables for a certain datasource")
@DevMCPEnableByDefault
public List<Table> getTables(@JsonRpcDescription("Datasource name") String datasource) {
if (isDev) {
List<Table> tableList = new ArrayList<>();
Expand Down Expand Up @@ -238,6 +242,7 @@ public String generateDot(@JsonRpcDescription("Datasource name") String datasour
}

@JsonRpcDescription("Execute SQL against a certain datasource")
@DevMCPEnableByDefault
public DataSet executeSQL(@JsonRpcDescription("Datasource name") String datasource,
@JsonRpcDescription("Valid SQL to execute") String sql,
@JsonRpcDescription("Page number for pagable rusults, starting at 1") Integer pageNumber,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ public class DevUIContent {
private final byte[] template;
private final Map<String, Object> data;
private final Map<String, String> descriptions;
private final Map<String, String> mcpDefaultEnabled;
private final Map<String, String> contentTypes;

private DevUIContent(DevUIContent.Builder builder) {
this.fileName = builder.fileName;
this.template = builder.template;
this.data = builder.data;
this.descriptions = builder.descriptions;
this.mcpDefaultEnabled = builder.mcpDefaultEnabled;
this.contentTypes = builder.contentTypes;
}

Expand All @@ -37,6 +39,10 @@ public Map<String, String> getDescriptions() {
return descriptions;
}

public Map<String, String> getMcpDefaultEnables() {
return mcpDefaultEnabled;
}

public Map<String, String> getContentTypes() {
return contentTypes;
}
Expand All @@ -51,6 +57,7 @@ public static class Builder {
private Map<String, Object> data;
private Map<String, String> descriptions;
private Map<String, String> contentTypes;
private Map<String, String> mcpDefaultEnabled;

private Builder() {
this.data = new HashMap<>();
Expand Down Expand Up @@ -88,6 +95,11 @@ public Builder descriptions(Map<String, String> descriptions) {
return this;
}

public Builder mcpDefaultEnables(Map<String, String> mcpDefaultEnabled) {
this.mcpDefaultEnabled = mcpDefaultEnabled;
return this;
}

public Builder contentTypes(Map<String, String> contentTypes) {
this.contentTypes = contentTypes;
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,31 +61,31 @@ public SubscriptionBuilder subscriptionBuilder() {
@Deprecated
public <T> void addAction(String methodName,
Function<Map<String, String>, T> action) {
this.addAction(new DeploymentJsonRpcMethod(methodName, null, Usage.onlyDevUI(), action));
this.addAction(new DeploymentJsonRpcMethod(methodName, null, Usage.onlyDevUI(), true, action));
}

@Deprecated
public <T> void addAssistantAction(String methodName,
BiFunction<Object, Map<String, String>, T> action) {
this.addAction(new DeploymentJsonRpcMethod(methodName, null, Usage.onlyDevUI(), action));
this.addAction(new DeploymentJsonRpcMethod(methodName, null, Usage.onlyDevUI(), true, action));
}

@Deprecated
public <T> void addAction(String methodName,
RuntimeValue runtimeValue) {
this.addAction(new RecordedJsonRpcMethod(methodName, null, Usage.onlyDevUI(), runtimeValue));
this.addAction(new RecordedJsonRpcMethod(methodName, null, Usage.onlyDevUI(), true, runtimeValue));
}

@Deprecated
public <T> void addSubscription(String methodName,
Function<Map<String, String>, T> action) {
this.addSubscription(new DeploymentJsonRpcMethod(methodName, null, Usage.onlyDevUI(), action));
this.addSubscription(new DeploymentJsonRpcMethod(methodName, null, Usage.onlyDevUI(), true, action));
}

@Deprecated
public <T> void addSubscription(String methodName,
RuntimeValue runtimeValue) {
this.addSubscription(new RecordedJsonRpcMethod(methodName, null, Usage.onlyDevUI(), runtimeValue));
this.addSubscription(new RecordedJsonRpcMethod(methodName, null, Usage.onlyDevUI(), true, runtimeValue));
}

private BuildTimeActionBuildItem addAction(DeploymentJsonRpcMethod deploymentJsonRpcMethod) {
Expand Down Expand Up @@ -116,6 +116,7 @@ public final class ActionBuilder {
private Function<Map<String, String>, ?> function;
private BiFunction<Object, Map<String, String>, ?> assistantFunction;
private RuntimeValue<?> runtimeValue;
private boolean mcpEnabledByDefault = false;

public ActionBuilder methodName(String methodName) {
this.methodName = methodName;
Expand All @@ -141,6 +142,11 @@ public ActionBuilder usage(EnumSet<Usage> usage) {
return this;
}

public ActionBuilder enableMcpFuctionByDefault() {
this.mcpEnabledByDefault = true;
return this;
}

public <T> ActionBuilder function(Function<Map<String, String>, T> function) {
if (this.runtimeValue != null || this.assistantFunction != null)
throw new IllegalStateException("Only one of runtimeValue, function or assistantFunction is allowed");
Expand Down Expand Up @@ -170,13 +176,17 @@ public BuildTimeActionBuildItem build() {
if (function != null) {
return addAction(
new DeploymentJsonRpcMethod(methodName, description, parameters, autoUsage(usage, description),
mcpEnabledByDefault,
function));
} else if (runtimeValue != null) {
return addAction(
new RecordedJsonRpcMethod(methodName, description, autoUsage(usage, description), runtimeValue));
new RecordedJsonRpcMethod(methodName, description, autoUsage(usage, description),
mcpEnabledByDefault,
runtimeValue));
} else if (assistantFunction != null) {
return addAction(
new DeploymentJsonRpcMethod(methodName, description, parameters, autoUsage(usage, description),
mcpEnabledByDefault,
assistantFunction));
} else {
throw new IllegalStateException("Either function, assistantFunction or runtimeValue must be provided");
Expand All @@ -189,6 +199,7 @@ public final class SubscriptionBuilder {
private String description;
private Map<String, AbstractJsonRpcMethod.Parameter> parameters = new LinkedHashMap<>();;
private EnumSet<Usage> usage;
private boolean mcpEnabledByDefault = false;
private Function<Map<String, String>, ?> function;
private RuntimeValue<?> runtimeValue;

Expand Down Expand Up @@ -216,6 +227,11 @@ public SubscriptionBuilder usage(EnumSet<Usage> usage) {
return this;
}

public SubscriptionBuilder enableMcpFuctionByDefault() {
this.mcpEnabledByDefault = true;
return this;
}

public <T> SubscriptionBuilder function(Function<Map<String, String>, T> function) {
this.function = function;
return this;
Expand All @@ -233,10 +249,12 @@ public void build() {
parameters = null;
if (function != null) {
addSubscription(new DeploymentJsonRpcMethod(methodName, description, parameters, autoUsage(usage, description),
mcpEnabledByDefault,
function));
} else if (runtimeValue != null) {
addSubscription(
new RecordedJsonRpcMethod(methodName, description, autoUsage(usage, description), runtimeValue));
new RecordedJsonRpcMethod(methodName, description, autoUsage(usage, description), mcpEnabledByDefault,
runtimeValue));
} else {
throw new IllegalStateException("Either function or runtimeValue must be provided");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public class BuildTimeData {
private Object content;
private String description;
private String contentType = "application/json"; // default
private boolean mcpEnabledByDefault = false;

public BuildTimeData() {

Expand All @@ -21,9 +22,16 @@ public BuildTimeData(Object content, String description) {
this.description = description;
}

public BuildTimeData(Object content, String description, String contentType) {
public BuildTimeData(Object content, String description, boolean mcpEnabledByDefault) {
this.content = content;
this.description = description;
this.mcpEnabledByDefault = mcpEnabledByDefault;
}

public BuildTimeData(Object content, String description, boolean mcpEnabledByDefault, String contentType) {
this.content = content;
this.description = description;
this.mcpEnabledByDefault = mcpEnabledByDefault;
this.contentType = contentType;
}

Expand All @@ -50,4 +58,12 @@ public String getContentType() {
public void setContentType(String contentType) {
this.contentType = contentType;
}

public boolean isMcpEnabledByDefault() {
return mcpEnabledByDefault;
}

public void setMcpEnabledByDefault(boolean mcpEnabledByDefault) {
this.mcpEnabledByDefault = mcpEnabledByDefault;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,27 +33,34 @@ public List<TemplateData> getTemplateDatas() {
}

public void add(String templatename, Map<String, Object> data) {
templateDatas.add(new TemplateData(templatename, templatename, data, Map.of(), Map.of())); // By default the template is used for only one file.
templateDatas.add(new TemplateData(templatename, templatename, data, Map.of(), Map.of(), Map.of())); // By default the template is used for only one file.
}

public void add(String templatename, String fileName, Map<String, Object> data, Map<String, String> descriptions,
Map<String, String> mcpDefaultEnabled,
Map<String, String> contentTypes) {
templateDatas.add(new TemplateData(templatename, fileName, data, descriptions, contentTypes));
templateDatas.add(new TemplateData(templatename, fileName, data, descriptions, mcpDefaultEnabled, contentTypes));
}

public static class TemplateData {
final String templateName;
final String fileName;
final Map<String, Object> data;
final Map<String, String> descriptions;
final Map<String, String> mcpDefaultEnabled;
final Map<String, String> contentTypes;

private TemplateData(String templateName, String fileName, Map<String, Object> data, Map<String, String> descriptions,
private TemplateData(String templateName,
String fileName,
Map<String, Object> data,
Map<String, String> descriptions,
Map<String, String> mcpDefaultEnabled,
Map<String, String> contentTypes) {
this.templateName = templateName;
this.fileName = fileName;
this.data = data;
this.descriptions = descriptions;
this.mcpDefaultEnabled = mcpDefaultEnabled;
this.contentTypes = contentTypes;
}

Expand All @@ -73,6 +80,10 @@ public Map<String, String> getDescriptions() {
return descriptions;
}

public Map<String, String> getMcpDefaultEnables() {
return mcpDefaultEnabled;
}

public Map<String, String> getContentTypes() {
return contentTypes;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,26 @@ public abstract class AbstractJsonRpcMethod {
private String description;
private Map<String, Parameter> parameters;
private EnumSet<Usage> usage;
private boolean mcpEnabledByDefault = false;

public AbstractJsonRpcMethod() {
}

public AbstractJsonRpcMethod(String methodName, String description,
EnumSet<Usage> usage) {
EnumSet<Usage> usage, boolean mcpEnabledByDefault) {
this.methodName = methodName;
this.description = description;
this.usage = usage;
this.mcpEnabledByDefault = mcpEnabledByDefault;
}

public AbstractJsonRpcMethod(String methodName, String description, Map<String, Parameter> parameters,
EnumSet<Usage> usage) {
EnumSet<Usage> usage, boolean mcpEnabledByDefault) {
this.methodName = methodName;
this.description = description;
this.parameters = parameters;
this.usage = usage;
this.mcpEnabledByDefault = mcpEnabledByDefault;
}

public String getMethodName() {
Expand Down Expand Up @@ -82,6 +85,14 @@ public void setUsage(EnumSet<Usage> usage) {
this.usage = usage;
}

public boolean isMcpEnabledByDefault() {
return mcpEnabledByDefault;
}

public void setMcpEnabledByDefault(boolean mcpEnabledByDefault) {
this.mcpEnabledByDefault = mcpEnabledByDefault;
}

public static class Parameter {
private Class<?> type;
private String description;
Expand Down
Loading
Loading