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 6edffdd14288 CAMEL-24867: camel-jbang-mcp - expose the sql,
datasource, circuit breaker, metrics, spans, EIP stats, startup and route
analysis runtime tools
6edffdd14288 is described below
commit 6edffdd1428876e4c9494090ff944279781464f5
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Sep 21 11:08:33 2026 +0200
CAMEL-24867: camel-jbang-mcp - expose the sql, datasource, circuit breaker,
metrics, spans, EIP stats, startup and route analysis runtime tools
CAMEL-24867
The MCP server wraps the shared runtime tool registry in `RuntimeTools`,
but only 27 of its runtime tools. A model working through the MCP server on an
app with a datasource could not look at the database at all, nor at circuit
breakers, metrics, spans, EIP statistics, startup steps, route analysis or
configuration drift, although the running app answers all of them and the
camel-jbang views and the agent REPL already have them.
This adds ten wrappers with the same `camel_runtime_*` naming and
delegation as the existing ones:
| Tool | Registry tool |
|---|---|
| `camel_runtime_sql` | `execute_sql` |
| `camel_runtime_datasources` | `get_datasources` |
| `camel_runtime_sql_trace` | `get_sql_trace` |
| `camel_runtime_circuit_breakers` | `get_circuit_breakers` |
| `camel_runtime_metrics` | `get_metrics` |
| `camel_runtime_eip_stats` | `get_eip_stats` |
| `camel_runtime_spans` | `get_spans` |
| `camel_runtime_startup_steps` | `get_startup_steps` |
| `camel_runtime_route_analysis` | `get_route_analysis` |
| `camel_runtime_config_drift` | `detect_config_drift` |
The remaining unwrapped registry tools are covered by other MCP tools
already (catalog, examples, errors, eval, control, heap dump, processes).
Documented in a new "Data, Resilience and Analysis" table of the
camel-jbang-mcp page. Tests: `RuntimeToolsTest` checks the required argument of
the sql tool and that every wrapper names a registry tool that exists.
Found while preparing the CAMEL-24834 tool-group experiment on the
camel-jbang-mcp server: the sql example on H2 was meant to compare the
authoring set with and without the sql tools, and the server had none of them.
---
.../modules/ROOT/pages/camel-jbang-mcp.adoc | 40 +++++
.../dsl/jbang/core/commands/mcp/RuntimeTools.java | 174 +++++++++++++++++----
.../jbang/core/commands/mcp/RuntimeToolsTest.java | 18 +++
3 files changed, 205 insertions(+), 27 deletions(-)
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
b/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
index ec63967b8187..30f7ee2a02e2 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
@@ -690,6 +690,46 @@ process is running).
| Get a JVM thread dump showing thread names, states, and stack traces.
|===
+==== Data, Resilience and Analysis
+
+[cols="1,3",options="header"]
+|===
+| Tool | Description
+
+| `camel_runtime_sql`
+| Execute a SQL statement against a DataSource of the running application:
columns and rows for a SELECT,
+ the update count otherwise. Lets the model look at the data a route reads or
writes, or try a statement
+ before putting it in a route.
+
+| `camel_runtime_datasources`
+| Datasource connection pool status: active, idle and total connections, max
pool size and waiting threads.
+
+| `camel_runtime_sql_trace`
+| Traced SQL executions of the camel-sql and camel-jdbc components with
timing, row counts, route ID and
+ failure status.
+
+| `camel_runtime_circuit_breakers`
+| Circuit breaker state (CLOSED/OPEN/HALF_OPEN), call counts, failure rates
and not-permitted calls.
+
+| `camel_runtime_metrics`
+| Micrometer metrics: counters, gauges, timers and distributions (requires
`--observe` or camel-micrometer).
+
+| `camel_runtime_eip_stats`
+| EIP usage across all routes with processor counts and performance figures.
+
+| `camel_runtime_spans`
+| OpenTelemetry spans with trace IDs, durations, route IDs and status
(requires `--observe`).
+
+| `camel_runtime_startup_steps`
+| Startup recorder steps with duration, level and type (requires
`camel.main.startup-recorder=true`).
+
+| `camel_runtime_route_analysis`
+| Anti-pattern and structure analysis per route: error handling, complexity,
component usage.
+
+| `camel_runtime_config_drift`
+| Compares the running route definitions with the source files to detect
configuration drift.
+|===
+
==== Configuration and Registry
[cols="1,3",options="header"]
diff --git
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
index 7a6f7fa9b495..2432e3a5212e 100644
---
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
+++
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
@@ -115,77 +115,77 @@ public class RuntimeTools {
@Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
description = "Get Camel context information: name, version, state,
uptime, route count, exchange statistics.")
public JsonObject camel_runtime_context(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid) {
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid) {
return delegateToRegistry("get_context", nameOrPid, Map.of());
}
@Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
description = "List Camel routes with their state, uptime, messages
processed, last error, and throughput statistics.")
public JsonObject camel_runtime_routes(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid) {
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid) {
return delegateToRegistry("get_routes", nameOrPid, Map.of());
}
@Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
description = "Get health check status for the Camel application.")
public JsonObject camel_runtime_health(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid) {
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid) {
return delegateToRegistry("get_health", nameOrPid, Map.of());
}
@Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
description = "List all endpoints registered in the Camel context
with their URIs and usage statistics.")
public JsonObject camel_runtime_endpoints(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid) {
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid) {
return delegateToRegistry("get_endpoints", nameOrPid, Map.of());
}
@Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
description = "Show currently in-flight exchanges (messages being
processed).")
public JsonObject camel_runtime_inflight(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid) {
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid) {
return delegateToRegistry("get_inflight", nameOrPid, Map.of());
}
@Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
description = "Show blocked exchanges that are stuck or waiting.")
public JsonObject camel_runtime_blocked(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid) {
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid) {
return delegateToRegistry("get_blocked", nameOrPid, Map.of());
}
@Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
description = "Show exchange variables in the Camel context.")
public JsonObject camel_runtime_variables(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid) {
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid) {
return delegateToRegistry("get_variables", nameOrPid, Map.of());
}
@Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
description = "Show consumer statistics (polling consumers,
event-driven consumers).")
public JsonObject camel_runtime_consumers(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid) {
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid) {
return delegateToRegistry("get_consumers", nameOrPid, Map.of());
}
@Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
description = "Show configuration properties of the running Camel
application.")
public JsonObject camel_runtime_properties(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid) {
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid) {
return delegateToRegistry("get_properties", nameOrPid, Map.of());
}
@Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
description = "Show services registered in the Camel service
registry.")
public JsonObject camel_runtime_services(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid) {
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid) {
return delegateToRegistry("get_services", nameOrPid, Map.of());
}
@Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
description = "Show JVM memory usage (heap/non-heap), garbage
collection stats, and thread counts.")
public JsonObject camel_runtime_memory(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid) {
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid) {
return delegateToRegistry("get_memory", nameOrPid, Map.of());
}
@@ -194,7 +194,7 @@ public class RuntimeTools {
@Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
description = "Get the source code of routes in the running Camel
application.")
public JsonObject camel_runtime_route_source(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid,
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid,
@ToolArg(description = "Filter source files by name (supports
wildcards)") String filter) {
return delegateToRegistry("get_route_source", nameOrPid,
Map.of("filter", filter != null ? filter : "*"));
@@ -203,7 +203,7 @@ public class RuntimeTools {
@Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
description = "Dump route definitions in XML, YAML, or Java DSL
format.")
public JsonObject camel_runtime_route_dump(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid,
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid,
@ToolArg(description = "Route ID to dump (use * for all routes)")
String routeId,
@ToolArg(description = "Output format: xml, yaml, or java
(default: yaml)") String format) {
Map<String, String> args = new HashMap<>();
@@ -215,7 +215,7 @@ public class RuntimeTools {
@Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
description = "Show the route structure as a tree of processors.")
public JsonObject camel_runtime_route_structure(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid,
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid,
@ToolArg(description = "Route ID to inspect (use * for all
routes)") String routeId) {
return delegateToRegistry("get_route_structure", nameOrPid,
Map.of("routeId", routeId != null ? routeId : "*"));
@@ -228,7 +228,7 @@ public class RuntimeTools {
Use includeDocs=true to enrich the response with
documentation from the Camel catalog \
for each EIP option and component endpoint option.""")
public JsonObject camel_runtime_processor_detail(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid,
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid,
@ToolArg(description = "Route ID to inspect (use * for all
routes)") String routeId,
@ToolArg(description = "If true, enrich each processor's options
with documentation from the Camel catalog") Boolean includeDocs) {
Map<String, String> args = new HashMap<>();
@@ -242,7 +242,7 @@ public class RuntimeTools {
@Tool(annotations = @Tool.Annotations(readOnlyHint = false,
destructiveHint = true, openWorldHint = false),
description = "Control a route: start, stop, suspend, or resume it.")
public JsonObject camel_runtime_route_control(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid,
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid,
@ToolArg(description = "Route ID to control") String routeId,
@ToolArg(description = "Command: start, stop, suspend, or resume")
String command) {
if (routeId == null || routeId.isBlank()) {
@@ -263,10 +263,130 @@ public class RuntimeTools {
return delegateToRegistry(toolName, nameOrPid, Map.of("routeId",
routeId));
}
+ // ---- Data, resilience, metrics and analysis (CAMEL-24867: the rest of
the shared runtime registry) ----
+
+ @Tool(annotations = @Tool.Annotations(readOnlyHint = false,
destructiveHint = true, openWorldHint = false),
+ description = """
+ Execute a SQL statement against a DataSource of the running
Camel application. \
+ Returns columns, rows and metadata for a SELECT, or the
update count for INSERT/UPDATE/DELETE. \
+ Use it to look at the data a route reads or writes, or to
try a statement before putting it in a route.""")
+ public JsonObject camel_runtime_sql(
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid,
+ @ToolArg(description = "The SQL statement to execute") String
query,
+ @ToolArg(description = "Name of the DataSource bean (auto-detected
if only one exists)",
+ required = false) String datasource,
+ @ToolArg(description = "Maximum number of rows to return (default
100)", required = false) String maxRows) {
+ if (query == null || query.isBlank()) {
+ throw new ToolCallException("query is required", null);
+ }
+ Map<String, String> args = new HashMap<>();
+ args.put("query", query);
+ putIfNotBlank(args, "datasource", datasource);
+ putIfNotBlank(args, "maxRows", maxRows);
+ return delegateToRegistry("execute_sql", nameOrPid, args);
+ }
+
+ @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
+ description = """
+ Get the datasources of the running Camel application with
their connection pool status: \
+ active, idle and total connections, max pool size and
waiting threads (HikariCP and Agroal).""")
+ public JsonObject camel_runtime_datasources(
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid) {
+ return delegateToRegistry("get_datasources", nameOrPid, Map.of());
+ }
+
+ @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
+ description = """
+ Get the traced SQL executions of the camel-sql and
camel-jdbc components: per statement timing, \
+ row counts, category (SELECT/INSERT/UPDATE/DELETE), route ID
and failure status, with summary statistics.""")
+ public JsonObject camel_runtime_sql_trace(
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid) {
+ return delegateToRegistry("get_sql_trace", nameOrPid, Map.of());
+ }
+
+ @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
+ description = """
+ Get the circuit breakers of the running Camel application:
state (CLOSED/OPEN/HALF_OPEN), \
+ call counts, failure rates and not-permitted calls
(Resilience4j and MicroProfile Fault Tolerance).""")
+ public JsonObject camel_runtime_circuit_breakers(
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid) {
+ return delegateToRegistry("get_circuit_breakers", nameOrPid, Map.of());
+ }
+
+ @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
+ description = """
+ Get the Micrometer metrics of the running Camel application:
counters, gauges, timers and \
+ distributions. Requires micrometer (run with --observe or
add camel-micrometer).""")
+ public JsonObject camel_runtime_metrics(
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid) {
+ return delegateToRegistry("get_metrics", nameOrPid, Map.of());
+ }
+
+ @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
+ description = """
+ Aggregate EIP usage across all routes: which processors are
used, how often, and their \
+ performance (total exchanges, failures, mean and max
processing time).""")
+ public JsonObject camel_runtime_eip_stats(
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid) {
+ return delegateToRegistry("get_eip_stats", nameOrPid, Map.of());
+ }
+
+ @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
+ description = """
+ Get the OpenTelemetry spans of the running Camel
application: trace IDs, span names, durations, \
+ route IDs and status. Requires tracing (run with
--observe).""")
+ public JsonObject camel_runtime_spans(
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid,
+ @ToolArg(description = "Maximum number of spans to return (default
100)", required = false) String limit) {
+ Map<String, String> args = new HashMap<>();
+ putIfNotBlank(args, "limit", limit);
+ return delegateToRegistry("get_spans", nameOrPid, args);
+ }
+
+ @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
+ description = """
+ Get the startup recorder steps of the running Camel
application: each step with duration, level \
+ and type, for diagnosing a slow startup. Requires
camel.main.startup-recorder=true.""")
+ public JsonObject camel_runtime_startup_steps(
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid) {
+ return delegateToRegistry("get_startup_steps", nameOrPid, Map.of());
+ }
+
+ @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
+ description = """
+ Analyze the routes of the running Camel application for
anti-patterns and structural issues: \
+ error handler and dead letter channel configuration,
complexity (processor count and nesting depth), \
+ component usage and potential issues, per route.""")
+ public JsonObject camel_runtime_route_analysis(
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid,
+ @ToolArg(description = "Route ID to analyze (* for all routes, the
default)", required = false) String routeId) {
+ Map<String, String> args = new HashMap<>();
+ putIfNotBlank(args, "routeId", routeId);
+ return delegateToRegistry("get_route_analysis", nameOrPid, args);
+ }
+
+ @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
+ description = """
+ Compare the running route definitions with the original
source files, to detect configuration \
+ drift between what runs and what is deployed.""")
+ public JsonObject camel_runtime_config_drift(
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid,
+ @ToolArg(description = "Dump format for the comparison: yaml
(default) or xml", required = false) String format) {
+ Map<String, String> args = new HashMap<>();
+ putIfNotBlank(args, "format", format);
+ return delegateToRegistry("detect_config_drift", nameOrPid, args);
+ }
+
+ private static void putIfNotBlank(Map<String, String> args, String key,
String value) {
+ if (value != null && !value.isBlank()) {
+ args.put(key, value);
+ }
+ }
+
@Tool(annotations = @Tool.Annotations(readOnlyHint = false,
destructiveHint = true, openWorldHint = false),
description = "Send a test message to a Camel endpoint in the
running application.")
public JsonObject camel_runtime_send(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid,
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid,
@ToolArg(description = "Endpoint URI to send to (e.g.,
direct:myRoute, seda:queue)") String endpoint,
@ToolArg(description = "Message body to send") String body,
@ToolArg(description = "Message headers as key=value pairs
separated by newlines") String headers) {
@@ -287,7 +407,7 @@ public class RuntimeTools {
@Tool(annotations = @Tool.Annotations(readOnlyHint = false,
destructiveHint = false, openWorldHint = false),
description = "Enable, disable, or dump message tracing for the
running Camel application.")
public JsonObject camel_runtime_trace(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid,
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid,
@ToolArg(description = "Action: enable, disable, or dump") String
action) {
if (action == null || action.isBlank()) {
throw new ToolCallException("action is required (enable, disable,
dump)", null);
@@ -298,7 +418,7 @@ public class RuntimeTools {
@Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
description = "Show top processor statistics: which processors are
slowest and most active.")
public JsonObject camel_runtime_top(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid) {
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid) {
return delegateToRegistry("get_top_processors", nameOrPid, Map.of());
}
@@ -307,7 +427,7 @@ public class RuntimeTools {
Get the inter-route topology showing how routes connect to
each other \
and to external endpoints. Returns nodes and edges
describing the route graph.""")
public JsonObject camel_runtime_route_topology(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid,
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid,
@ToolArg(description = "Include live metrics (message counts,
throughput) on nodes and edges") Boolean metric,
@ToolArg(description = "Include external systems (databases,
messaging brokers, etc.) as nodes") Boolean external) {
Map<String, String> args = new HashMap<>();
@@ -319,7 +439,7 @@ public class RuntimeTools {
@Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
description = "Get a JVM thread dump showing thread names, states,
and stack traces.")
public JsonObject camel_runtime_thread_dump(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid) {
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid) {
return delegateToRegistry("get_thread_dump", nameOrPid, Map.of());
}
@@ -328,7 +448,7 @@ public class RuntimeTools {
Get a class-level heap histogram showing instance counts and
byte usage per class. \
Useful for diagnosing memory leaks and understanding which
classes dominate heap usage.""")
public JsonObject camel_runtime_heap_histogram(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid) {
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid) {
return delegateToRegistry("get_heap_histogram", nameOrPid, Map.of());
}
@@ -337,7 +457,7 @@ public class RuntimeTools {
Write a heap dump (.hprof) file from a running Camel
integration for deep memory analysis \
with tools like Eclipse MAT, VisualVM, or jhat. The dump is
written to the process working directory.""")
public JsonObject camel_runtime_heap_dump(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid,
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid,
@ToolArg(description = "File name for the heap dump (without
.hprof extension). Defaults to heap-dump-<timestamp>") String name,
@ToolArg(description = "Whether to dump only live objects (default
true). Live dumps trigger a GC first") String live) {
RuntimeService.ProcessInfo p =
runtimeService.findSingleProcess(nameOrPid);
@@ -365,7 +485,7 @@ public class RuntimeTools {
Entries with lowConfidence=true have unreliable growth
percentages due to low sample counts or \
sample counts that diverge significantly between runs —
recommend a longer recording duration.""")
public JsonObject camel_runtime_memory_leak(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid,
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid,
@ToolArg(description = "Command: start, stop, status, or query")
String command,
@ToolArg(description = "Recording duration in seconds (only for
start command, default 60, use 0 for manual stop)") String duration,
@ToolArg(description = "Recording mode: dual (default, two
recordings at Xs and 2Xs with trend comparison) or single (one recording)")
String mode,
@@ -501,7 +621,7 @@ public class RuntimeTools {
This is always captured (no need to enable tracing) and
shows the single most recent exchange \
with its route path, processors visited, headers, body, and
timing.""")
public JsonObject camel_runtime_history(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid) {
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid) {
return delegateToRegistry("get_history", nameOrPid, Map.of());
}
@@ -510,7 +630,7 @@ public class RuntimeTools {
Receive (poll) a message from a Camel endpoint in the
running application. \
This is the complement to camel_runtime_send — it consumes
one message from the endpoint.""")
public JsonObject camel_runtime_receive(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid,
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid,
@ToolArg(description = "Endpoint URI to receive from") String
endpoint) {
if (endpoint == null || endpoint.isBlank()) {
throw new ToolCallException("endpoint is required", null);
@@ -526,7 +646,7 @@ public class RuntimeTools {
@Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
description = "Browse messages in a Camel endpoint (e.g., browse
messages queued in a SEDA endpoint).")
public JsonObject camel_runtime_browse(
- @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid,
+ @ToolArg(description = NAME_OR_PID_DESC, required = false) String
nameOrPid,
@ToolArg(description = "Endpoint URI to browse") String endpoint,
@ToolArg(description = "Maximum number of messages to return
(default: 50)") Integer limit) {
if (endpoint == null || endpoint.isBlank()) {
diff --git
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeToolsTest.java
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeToolsTest.java
index 74feb4d32e19..851060c78f88 100644
---
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeToolsTest.java
+++
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeToolsTest.java
@@ -19,6 +19,7 @@ package org.apache.camel.dsl.jbang.core.commands.mcp;
import java.util.List;
import io.quarkiverse.mcp.server.ToolCallException;
+import org.apache.camel.dsl.jbang.core.commands.ai.ToolRegistry;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -66,6 +67,23 @@ class RuntimeToolsTest {
.hasMessageContaining("command is required");
}
+ @Test
+ void sqlRequiresQuery() {
+ RuntimeTools tools = createTools();
+ assertThatThrownBy(() -> tools.camel_runtime_sql(null, null, null,
null))
+ .isInstanceOf(ToolCallException.class)
+ .hasMessageContaining("query is required");
+ }
+
+ @Test
+ void theNewWrappersDelegateToRegistryTools() {
+ // CAMEL-24867: every wrapper names a tool the shared registry has, so
a typo cannot hide until runtime
+ for (String name : List.of("execute_sql", "get_datasources",
"get_sql_trace", "get_circuit_breakers", "get_metrics",
+ "get_eip_stats", "get_spans", "get_startup_steps",
"get_route_analysis", "detect_config_drift")) {
+ assertThat(ToolRegistry.findTool(name)).as(name).isNotNull();
+ }
+ }
+
@Test
void sendRequiresEndpoint() {
RuntimeTools tools = createTools();