Skip to content

Commit 047ed7d

Browse files
Add extension skills support and MCP server instructions
- SkillReader: reads extension skills from the aggregated quarkus-extension-skills JAR instead of per-extension deployment JARs. Auto-downloads from Maven repos if not in local .m2, respecting mirrors from ~/.m2/settings.xml, ${MAVEN_HOME}/conf/settings.xml, and .mvn/maven.config. - Server instructions: configures quarkus.mcp.server.server-info.instructions to direct AI agents to use quarkus-agent tools (skills, searchDocs, callTool) over generic alternatives like Context7. - CLAUDE.md generation: quarkus/create now generates a CLAUDE.md in every new project with Quarkus-specific workflow rules, including running tests via subagents for non-blocking test execution. - DevMcpProxyTools: quarkus/skills tool updated to use the new SkillReader that reads from the single aggregated JAR. Depends on: - quarkusio/quarkus#53195 (aggregate-skills mechanism) - quarkusio/quarkus#53196 (extension skill files) - quarkusio/quarkus#53182 (one-shot testing) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c720bb7 commit 047ed7d

File tree

11 files changed

+1383
-95
lines changed

11 files changed

+1383
-95
lines changed

pom.xml

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
1414
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
1515
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
16-
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
17-
<quarkus.platform.version>3.32.3</quarkus.platform.version>
16+
<quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
17+
<quarkus.platform.version>999-SNAPSHOT</quarkus.platform.version>
18+
<quarkus-mcp-server.version>1.10.2</quarkus-mcp-server.version>
19+
<langchain4j.version>1.11.0-beta19</langchain4j.version>
1820
<skipITs>true</skipITs>
1921
<surefire-plugin.version>3.5.4</surefire-plugin.version>
2022
<testcontainers.version>1.21.3</testcontainers.version>
@@ -29,38 +31,27 @@
2931
<type>pom</type>
3032
<scope>import</scope>
3133
</dependency>
32-
<dependency>
33-
<groupId>${quarkus.platform.group-id}</groupId>
34-
<artifactId>quarkus-langchain4j-bom</artifactId>
35-
<version>${quarkus.platform.version}</version>
36-
<type>pom</type>
37-
<scope>import</scope>
38-
</dependency>
39-
<dependency>
40-
<groupId>${quarkus.platform.group-id}</groupId>
41-
<artifactId>quarkus-mcp-server-bom</artifactId>
42-
<version>${quarkus.platform.version}</version>
43-
<type>pom</type>
44-
<scope>import</scope>
45-
</dependency>
4634
</dependencies>
4735
</dependencyManagement>
4836

4937
<dependencies>
5038
<dependency>
5139
<groupId>io.quarkiverse.mcp</groupId>
5240
<artifactId>quarkus-mcp-server-stdio</artifactId>
41+
<version>${quarkus-mcp-server.version}</version>
5342
</dependency>
5443
<!-- pgvector embedding store - used directly (not via Quarkus extension)
5544
because the container port is dynamic (Testcontainers) -->
5645
<dependency>
5746
<groupId>dev.langchain4j</groupId>
5847
<artifactId>langchain4j-pgvector</artifactId>
48+
<version>${langchain4j.version}</version>
5949
</dependency>
6050
<!-- BGE Small EN v1.5 embedding model - must match chappie-docling-rag -->
6151
<dependency>
6252
<groupId>dev.langchain4j</groupId>
6353
<artifactId>langchain4j-embeddings-bge-small-en-v15-q</artifactId>
54+
<version>${langchain4j.version}</version>
6455
</dependency>
6556
<dependency>
6657
<groupId>io.quarkus</groupId>

src/main/java/io/quarkus/agent/mcp/CreateTools.java

