This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 45343260ef56 CAMEL-23529: camel-jbang-mcp - add ascii/unicode theme
support to route diagram tool (#23306)
45343260ef56 is described below
commit 45343260ef569a3ec0051553df23a547a49c33df
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon May 18 21:31:53 2026 +0200
CAMEL-23529: camel-jbang-mcp - add ascii/unicode theme support to route
diagram tool (#23306)
The MCP camel_render_route_diagram tool now supports 'ascii' and 'unicode'
themes in addition to the existing PNG image themes (dark, light,
transparent).
For text themes the diagram is returned directly as the asciiDiagram field
of
the result so that LLM callers can read and reason about the route structure
without needing to open a file. The temporary output file uses a .txt suffix
in text mode so CamelRouteDiagramAction writes to the correct path.
Signed-off-by: Claus Ibsen <[email protected]>
Co-authored-by: Claude Sonnet 4.6 <[email protected]>
---
.../jbang/core/commands/mcp/RouteDiagramTools.java | 50 ++++++++++++++++------
.../core/commands/mcp/RouteDiagramToolsTest.java | 20 +++++++++
2 files changed, 56 insertions(+), 14 deletions(-)
diff --git
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RouteDiagramTools.java
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RouteDiagramTools.java
index 452ee8892eaf..0ecdb4f8ce5b 100644
---
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RouteDiagramTools.java
+++
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RouteDiagramTools.java
@@ -19,6 +19,7 @@ package org.apache.camel.dsl.jbang.core.commands.mcp;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.Set;
import jakarta.enterprise.context.ApplicationScoped;
@@ -30,25 +31,32 @@ import
org.apache.camel.dsl.jbang.core.commands.action.CamelRouteDiagramAction;
import org.apache.camel.dsl.jbang.core.common.Printer;
/**
- * MCP Tool for generating a visual PNG diagram of Camel routes from a source
file (non-running integration). Wraps the
- * {@code camel route-diagram} jbang command.
+ * MCP Tool for generating a visual diagram of Camel routes from a source file
(non-running integration). Wraps the
+ * {@code camel route-diagram} jbang command. Supports both PNG image output
and plain-text ASCII/Unicode output.
*/
@ApplicationScoped
public class RouteDiagramTools {
+ private static final Set<String> TEXT_THEMES = Set.of("ascii", "unicode");
+
@Tool(annotations = @Tool.Annotations(readOnlyHint = false,
destructiveHint = false, openWorldHint = false),
- description = "Generate a visual PNG diagram of Camel routes from a
route source file (YAML, XML, Java, ...). "
- + "The source file is parsed without running it. The
resulting PNG is written to disk and the "
- + "absolute path is returned so it can be displayed or
shared. Useful to visualize a route "
- + "structure for review, documentation, or
troubleshooting.")
+ description = "Generate a diagram of Camel routes from a route
source file (YAML, XML, Java, ...). "
+ + "The source file is parsed without running it. "
+ + "For image themes (dark, light, transparent) the PNG
is written to disk and the absolute path "
+ + "is returned. For text themes (ascii, unicode) the
diagram is returned directly as text so "
+ + "it can be read and understood by the caller. "
+ + "Useful to visualize a route structure for review,
documentation, or troubleshooting.")
public RouteDiagramResult camel_render_route_diagram(
@ToolArg(description = "Absolute or relative path to the Camel
route source file (YAML, XML, Java, ...)") String sourceFile,
- @ToolArg(description = "Optional output PNG file path. If not
specified, a temporary file is created.") String outputFile,
- @ToolArg(description = "Color theme: 'dark' (default), 'light',
'transparent', or a custom spec like "
- + "'bg=#1e1e1e:from=#2e7d32:to=#1565c0'")
String theme,
+ @ToolArg(description = "Optional output file path. For image
themes a PNG is written; for text themes a .txt "
+ + "file is written. If not specified, a
temporary file is created.") String outputFile,
+ @ToolArg(description = "Color theme: 'dark' (default), 'light',
'transparent', 'ascii' (plain ASCII art), "
+ + "'unicode' (box-drawing characters), or a
custom spec like "
+ + "'bg=#1e1e1e:from=#2e7d32:to=#1565c0'. "
+ + "Use 'ascii' or 'unicode' to get a text
diagram that can be read directly.") String theme,
@ToolArg(description = "Optional filter to limit the diagram to
routes whose route id or source filename "
+ "matches the given pattern (supports
wildcards)") String filter,
- @ToolArg(description = "Image width in pixels; 0 (or unset) =
auto") Integer width,
+ @ToolArg(description = "Image width in pixels; 0 (or unset) = auto
(only used for image themes)") Integer width,
@ToolArg(description = "Font size in logical pixels for node text
(default 12)") Integer fontSize,
@ToolArg(description = "Node box width in logical pixels (default
180)") Integer boxWidth,
@ToolArg(description = "What text to display in diagram nodes:
'code' (default), 'description' (prefer "
@@ -64,10 +72,13 @@ public class RouteDiagramTools {
throw new ToolCallException("Source file does not exist: " +
sourceFile, null);
}
+ boolean textMode = theme != null &&
TEXT_THEMES.contains(theme.toLowerCase());
+
String resolvedOutput;
try {
if (outputFile == null || outputFile.isBlank()) {
- Path tmp = Files.createTempFile("camel-route-diagram-",
".png");
+ String suffix = textMode ? ".txt" : ".png";
+ Path tmp = Files.createTempFile("camel-route-diagram-",
suffix);
resolvedOutput = tmp.toAbsolutePath().toString();
} else {
resolvedOutput = new File(outputFile).getAbsolutePath();
@@ -97,17 +108,28 @@ public class RouteDiagramTools {
boolean success = exit == 0 && out.isFile() && out.length() > 0;
long size = out.exists() ? out.length() : 0L;
+ if (textMode) {
+ String asciiContent = success ? Files.readString(out.toPath())
: null;
+ String message = success
+ ? "ASCII diagram generated (" + size + " bytes)"
+ : "Failed to render diagram (exit code " + exit + ")";
+ return new RouteDiagramResult(success, resolvedOutput, size,
message, asciiContent);
+ }
+
String message = success
? "Diagram saved to: " + resolvedOutput
: "Failed to render diagram (exit code " + exit + ")";
-
- return new RouteDiagramResult(success, resolvedOutput, size,
message);
+ return new RouteDiagramResult(success, resolvedOutput, size,
message, null);
} catch (Throwable e) {
throw new ToolCallException(
"Failed to render route diagram (" +
e.getClass().getName() + "): " + e.getMessage(), null);
}
}
- public record RouteDiagramResult(boolean success, String outputFile, long
sizeBytes, String message) {
+ /**
+ * @param asciiDiagram plain-text diagram content for ascii/unicode
themes; null for image themes
+ */
+ public record RouteDiagramResult(boolean success, String outputFile, long
sizeBytes, String message,
+ String asciiDiagram) {
}
}
diff --git
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/RouteDiagramToolsTest.java
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/RouteDiagramToolsTest.java
index fcceec06a600..a257762b4bf6 100644
---
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/RouteDiagramToolsTest.java
+++
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/RouteDiagramToolsTest.java
@@ -50,4 +50,24 @@ class RouteDiagramToolsTest {
.isInstanceOf(ToolCallException.class)
.hasMessageContaining("does not exist");
}
+
+ @Test
+ void nonExistingSourceFileWithAsciiThemeThrows() {
+ RouteDiagramTools tools = new RouteDiagramTools();
+
+ assertThatThrownBy(() -> tools.camel_render_route_diagram(
+ "/no/such/file.yaml", null, "ascii", null, null, null, null,
null, null))
+ .isInstanceOf(ToolCallException.class)
+ .hasMessageContaining("does not exist");
+ }
+
+ @Test
+ void nonExistingSourceFileWithUnicodeThemeThrows() {
+ RouteDiagramTools tools = new RouteDiagramTools();
+
+ assertThatThrownBy(() -> tools.camel_render_route_diagram(
+ "/no/such/file.yaml", null, "unicode", null, null, null, null,
null, null))
+ .isInstanceOf(ToolCallException.class)
+ .hasMessageContaining("does not exist");
+ }
}