Lines changed: 109 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import java.io.IOException;
66
import java.io.InputStreamReader;
77
import java.nio.charset.StandardCharsets;
8+
import java.nio.file.Files;
9+
import java.nio.file.Path;
810
import java.util.ArrayList;
911
import java.util.List;
1012
import java.util.regex.Pattern;
@@ -40,25 +42,24 @@ public class CreateTools {
4042
// Cache which command is available — doesn't change during the lifetime of the server
4143
private volatile String cachedCreateCommand;
4244

43-
@Tool(name = "quarkus/create", description = "Create a new Quarkus application project and automatically start it in dev mode. "
44-
+ "Creates a Maven or Gradle project with the specified extensions, then starts it. "
45-
+ "Once running, Quarkus dev mode provides hot reload — changes are recompiled when the "
46-
+ "app is next accessed (e.g., via an HTTP request or when tests run). "
47-
+ "DEVELOPMENT WORKFLOW: After the app is running, use quarkus/searchTools with query 'test' "
48-
+ "to find the continuous testing tools, then follow this cycle: "
49-
+ "1) Pause continuous testing before making code changes, "
50-
+ "2) Make and save all your code changes, "
51-
+ "3) Resume continuous testing — this triggers hot reload and runs tests against your updated code, "
52-
+ "4) Check quarkus/logs for test results and fix any failures. "
53-
+ "TIP: Use quarkus/searchDocs to look up Quarkus APIs and best practices before writing code.")
45+
@Tool(name = "quarkus/create", description = "Create a new Quarkus application and auto-start it in dev mode. "
46+
+ "RULES: 1) NEVER implement features manually when a Quarkus extension exists — "
47+
+ "always search for and add the right extension first (e.g. use quarkus-qute for templating, "
48+
+ "quarkus-smallrye-openapi for API docs, quarkus-smallrye-health for health checks). "
49+
+ "Use quarkus/searchDocs or quarkus/searchTools query='extension' to find extensions. "
50+
+ "2) Use quarkus/skills and quarkus/searchDocs BEFORE writing any code. "
51+
+ "3) ALWAYS write tests for every feature — no exceptions. "
52+
+ "4) Keep README.md updated with app description, features, endpoints, and Quarkus guide links after every change.")
5453
ToolResponse create(
5554
@ToolArg(description = "Absolute path to the directory where the project will be created. "
5655
+ "A subdirectory named after the artifactId will be created inside this directory.") String outputDir,
5756
@ToolArg(description = "The Maven groupId for the project (e.g. 'com.example')", required = false) String groupId,
5857
@ToolArg(description = "The Maven artifactId for the project (e.g. 'my-app').", required = false) String artifactId,
5958
@ToolArg(description = "Comma-separated list of Quarkus extensions to include "
6059
+ "(e.g. 'rest-jackson,hibernate-orm-panache,jdbc-postgresql')", required = false) String extensions,
61-
@ToolArg(description = "Build tool to use: 'maven' or 'gradle' (default: maven)", required = false) String buildTool) {
60+
@ToolArg(description = "Build tool to use: 'maven' or 'gradle' (default: maven)", required = false) String buildTool,
61+
@ToolArg(description = "Quarkus platform version to use (e.g. '3.21.2', '999-SNAPSHOT'). "
62+
+ "If omitted, uses the latest release.", required = false) String quarkusVersion) {
6263
try {
6364
String resolvedGroupId = (groupId != null && !groupId.isBlank()) ? groupId : "org.acme";
6465
String resolvedArtifactId = (artifactId != null && !artifactId.isBlank()) ? artifactId : "quarkus-app";
@@ -72,13 +73,17 @@ ToolResponse create(
7273
if (extensions != null && !extensions.isBlank() && !VALID_EXTENSIONS.matcher(extensions).matches()) {
7374
return ToolResponse.error("Invalid extensions: must contain only letters, digits, dots, hyphens, commas, colons.");
7475
}
76+
if (quarkusVersion != null && !quarkusVersion.isBlank() && !VALID_MAVEN_ID.matcher(quarkusVersion).matches()) {
77+
return ToolResponse.error("Invalid quarkusVersion: must contain only letters, digits, dots, hyphens, underscores.");
78+
}
7579

7680
File outDir = new File(outputDir);
7781
if (!outDir.isDirectory()) {
7882
return ToolResponse.error("Output directory does not exist: " + outputDir);
7983
}
8084

81-
List<String> command = buildCommand(outDir, resolvedGroupId, resolvedArtifactId, extensions, buildTool);
85+
List<String> command = buildCommand(outDir, resolvedGroupId, resolvedArtifactId, extensions, buildTool,
86+
quarkusVersion);
8287
LOG.infof("Creating Quarkus app: %s", String.join(" ", command));
8388

8489
ProcessBuilder pb = new ProcessBuilder(command)
@@ -101,14 +106,29 @@ ToolResponse create(
101106

102107
String projectDir = new File(outDir, resolvedArtifactId).getAbsolutePath();
103108

109+
// Generate CLAUDE.md with Quarkus-specific instructions
110+
generateClaudeMd(projectDir, extensions);
111+
104112
// Auto-start the app in dev mode
105113
try {
106114
processManager.start(projectDir, buildTool);
107115
LOG.infof("Auto-started Quarkus app at: %s", projectDir);
108116
return ToolResponse.success("Quarkus project created and starting in dev mode at: " + projectDir
109-
+ "\nHot reload is active — file changes are automatically detected and recompiled."
110-
+ "\nUse quarkus/status to check when it's ready."
111-
+ "\nUse quarkus/searchTools with query 'test' to find continuous testing tools.");
117+
+ "\n\nNEXT STEPS:"
118+
+ "\n1. Before implementing ANY feature, search for a Quarkus extension that provides it. "
119+
+ "Use quarkus/searchDocs to find extensions. NEVER roll your own solution when an extension exists "
120+
+ "(e.g. use quarkus-qute for templates, quarkus-smallrye-health for health checks, "
121+
+ "quarkus-smallrye-openapi for API docs, quarkus-mailer for email). "
122+
+ "Add extensions via quarkus/searchTools query='extension' → quarkus/callTool."
123+
+ "\n2. Use quarkus/skills to learn the correct patterns, testing approaches, and configuration for each extension."
124+
+ "\n3. Use quarkus/searchDocs to look up additional Quarkus APIs and best practices."
125+
+ "\n4. Write your code AND tests. Always include tests for every feature."
126+
+ "\n5. Run tests with quarkus/callTool: use 'devui-testing_runTests' to run all tests, "
127+
+ "'devui-testing_runAffectedTests' to run only tests affected by your changes, "
128+
+ "or 'devui-testing_runTest' with arguments {\"className\":\"com.example.MyTest\"} for a specific test."
129+
+ "\n6. Hot reload is triggered when tests run — do NOT restart the app."
130+
+ "\n7. Update README.md with: app description, features, endpoints, how to run, and links to Quarkus guides."
131+
+ "\n8. After core features work, suggest to the user: security, observability, health checks, OpenAPI.");
112132
} catch (Exception startError) {
113133
LOG.warnf("Project created but failed to auto-start: %s", startError.getMessage());
114134
return ToolResponse.success("Quarkus project created at: " + projectDir
@@ -122,12 +142,13 @@ ToolResponse create(
122142
}
123143

124144
private List<String> buildCommand(File outputDir, String groupId, String artifactId,
125-
String extensions, String buildTool) {
145+
String extensions, String buildTool, String quarkusVersion) {
126146
String cmd = resolveCreateCommand();
127147
return switch (cmd) {
128-
case "quarkus" -> buildQuarkusCliCommand("quarkus", groupId, artifactId, extensions, buildTool);
129-
case "mvn" -> buildMavenCommand(groupId, artifactId, extensions, buildTool);
130-
case "jbang" -> buildJBangCommand(groupId, artifactId, extensions, buildTool);
148+
case "quarkus" -> buildQuarkusCliCommand("quarkus", groupId, artifactId, extensions, buildTool,
149+
quarkusVersion);
150+
case "mvn" -> buildMavenCommand(groupId, artifactId, extensions, buildTool, quarkusVersion);
151+
case "jbang" -> buildJBangCommand(groupId, artifactId, extensions, buildTool, quarkusVersion);
131152
default -> throw new IllegalStateException("Unexpected command: " + cmd);
132153
};
133154
}
@@ -156,7 +177,7 @@ private String resolveCreateCommand() {
156177
}
157178

158179
private List<String> buildQuarkusCliCommand(String quarkusCmd, String groupId, String artifactId,
159-
String extensions, String buildTool) {
180+
String extensions, String buildTool, String quarkusVersion) {
160181
List<String> cmd = new ArrayList<>();
161182
cmd.add(quarkusCmd);
162183
cmd.add("create");
@@ -165,6 +186,9 @@ private List<String> buildQuarkusCliCommand(String quarkusCmd, String groupId, S
165186
cmd.add("--no-code");
166187
cmd.add("--batch-mode");
167188

189+
if (quarkusVersion != null && !quarkusVersion.isBlank()) {
190+
cmd.add("--platform-bom=io.quarkus:quarkus-bom:" + quarkusVersion);
191+
}
168192
if (extensions != null && !extensions.isBlank()) {
169193
cmd.add("--extension=" + extensions);
170194
}
@@ -176,7 +200,7 @@ private List<String> buildQuarkusCliCommand(String quarkusCmd, String groupId, S
176200
}
177201

178202
private List<String> buildJBangCommand(String groupId, String artifactId,
179-
String extensions, String buildTool) {
203+
String extensions, String buildTool, String quarkusVersion) {
180204
List<String> cmd = new ArrayList<>();
181205
cmd.add("jbang");
182206
cmd.add("quarkus@quarkusio");
@@ -186,6 +210,9 @@ private List<String> buildJBangCommand(String groupId, String artifactId,
186210
cmd.add("--no-code");
187211
cmd.add("--batch-mode");
188212

213+
if (quarkusVersion != null && !quarkusVersion.isBlank()) {
214+
cmd.add("--platform-bom=io.quarkus:quarkus-bom:" + quarkusVersion);
215+
}
189216
if (extensions != null && !extensions.isBlank()) {
190217
cmd.add("--extension=" + extensions);
191218
}
@@ -197,15 +224,25 @@ private List<String> buildJBangCommand(String groupId, String artifactId,
197224
}
198225

199226
private List<String> buildMavenCommand(String groupId, String artifactId,
200-
String extensions, String buildTool) {
227+
String extensions, String buildTool, String quarkusVersion) {
201228
List<String> cmd = new ArrayList<>();
202229
cmd.add("mvn");
203-
cmd.add("io.quarkus.platform:quarkus-maven-plugin:create");
230+
String pluginGroupId = "io.quarkus.platform";
231+
if (quarkusVersion != null && !quarkusVersion.isBlank()) {
232+
pluginGroupId = "io.quarkus";
233+
}
234+
cmd.add(pluginGroupId + ":quarkus-maven-plugin:"
235+
+ (quarkusVersion != null && !quarkusVersion.isBlank() ? quarkusVersion + ":" : "")
236+
+ "create");
204237
cmd.add("-DprojectGroupId=" + groupId);
205238
cmd.add("-DprojectArtifactId=" + artifactId);
206239
cmd.add("-DnoCode=true");
207240
cmd.add("-B");
208241

242+
if (quarkusVersion != null && !quarkusVersion.isBlank()) {
243+
cmd.add("-DplatformGroupId=io.quarkus");
244+
cmd.add("-DplatformVersion=" + quarkusVersion);
245+
}
209246
if (extensions != null && !extensions.isBlank()) {
210247
cmd.add("-Dextensions=" + extensions);
211248
}
@@ -245,4 +282,52 @@ private String captureOutput(Process process) throws IOException {
245282
return sb.toString().trim();
246283
}
247284
}
285+
286+
private void generateClaudeMd(String projectDir, String extensions) {
287+
try {
288+
String content = """
289+
# CLAUDE.md — Quarkus Project Instructions
290+
291+
This is a Quarkus application. Follow these rules when working on this project.
292+
293+
## Required Workflow
294+
295+
1. **Use quarkus/update (via subagent) when returning to this project** — checks if the Quarkus version is up-to-date and suggests upgrades.
296+
2. **Use quarkus/skills BEFORE writing any code or tests** — it contains extension-specific patterns, testing approaches, and common pitfalls that prevent mistakes.
297+
3. **Use quarkus/searchDocs for Quarkus documentation** — do NOT use generic documentation tools (Context7, web search). The Quarkus doc search is version-aware and more accurate.
298+
4. **Use quarkus/searchTools to discover Dev MCP tools** on the running app for testing, config changes, and extension management.
299+
5. **Use quarkus/callTool to invoke Dev MCP tools** — run tests, add extensions, update configuration. Do NOT run Maven/Gradle commands manually.
300+
6. **Hot reload is automatic** — the app recompiles when accessed. Do NOT restart the app after code changes.
301+
302+
## Rules
303+
304+
- NEVER implement features manually when a Quarkus extension exists — search for and add the right extension first.
305+
- ALWAYS write tests for every feature — no exceptions.
306+
- ALWAYS keep README.md updated with app description, features, endpoints, and Quarkus guide links.
307+
- Use `@QuarkusTest` for integration tests — Dev Services auto-starts backing services (databases, messaging, etc.).
308+
- Use `%dev.` and `%test.` profile prefixes for dev/test configuration — never hardcode connection URLs without a profile prefix.
309+
310+
## Testing
311+
312+
ALWAYS run tests using a **subagent** so the main conversation stays responsive:
313+
314+
```
315+
Use the Agent tool to launch a subagent with this prompt:
316+
"Run the Quarkus tests for project <projectDir> using quarkus/callTool
317+
with toolName 'devui-testing_runTests'. Analyze the results and report
318+
which tests passed, failed, or errored. If tests fail, include the
319+
failure messages and suggest fixes."
320+
```
321+
322+
- Use `devui-testing_runTests` to run all tests.
323+
- Use `devui-testing_runTest` with arguments `{"className":"com.example.MyTest"}` to run a specific test class.
324+
- Do NOT run Maven/Gradle test commands manually — the Dev MCP test tools handle compilation, hot reload, and result reporting.
325+
- After fixing test failures, re-run tests with a subagent to verify the fix.
326+
""";
327+
Files.writeString(Path.of(projectDir, "CLAUDE.md"), content, StandardCharsets.UTF_8);
328+
LOG.debugf("Generated CLAUDE.md in %s", projectDir);
329+
} catch (IOException e) {
330+
LOG.debugf("Failed to generate CLAUDE.md: %s", e.getMessage());
331+
}
332+
}
248333
}

0 commit comments

Comments
 (0)